diff --git a/_const/enum/cp_e/cp_relation.go b/_const/enum/cp_e/cp_relation.go new file mode 100644 index 0000000000000000000000000000000000000000..5d55d0ebf011b169e3507297c81ff18480e56431 --- /dev/null +++ b/_const/enum/cp_e/cp_relation.go @@ -0,0 +1,6 @@ +package cp_e + +const ( + //新用户 + CpRelationInviteDiamond = 18888 +) diff --git a/cv/cp_cv/cp_relation.go b/cv/cp_cv/cp_relation.go new file mode 100644 index 0000000000000000000000000000000000000000..b5df88cc0150d0c817848dbd2c4eae1cf664cf65 --- /dev/null +++ b/cv/cp_cv/cp_relation.go @@ -0,0 +1,5 @@ +package cp_cv + +type CheckCpRelationRes struct { + NoBind bool `json:"isBind"` +} diff --git a/cv/user_cv/user.go b/cv/user_cv/user.go new file mode 100644 index 0000000000000000000000000000000000000000..24eb08ae7409be2103959a5596bd95da47014a7b --- /dev/null +++ b/cv/user_cv/user.go @@ -0,0 +1,16 @@ +package user_cv + +type CvUserTiny struct { + Id uint64 `json:"id,omitempty"` + ExternalId string `json:"externalId"` + Avatar string `json:"avatar"` + Nick string `json:"nick"` + Sex uint8 `json:"sex"` + Code string `json:"code"` + Country string `json:"country"` + CountryIcon string `json:"countryIcon"` + IsPrettyCode bool `json:"isPrettyCode"` // 是否靓号 + IsLogout bool `json:"isLogout"` //是否注销 true:已经注销, false:没有注销 + //生日,如果是其它人用户信息,年龄则按照是否展示显示,如果是本人,年龄则按照是否存在展示 + Birthday *uint64 `json:"birthday"` +} diff --git a/domain/model/cp_m/cp_relation.go b/domain/model/cp_m/cp_relation.go new file mode 100644 index 0000000000000000000000000000000000000000..b15cd0b023046378f6891ae7bba3af88456bf79b --- /dev/null +++ b/domain/model/cp_m/cp_relation.go @@ -0,0 +1,41 @@ +package cp_m + +import ( + "git.hilo.cn/hilo-common/domain" + "gorm.io/gorm" + "hilo-user/myerr/bizerr" + "time" +) + +type CpRelation struct { + Id uint64 `json:"id"` + UserId1 uint64 `json:"userId1"` + UserId2 uint64 `json:"userId2"` + CreatedTime time.Time `json:"createdTime"` +} + +func CreateCp(model *domain.Model, userId1, userId2 uint64) error { + userIds := []uint64{userId1, userId2} + result := model.DB().Exec("insert into cp_relation(user_id1, user_id2) values(?,?) where not exists (select user_id1 from cp_relation where user_id1 in (?) or user_id2 in (?));", userId1, userId2, userIds, userIds) + if result.Error != nil { + model.Log.Errorf("CreateCp user1:%d, user2:%d, err:%v", userId1, userId2, result.Error) + return result.Error + } + if result.RowsAffected <= 0 { + return bizerr.TransactionFailed + } + return nil +} + +func GetCp(model *domain.Model, userId uint64) (*CpRelation, error) { + res := new(CpRelation) + err := model.DB().Model(CpRelation{}).Where("user_id1 = ? or user_id2 = ?", userId, userId).First(&res).Error + if err != nil { + if err == gorm.ErrRecordNotFound { + return res, nil + } + model.Log.Errorf("CreateCp userId:%d, err:%v", userId, err) + return nil, err + } + return res, nil +} diff --git a/domain/model/diamond_m/diamond.go b/domain/model/diamond_m/diamond.go new file mode 100644 index 0000000000000000000000000000000000000000..22b63b09cd28aa404b98abfea955265d4b46d48e --- /dev/null +++ b/domain/model/diamond_m/diamond.go @@ -0,0 +1,190 @@ +package diamond_m + +import ( + "git.hilo.cn/hilo-common/_const/enum/diamond_e" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/resource/mysql" + "git.hilo.cn/hilo-common/utils" + "hilo-user/myerr" + "hilo-user/myerr/bizerr" + "strconv" + "time" +) + +type DiamondAccount struct { + mysql.Entity + *domain.Model `gorm:"-"` + UserId mysql.ID + DiamondNum mysql.Num + PinkDiamondNum mysql.Num + Status diamond_e.StatusAccount +} + +//账号详情 +type DiamondAccountDetail struct { + mysql.Entity + *domain.Model `gorm:"-"` + UserId mysql.ID + DiamondAccountId mysql.ID + OperateId mysql.ID + OperateType diamond_e.OperateType + OriginId mysql.ID + AddReduce mysql.AddReduce + Num mysql.Num + Remark mysql.Str + BefNum mysql.Num + AftNum mysql.Num + diamondAccount *DiamondAccount `gorm:"-"` +} + +// 粉钻详情 +type DiamondPinkAccountDetail struct { + mysql.Entity + *domain.Model `gorm:"-"` + UserId mysql.ID + DiamondAccountId mysql.ID + OperateId mysql.ID + OperateType diamond_e.OperateType + OriginId mysql.ID + AddReduce mysql.AddReduce + Num mysql.Num + Remark mysql.Str + BefNum mysql.Num + AftNum mysql.Num + diamondAccount *DiamondAccount `gorm:"-"` +} + +//账号操作配置 +type DiamondOperateSet struct { + mysql.Entity + *domain.Model `gorm:"-"` + DiamondNum mysql.NumAll + FrequencyNum mysql.NumAll + FrequencyDay mysql.NumAll + DiamondMaxNum mysql.NumAll + AddReduce mysql.AddReduce + Type diamond_e.OperateType + Name mysql.Str + Status mysql.UserYesNo + DiamondType diamond_e.OperateType +} + +//通过userId获取帐号 +func GetDiamondAccountByUserId(model *domain.Model, userId mysql.ID) (*DiamondAccount, error) { + var diamondAccount DiamondAccount + if err := model.Db.WithContext(model).Where(&DiamondAccount{ + UserId: userId, + }).First(&diamondAccount).Error; err != nil { + return nil, myerr.WrapErr(err) + } + diamondAccount.Model = model + return &diamondAccount, nil +} + +//匹配条件扣费 +func (diamondAccount *DiamondAccount) ChangeDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) { + return diamondAccount.addDiamondAccountDetail(operateType, originId, diamondNum) +} + +//钻石操作记录, +func (diamondAccount *DiamondAccount) addDiamondAccountDetail(operateType diamond_e.OperateType, originId mysql.ID, diamondNum mysql.Num) (*DiamondAccountDetail, error) { + var diamondOperateSet DiamondOperateSet + var err error + if err = diamondAccount.Db.Where(&DiamondOperateSet{ + Type: operateType, + Status: mysql.USER, + DiamondType: mysql.DiamondYellow, + }).First(&diamondOperateSet).Error; err != nil { + return nil, myerr.WrapErr(err) + } + + //判断是增加,账号是否被冻结 + if diamondAccount.Status == diamond_e.Frozen && diamondOperateSet.AddReduce == mysql.REDUCE { + return nil, bizerr.DiamondAccountFrozen + } + + //无限,检查次数 + var count int64 + if diamondOperateSet.FrequencyDay == -1 { + if diamondOperateSet.FrequencyNum != -1 { + diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{ + UserId: diamondAccount.UserId, + OperateType: operateType, + }).Count(&count) + if count >= int64(diamondOperateSet.FrequencyNum) { + return nil, bizerr.DiamondFrequency + //return nil, myerr.NewSysError("钻石操作次数多大, userId:" + mysql.IdToStr(diamondAccount.UserId) + " diamondOperateSetId" + mysql.IdToStr(diamondOperateSet.ID)) + } + } + } else if diamondOperateSet.FrequencyDay == 1 { + beginTime, err := time.ParseInLocation("2006-01-02", time.Now().Format("2006-01-02"), time.Local) + if err != nil { + return nil, myerr.WrapErr(err) + } + //一天的次数 + diamondAccount.Db.Model(&DiamondAccountDetail{}).Where(&DiamondAccountDetail{ + UserId: diamondAccount.UserId, + OperateType: operateType, + }).Where("created_time >= ? ", beginTime).Count(&count) + if count >= int64(diamondOperateSet.FrequencyNum) { + return nil, bizerr.DiamondFrequency + } + //终极拦截,利用 + diamondAccount.SetCheckUpdateCondition(" EXISTS (SELECT * from (SELECT COUNT(1) as n from diamond_account_detail d where d.user_id = " + strconv.FormatUint(diamondAccount.UserId, 10) + " and d.operate_type = " + strconv.FormatUint(uint64(operateType), 10) + " and d.created_time >= from_unixtime(" + strconv.FormatInt(utils.GetZeroTime(time.Now()).Unix(), 10) + ")) t where t.n < " + strconv.Itoa(diamondOperateSet.FrequencyNum) + " )") + } + + //-1,代表值无效,由参数给与 + var upateDiamondNum mysql.Num + if diamondOperateSet.DiamondNum == -1 { + upateDiamondNum = diamondNum + } else { + upateDiamondNum = mysql.Num(diamondOperateSet.DiamondNum) + } + + var afterNum mysql.Num + if diamondOperateSet.AddReduce == mysql.ADD { + afterNum = diamondAccount.DiamondNum + upateDiamondNum + } else if diamondOperateSet.AddReduce == mysql.REDUCE { + if diamondAccount.DiamondNum < upateDiamondNum { + return nil, bizerr.DiamondNoEnough + } + afterNum = diamondAccount.DiamondNum - upateDiamondNum + } else { + return nil, myerr.NewSysError("AddReduce 值错误:" + mysql.TypeToString(mysql.Type(diamondOperateSet.AddReduce))) + } + + diamondAccountDetail := &DiamondAccountDetail{ + Model: diamondAccount.Model, + UserId: diamondAccount.UserId, + DiamondAccountId: diamondAccount.ID, + OperateId: diamondOperateSet.ID, + OperateType: diamondOperateSet.Type, + OriginId: originId, + AddReduce: diamondOperateSet.AddReduce, + Num: upateDiamondNum, + Remark: diamondOperateSet.Name, + BefNum: diamondAccount.DiamondNum, + AftNum: afterNum, + diamondAccount: diamondAccount, + } + return diamondAccountDetail, err +} + +// 此方法必须要在事务里面调用 +func ChangeDiamondAccountDetail(model *domain.Model, operateType diamond_e.OperateType, originId mysql.ID, userId mysql.ID, diamondNum mysql.Num) error { + diamondAccount, err := GetDiamondAccountByUserId(model, userId) + if err != nil { + model.Log.Errorf("ChangeDiamondAccountDetail operateType:%v, originId:%v, userId:%v, diamondNum:%v, err:%v", operateType, originId, userId, diamondNum, err) + return err + } + diamondAccountDetail, err := diamondAccount.ChangeDiamondAccountDetail(operateType, originId, diamondNum) + if err != nil { + model.Log.Errorf("ChangeDiamondAccountDetail operateType:%v, originId:%v, userId:%v, diamondNum:%v, err:%v", operateType, originId, userId, diamondNum, err) + return err + } + if err := diamondAccountDetail.PersistentNoInTransactional(); err != nil { + model.Log.Errorf("ChangeDiamondAccountDetail operateType:%v, originId:%v, userId:%v, diamondNum:%v, err:%v", operateType, originId, userId, diamondNum, err) + return err + } + return nil +} diff --git a/domain/model/diamond_m/repo.go b/domain/model/diamond_m/repo.go new file mode 100755 index 0000000000000000000000000000000000000000..7b48c1552015245e4cd901f46563f4ac861f1645 --- /dev/null +++ b/domain/model/diamond_m/repo.go @@ -0,0 +1,52 @@ +package diamond_m + +import ( + "git.hilo.cn/hilo-common/resource/mysql" + "gorm.io/gorm" + "hilo-user/domain/model" + "hilo-user/myerr" + "strconv" +) + +func (diamondAccountDetail *DiamondAccountDetail) PersistentNoInTransactional() error { + //fixme: 这里有点奇怪, diamondAccount持久化动作在diamondAccountDetail持久化之后,RowsAffected 就一定是0 + txDiamondAccount := diamondAccountDetail.Db.Model(diamondAccountDetail.diamondAccount) + if diamondAccountDetail.diamondAccount.CheckUpdateCondition() { + txDiamondAccount = txDiamondAccount.Where(diamondAccountDetail.diamondAccount.GetUpdateCondition()) + } + if diamondAccountDetail.AddReduce == mysql.ADD { + //增加 + txDiamondAccount.UpdateColumn("diamond_num", gorm.Expr("diamond_num + ?", diamondAccountDetail.Num)) + } else if diamondAccountDetail.AddReduce == mysql.REDUCE { + //减少,保证不能扣成负数 + txDiamondAccount.Where("diamond_num >= ?", diamondAccountDetail.Num).UpdateColumn("diamond_num", gorm.Expr("diamond_num - ?", diamondAccountDetail.Num)) + } else { + myerr.NewSysError("addReduce 枚举错误 value:" + mysql.TypeToString(mysql.Type(diamondAccountDetail.AddReduce))) + } + if err := txDiamondAccount.Error; err != nil { + return myerr.WrapErr(err) + } + if txDiamondAccount.RowsAffected == 0 { + diamondAccountDetail.Log.Errorf("gorm condition update.RowsAffected = 0,AddReduce:%v", diamondAccountDetail.AddReduce) + return myerr.NewWaring("gorm condition update.RowsAffected = 0") + } + + //持久化diamondAccountDetail + if err := model.Persistent(diamondAccountDetail.Db, diamondAccountDetail); err != nil { + return myerr.WrapErr(err) + } + //改变diamondAccount值 + if diamondAccountDetail.diamondAccount == nil { + return myerr.NewSysError("持久化错误, 模型:DiamondAccountDetail 中没有diamondAccount, DiamondAccountDetail.Id =" + strconv.Itoa(int(diamondAccountDetail.ID))) + } + + var newDiamondAccount DiamondAccount + if err := diamondAccountDetail.Db.First(&newDiamondAccount, diamondAccountDetail.diamondAccount.ID).Error; err != nil { + return myerr.WrapErr(err) + } + + if newDiamondAccount.DiamondNum < 0 { + return myerr.NewSysError("diamond_account表中,diamond_num 不能小于0, diamondAccount.id = " + strconv.Itoa(int(newDiamondAccount.ID))) + } + return nil +} diff --git a/mysql/3.9.0.sql b/mysql/3.9.0.sql new file mode 100644 index 0000000000000000000000000000000000000000..89e9f76fd0729b24d271ead86537bb30fd3f916c --- /dev/null +++ b/mysql/3.9.0.sql @@ -0,0 +1,15 @@ +CREATE TABLE `cp_relation` ( + `id` bigint unsigned AUTO_INCREMENT NOT NULL, + `user_id1` bigint NOT NULL COMMENT 'user_id1', + `user_id2` bigint NOT NULL COMMENT 'user_id2', + `created_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP, + `updated_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP, + PRIMARY KEY (`id`), + UNIQUE KEY `uid1_idx` (`user_id1`) USING BTREE, + UNIQUE KEY `uid2_idx` (`user_id2`) USING BTREE, + KEY `ctime_idx` (`created_time`) USING BTREE +) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='cp关系表'; + +INSERT INTO hilo.diamond_operate_set (diamond_num, frequency_num, frequency_day, diamond_max_num, add_reduce, `type`, name, status, diamond_type) +VALUES (-1, -1, -1, -1, 2, 94, 'cp邀请扣费', 1, 1), + (-1, -1, -1, -1, 1, 95, 'cp邀请退费', 1, 1); diff --git a/route/cp_r/cp_relation.go b/route/cp_r/cp_relation.go new file mode 100644 index 0000000000000000000000000000000000000000..cd796979028167e74a3dad69144eeddbc874933d --- /dev/null +++ b/route/cp_r/cp_relation.go @@ -0,0 +1,125 @@ +package cp_r + +import ( + "encoding/json" + "git.hilo.cn/hilo-common/_const/enum/diamond_e" + "git.hilo.cn/hilo-common/domain" + "git.hilo.cn/hilo-common/mycontext" + "git.hilo.cn/hilo-common/myerr/comerr" + "git.hilo.cn/hilo-common/rpc" + "git.hilo.cn/hilo-common/sdk/tencentyun" + "git.hilo.cn/hilo-common/txop/msg" + "github.com/gin-gonic/gin" + "hilo-user/_const/enum/cp_e" + "hilo-user/cv/cp_cv" + "hilo-user/domain/model/cp_m" + "hilo-user/domain/model/diamond_m" + "hilo-user/domain/model/user_m" + "hilo-user/req" + "hilo-user/resp" +) + +// @Tags cp关系 +// @Summary 检查用户是否绑定了cp +// @Param code query int true "用户code" +// @Success 200 {object} cp_cv.CheckCpRelationRes +// @Router /v2/cp/relation/check [get] +func CheckUserCpRelation(c *gin.Context) (*mycontext.MyContext, error) { + myCtx := mycontext.CreateMyContext(c.Keys) + userCode := c.Query("code") + + _, lang, err := req.GetUserIdLang(c, myCtx) + if err != nil { + return myCtx, err + } + + model := domain.CreateModelContext(myCtx) + user, err := user_m.GetUserByCode(model, userCode) + if err != nil { + return myCtx, err + } + + cp, err := cp_m.GetCp(model, user.ID) + if err != nil { + return myCtx, err + } + if cp.Id > 0 { + return myCtx, msg.GetErrByLanguage(model, 0, lang, comerr.InvalidParameter) + } + + resp.ResponseOk(c, cp_cv.CheckCpRelationRes{}) + return myCtx, nil +} + +// @Tags cp关系 +// @Summary 发送cp邀请 +// @Param code formData int true "用户code" +// @Success 200 +// @Router /v2/cp/relation/invite [post] +func InviteCpRelation(c *gin.Context) (*mycontext.MyContext, error) { + myCtx := mycontext.CreateMyContext(c.Keys) + userCode := c.Query("code") + + myUserId, lang, err := req.GetUserIdLang(c, myCtx) + if err != nil { + return myCtx, err + } + + model := domain.CreateModelContext(myCtx) + user, err := user_m.GetUser(model, myUserId) + if err != nil { + return myCtx, err + } + userInvite, err := user_m.GetUserByCode(model, userCode) + if err != nil { + return myCtx, err + } + + // 自己是否有cp了 + myCp, err := cp_m.GetCp(model, myUserId) + if err != nil { + return myCtx, err + } + if myCp.Id > 0 { + return myCtx, msg.GetErrByLanguage(model, 0, lang, comerr.InvalidParameter) + } + // 对方是否已经有cp了 + inviCp, err := cp_m.GetCp(model, userInvite.ID) + if err != nil { + return myCtx, err + } + if inviCp.Id > 0 { + return myCtx, msg.GetErrByLanguage(model, 0, lang, comerr.InvalidParameter) + } + err = model.Transaction(func(model *domain.Model) error { + // 扣费 + err = diamond_m.ChangeDiamondAccountDetail(model, diamond_e.CpInvite, userInvite.ID, myUserId, cp_e.CpRelationInviteDiamond) + if err != nil { + model.Log.Errorf("InviteCpRelation myUserId:%d, err:%v", myUserId, err) + return err + } + // 发送私信 + type CpInviteMessage struct { + Identifier string `json:"identifier"` + Msg string `json:"msg"` + } + data, _ := json.Marshal(CpInviteMessage{ + Identifier: "CpInviteMessage", + Msg: "Do you want to be CP with me?", + }) + if err := tencentyun.BatchSendCustomMsg(model, 1, user.ExternalId, []string{userInvite.ExternalId}, string(data), "cp邀请"); err != nil { + model.Log.Errorf("BatchSendCustomMsg fail:%v", err) + return err + } + return nil + }) + if err != nil { + model.Log.Errorf("InviteCpRelation myUserId:%d, err:%v", myUserId, err) + return myCtx, err + } + // socket 推送弹窗 + go rpc.SendCpInviteNotice(userInvite.ID, user.Code, user.Nick, user.Avatar, "Do you want to be CP with me?") + + resp.ResponseOk(c, cp_cv.CheckCpRelationRes{}) + return myCtx, nil +}