package user import ( "yunyan/comm" "yunyan/lego/sys/log" "yunyan/pb" "strings" "time" ) // @Summary 扫激活码领取奖励 // @Description 新设计:激活码 = 厂家公码。每个用户一生仅可领取 1 次(DBUser.Isactivatecode 判重) // @Tags User // @Accept json // @Produce json // @Security BearerAuth // @Param user body pb.UserBindAuthCodeReq true "扫激活码请求" // @Success 200 {object} comm.HttpResult{data=pb.UserBindAuthCodeResp} "成功返回" // @Router /api/home/user_bindauthcode [post] func (this *apiComp) BindAuthCode(session comm.IUserSession, req *pb.UserBindAuthCodeReq) (resp *pb.UserBindAuthCodeResp, errdata *pb.ErrorData) { var ( user *pb.DBUser code *pb.DBFactoryPublicCode err error now = time.Now().Unix() codeStr = strings.ToUpper(strings.TrimSpace(req.Code)) affected int64 ) if codeStr == "" { errdata = &pb.ErrorData{ Code: pb.ErrorCode_ReqParameterError, Message: "激活码不能为空", } return } if user, err = this.module.model.findforid(session.GetUserId()); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_UserSessionNobeing, Message: err.Error(), } return } // 1) 快速判重:user.Isactivatecode 为 true 表示已经领过激活码奖励 if user.Isactivatecode { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "您已领取过激活码奖励,不可重复领取", } return } // 2) 校验公码 if code, err = this.module.model.getFactoryPublicCode(codeStr); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeIllegal, Message: "激活码无效", } return } if code.Status != 0 { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "激活码已停用", } return } if code.Expiretime > 0 && code.Expiretime < now { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "激活码已过期", } return } if code.Maxuses > 0 && code.Useduses >= code.Maxuses { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "激活码已达核销上限", } return } // 3) 原子置位 user.isactivatecode → 防同账号并发扫码兜底,输的请求 affected=0 if affected, err = this.module.model.tryMarkActivateCode(user.Uid); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if affected == 0 { errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "您已领取过激活码奖励,不可重复领取", } return } user.Isactivatecode = true // 4) 原子扣减公码名额(CAS 防超发);失败需回滚 user.isactivatecode 以便用户换码重试 if affected, err = this.module.model.incrFactoryPublicCodeUsed(codeStr); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if affected == 0 { // 公码已达上限:回滚 user.isactivatecode,让用户换码重试 if rbAff, rbErr := this.module.model.rollbackActivateCode(user.Uid); rbErr != nil || rbAff == 0 { this.module.Error("BindAuthCode rollback isactivatecode fail", log.Field{Key: "uid", Value: user.Uid}, log.Field{Key: "rbAff", Value: rbAff}) } errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "激活码已达核销上限", } return } // 5) 计算奖励:VIP 按天累加到自购到期时间(公码是发给**账号**的,归属用户,与设备无关); // 公码上配的翻译/会议分钟数(旧字段)按当前换算系数折成算力发到用户余额。 addVipDay := int64(code.Bindrewardvpitime) addTradeSec := int64(code.Bindrewardtranslate) * 60 addMeetSec := int64(code.Bindrewardmeeting) * 60 addCompute := comm.LegacyBucketsToCompute(comm.LoadComputeRates(), addTradeSec, addMeetSec, 0) if addVipDay > 0 { addVipSec := addVipDay * 24 * 60 * 60 if user.Vipexptime == 0 || user.Vipexptime < now { user.Vipexptime = now + addVipSec } else { user.Vipexptime += addVipSec } } user.Computebalance += addCompute // 6) 保存用户余额(isactivatecode 已经在步骤 3 提前落库,这里 Save 会覆盖一并写回,是幂等的) if err = this.module.model.save(user); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if addVipDay > 0 || addCompute > 0 { _ = this.module.model.addIntegralLog(&pb.DBUserUseLog{ Uid: user.Uid, Logtype: pb.UserLogType_ActivityReward, Ts: now, Addvipday: addVipDay, Compute: addCompute, Extra: code.Code, }) } // 埋点:二维码(公码)激活成功。只统计到 应用×区域(ProductId 留 0,不落地到产品),与用户口径一致。 if this.module.analyze != nil { this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventQRCodeActive, Uid: user.Uid}) } this.module.Debug("BindAuthCode", log.Field{Key: "uid", Value: user.Uid}, log.Field{Key: "code", Value: code.Code}, log.Field{Key: "factoryid", Value: code.Brandid}) resp = &pb.UserBindAuthCodeResp{ Isgiveaway: addVipDay > 0 || addTradeSec > 0 || addMeetSec > 0, Viplv: user.Viplv, Vipexptime: user.Vipexptime, Tradeintegral: user.Tradeintegral, Tradetotalintegral: user.Tradetotalintegral, Meetintegral: user.Meetintegral, Meettotalintegral: user.Meettotalintegral, Addvipday: addVipDay, Addtradesecond: addTradeSec, Addmeetsecond: addMeetSec, Factoryid: code.Brandid, Productid: code.Productid, } return }