package user import ( "encoding/json" "fmt" "strconv" "strings" "time" "yunyan/comm" "yunyan/lego/sys/log" "yunyan/pb" "yunyan/utils/license" ) // @Summary 绑定耳机设备 // @Description 绑定耳机设备 // @Tags User // @Accept json // @Produce json // @Security BearerAuth // @Param user body pb.UserBindDeviceReq true "发送绑定请求" // @Success 200 {object} comm.HttpResult{data=pb.UserBindDeviceResp} "成功返回" // @Router /api/home/user_binddevice [post] func (this *apiComp) BindDevice(session comm.IUserSession, req *pb.UserBindDeviceReq) (resp *pb.UserBindDeviceResp, errdata *pb.ErrorData) { var ( user *pb.DBUser product *pb.DBProduct divices []*pb.DBUserDivice model *pb.DBAuthCode codestate int32 divice *pb.DBUserDivice pid uint16 Isgiveaway bool err error ) if user, err = this.module.model.findforid(session.GetUserId()); err != nil { //未找到授权码 errdata = &pb.ErrorData{ Code: pb.ErrorCode_UserSessionNobeing, Message: err.Error(), } return } if product, err = this.module.cache.GetProduct(req.Pid); err != nil { //未找到授权码 errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: err.Error(), } return } if divices, err = this.module.model.getDevices(session.GetUserId()); err != nil { //未找到授权码 errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: err.Error(), } return } for _, v := range divices { if v.Devicename == req.Devicename && v.Devicemac == req.Devicemac && v.License == req.Code { // errdata = &pb.ErrorData{ // Code: pb.ErrorCode_DeviceBinded, // Message: "设备已绑定", // } divice = v resp = &pb.UserBindDeviceResp{ Device: divice, } return } } if product.Devicetype == pb.DeviceType_Classic_bluetooth_Headset { if model, err = this.module.model.getFactoryDeviceformac(product.Id, req.Devicemac); err != nil { //设备已被注册 errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: err.Error(), } return } } else { pid, err = license.ProductID(req.Code) if err != nil { //老得一批License 由于数据和新版对因不上 会产生错误 // errdata = &pb.ErrorData{ // Code: pb.ErrorCode_AuthorizeIllegal, // Message: err.Error(), // } err = nil pid = uint16(45058) // return } if model, err = this.module.model.getFactoryDevice(uint32(pid), req.Code); err != nil { //设备已被注册 errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: err.Error(), } return } } if model.Disabled != 0 { //设备码已被后台禁用 直接拒绝绑定 errdata = &pb.ErrorData{ Code: pb.ErrorCode_AuthorizeNoCanUse, Message: "设备码已被禁用", } return } codestate = model.Status if model.Status != 0 && model.Uid != session.GetUserId() { //授权码已被使用 自动解绑 if divices, err = this.module.model.getDevices(model.Uid); err == nil { for _, v := range divices { if v.License == model.Code { this.module.model.delDevice(v.Id) // 删除设备 // 接管即原用户被动解绑:按原设备自身的产品/渠道冲正 -1, // 否则同一台设备每换一次账号就多计一次绑定。 // 不带 Uid:原用户并未操作,不应被计入当日活跃用户。 if this.module.analyze != nil { this.module.analyze.Report(&comm.StatEvent{ Type: comm.StatEventDeviceBind, ProductId: uint32(v.Productid), ChannelId: v.Channelid, Count: -1, }) } } } } // errdata = &pb.ErrorData{ // Code: pb.ErrorCode_AuthorizeNoCanUse, // Message: "授权码已被使用", // } // return } // 移动渠道产品生成 CMEI: TUI(8位) + batch(2位) + serial(5位) = 15位 var cmei string if product.Channel == "mobile" { var tui string if product.Channelextra != "" { extra := struct { Tui string `json:"tui"` }{} if jerr := json.Unmarshal([]byte(product.Channelextra), &extra); jerr == nil { tui = extra.Tui } } macStr := strings.ReplaceAll(req.Devicemac, ":", "") if len(tui) == 8 && len(macStr) == 12 { batch, _ := strconv.ParseUint(macStr[4:6], 16, 8) serial, _ := strconv.ParseUint(macStr[8:12], 16, 16) cmei = fmt.Sprintf("%s%02d%05d", tui, batch%100, serial%100000) } } divice = &pb.DBUserDivice{ Uid: session.GetUserId(), Productid: product.Id, // 渠道商归属来自授权码(生产时定下),绑定即固化到用户设备上, // 后续该用户的充值/消耗埋点都按它归到对应渠道商名下(统计与月结算的依据)。 Channelid: model.Channelid, Devicetype: product.Devicetype, Devicename: req.Devicename, Devicemac: req.Devicemac, License: req.Code, Cmei: cmei, Version: req.Version, } model.Status = 1 //已绑定 model.Uid = session.GetUserId() model.Devicemac = req.Devicemac // 首次激活(此前 status==0)记录激活时间:供按统计日聚合设备激活/绑定数。 // 历史设备漏记 usedtime 是失误,从此补上;仅在 0→1 首激时写,换绑/接管不覆盖原始激活日。 if codestate == 0 { model.Usedtime = time.Now().Unix() } if err = this.module.model.saveFactoryDevice(model.Productid, model); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } if err = this.module.model.addDevice(divice); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } // 记录最后绑定的产品ID与渠道商:后续充值/消耗埋点按它们归属统计与结算 user.Lastbindproductid = product.Id user.Lastbindchannelid = model.Channelid userSaved := false // 设备激活 + 绑定埋点。 // 激活是「设备一生一次」的事件:只在 codestate==0 首激时记,换绑/接管不再计—— // 与上面 model.Usedtime 只在 0→1 时写保持同一口径,否则激活数被换绑次数放大。 // 绑定是净增流量:这里 +1,解绑/被接管处 -1(见 api_unbinddevice.go 与上面自动解绑分支)。 if this.module.analyze != nil { if codestate == 0 { this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventDeviceActive, Uid: session.GetUserId(), ProductId: uint32(product.Id), ChannelId: model.Channelid}) } this.module.analyze.Report(&comm.StatEvent{Type: comm.StatEventDeviceBind, Uid: session.GetUserId(), ProductId: uint32(product.Id), ChannelId: model.Channelid}) } this.module.Debug("BindDevice", log.Field{Key: "Isgiveaway", Value: user.Isgiveaway}, log.Field{Key: "Iscanbindagain", Value: product.Iscanbindagain}, log.Field{Key: "codestate", Value: codestate}) if !user.Isgiveaway || codestate == 0 { Isgiveaway = true user.Isgiveaway = true var addVipDay, addTrade, addMeet int64 if product.Bindrewardvpitime > 0 { addVipDay = 90 if user.Vipexptime == 0 || user.Vipexptime < time.Now().Unix() { //重置 user.Vipexptime = time.Now().Unix() + 90*24*60*60 } else { //累加 user.Vipexptime = user.Vipexptime + 90*24*60*60 } } // translate/meeting 配置为分钟,积分按秒存储 if product.Bindrewardtranslate > 0 { addTrade = int64(product.Bindrewardtranslate) * 60 user.Tradeintegral += addTrade user.Tradetotalintegral += addTrade } if product.Bindrewardmeeting > 0 { addMeet = int64(product.Bindrewardmeeting) * 60 user.Meetintegral += addMeet user.Meettotalintegral += addMeet } err = this.module.model.save(user) if err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } userSaved = true // 记录绑定设备奖励流水 if addVipDay > 0 || addTrade > 0 || addMeet > 0 { _ = this.module.model.addIntegralLog(&pb.DBUserUseLog{ Uid: session.GetUserId(), Logtype: pb.UserLogType_ActivityReward, Ts: time.Now().Unix(), Addvipday: addVipDay, Addtradesecond: addTrade, Addmeetsecond: addMeet, Extra: req.Code, // 绑定时使用的设备/授权码 }) // 绑定赠送资源埋点 if this.module.analyze != nil { this.module.analyze.Report(&comm.StatEvent{ Type: comm.StatEventResourceGrant, ProductId: product.Id, ChannelId: model.Channelid, Uid: session.GetUserId(), GrantVipDay: addVipDay, GrantTradeSecond: addTrade, GrantMeetSecond: addMeet, }) } } } // 首绑送资源分支未触发时,仍需持久化 lastbindproductid / lastbindchannelid if !userSaved { if err = this.module.model.save(user); err != nil { errdata = &pb.ErrorData{ Code: pb.ErrorCode_DBError, Message: err.Error(), } return } } resp = &pb.UserBindDeviceResp{ Device: divice, Isgiveaway: Isgiveaway, Viplv: user.Viplv, Vipexptime: user.Vipexptime, Tradeintegral: user.Tradeintegral, Tradetotalintegral: user.Tradetotalintegral, Meetintegral: user.Meetintegral, Meettotalintegral: user.Meettotalintegral, } return }