package api import ( "strings" "time" "yunyan/comm" "yunyan/lego/sys/log" "yunyan/pb" "yunyan/utils/license" "yunyan/utils/mac" ) // CreateAuthCodes 创建授权码/设备码(一次生产 = 一个批次)。 // // 序号采用「产品级全局游标」:每次生产原子地从 product.serialcursor 上分配一段连续区间 // [start, end],不再随批次重置,也不再依赖厂家维度的 probatch 计数器。 // 每次生产落一条 production_batch 台账(记录序号区间/MAC区间/数量), // 再把该批次的全部授权码写入 license_ 分表。 func (this *apiComp) CreateAuthCodes(session comm.IUserSession, req *pb.ApiCreateAuthCodesReq) (resp *pb.ApiCreateAuthCodesResp, errdata *pb.ErrorData) { var ( err error product *pb.DBProduct now = time.Now().Unix() pid = req.Productid ) // 校验品牌商存在 if _, err = this.module.model.factory(req.Brandid); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } // 渠道商必选:归属随批次写进每条授权码,用户绑定该设备后统计与结算都按它归属。 channel, errdata := this.resolveProductionChannel(req.Channelid, req.Brandid) if errdata != nil { return } if product, err = this.module.model.product(pid); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} return } // 每副产品需要生产的设备码份数(0或1视为单份) codeCount := product.Devicecodecount if codeCount == 0 { codeCount = 1 } isClassicBT := product.Devicetype == pb.DeviceType_Classic_bluetooth_Headset // 计算本次生产的授权码总数 var ( total int64 macaddresses []string ) switch req.Createtype { case 1: // 生产模式:数量 * 每副份数 total = int64(req.Number) * int64(codeCount) case 2: // 导入模式:按逗号拆分的 MAC 份数 macaddresses = strings.Split(req.Macaddresses, ",") total = int64(len(macaddresses)) default: errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "createtype 仅支持 1(生产) 或 2(导入)"} return } if total <= 0 { errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "生成数量必须大于0"} return } // 原子分配产品级序号区间 [start, end] end, err := this.module.model.allocSerialRange(pid, total) if err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} this.module.Error("allocSerialRange Fail!", log.Field{Key: "err", Value: err.Error()}) return } start := end - total + 1 models := make([]*pb.DBAuthCode, 0, total) if req.Createtype == 1 { for i := int64(0); i < total; i++ { serial := start + i var code string if code, err = license.GenerateV2(uint16(pid), uint32(serial)); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: err.Error()} return } device := &pb.DBAuthCode{ Code: code, Codetype: pb.Codetype_AUTHCODE, Brandid: req.Brandid, Channelid: channel.Id, Productid: pid, Number: serial, Createtime: now, } if isClassicBT { // 经典蓝牙耳机:生成设备码 + MAC device.Codetype = pb.Codetype_DEVICECODE device.Devicemac = mac.Generate(uint16(pid), uint32(serial)) } models = append(models, device) } } else { // 导入模式 for i := int64(0); i < total; i++ { serial := start + i var code string if code, err = license.GenerateV2(uint16(pid), uint32(serial)); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_SystemError, Message: err.Error()} return } models = append(models, &pb.DBAuthCode{ Code: code, Codetype: pb.Codetype_DEVICECODE, Brandid: req.Brandid, Channelid: channel.Id, Productid: pid, Number: serial, Createtime: now, Devicemac: strings.TrimSpace(macaddresses[i]), }) } } // 经典蓝牙时批次记录 MAC 区间,否则留空 var startmac, endmac string if isClassicBT { startmac = mac.Generate(uint16(pid), uint32(start)) endmac = mac.Generate(uint16(pid), uint32(end)) } // 先落批次台账。批次号 = 6 位可读短码(batchno 唯一索引兜底防碰撞),授权码用它关联批次。 // 优先用预览确认流程回传的 req.Batchno(保证二次确认展示的号 = 实际生产的号),未传则现生成。 batchno := strings.TrimSpace(req.Batchno) if batchno == "" { batchno, _ = comm.GeneratePublicCode() } batch := &pb.DBProductionBatch{ Ts: now, Brandid: req.Brandid, Channelid: channel.Id, Productid: pid, Serialstart: start, Serialend: end, Startmac: startmac, Endmac: endmac, Devicenum: uint32(total), Batchno: batchno, Remark: req.Remark, } if err = this.module.model.addproductionbatch(batch); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} this.module.Error("addproductionbatch Fail!", log.Field{Key: "err", Value: err.Error()}) return } // 回填批次号后批量写入分表 for _, row := range models { row.Batchno = batchno } if err = this.module.model.addfactoryDevics(pid, models); err != nil { errdata = &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()} this.module.Error("addauthcodes Fail!", log.Field{Key: "err", Value: err.Error()}) return } resp = &pb.ApiCreateAuthCodesResp{ Brandid: req.Brandid, Channelid: channel.Id, Productid: pid, Batchno: batchno, Startmac: startmac, Endmac: endmac, Serialstart: start, Serialend: end, Number: uint32(total), Devices: models, } return }