You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
188 lines
6.8 KiB
188 lines
6.8 KiB
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/sys/log"
|
|
"yunyan/lego/sys/postgres"
|
|
"yunyan/pb"
|
|
"yunyan/utils/devcode"
|
|
"yunyan/utils/mac"
|
|
)
|
|
|
|
// checkProductionFactory 校验批次要记的代工厂:0=不指定直接放行;非 0 时必须存在且未停用。
|
|
// 直接查表而不建 model 层:oem_factory 是后台(console 模块)维护的档案,api 这边只读一个存在性,
|
|
// 为它在 api 模块再复制一份 struct 反而会两处定义漂移。
|
|
func (this *apiComp) checkProductionFactory(id uint32) *pb.ErrorData {
|
|
if id == 0 {
|
|
return nil // 可选:存量流程没有工厂概念,不强制填
|
|
}
|
|
var n int64
|
|
if err := postgres.Table(comm.TableOemFactory).Where("id=? AND enabled=?", id, true).Count(&n).Error; err != nil {
|
|
return &pb.ErrorData{Code: pb.ErrorCode_DBError, Message: err.Error()}
|
|
}
|
|
if n == 0 {
|
|
return &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError, Message: "代工厂不存在或已停用,请到后台「工厂管理」确认"}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
// CreateAuthCodes 创建授权码/设备码(一次生产 = 一个批次)。
|
|
//
|
|
// 序号采用「产品级全局游标」:每次生产原子地从 product.serialcursor 上分配一段连续区间
|
|
// [start, end],不再随批次重置,也不再依赖厂家维度的 probatch 计数器。
|
|
// 每次生产落一条 production_batch 台账(记录序号区间/MAC区间/数量),
|
|
// 再把该批次的全部授权码写入 license_<productid十六进制> 分表。
|
|
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
|
|
}
|
|
// 代工厂可选(0=不指定):只落在批次台账上,不写进每条授权码——
|
|
// 换厂是批次级的事,授权码里再存一份只会在改厂时产生两处不一致。
|
|
if errdata = this.checkProductionFactory(req.Factoryid); 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
|
|
}
|
|
// 计算本次生产的设备总数
|
|
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
|
|
|
|
// ⚠️ 一条设备 = 一个 MAC,code 就是 MAC(2026-09-02 起,与 modules/console 同一口径)。
|
|
// 授权码字符串已彻底移除:绑定/禁用/重置/结算反查批次全走 MAC,
|
|
// MAC 前 2 字节就是 PID,产品/批次/渠道都能从它查回来,不需要第二套标识。
|
|
models := make([]*pb.DBAuthCode, 0, total)
|
|
for i := int64(0); i < total; i++ {
|
|
device := &pb.DBAuthCode{
|
|
Codetype: pb.Codetype_DEVICECODE,
|
|
Brandid: req.Brandid,
|
|
Channelid: channel.Id,
|
|
Productid: pid,
|
|
Number: start + i,
|
|
Createtime: now,
|
|
}
|
|
if req.Createtype == 1 {
|
|
// MAC 只是 (PID,序号) 的一种编码,与设备是不是经典蓝牙无关,所有产品一视同仁。
|
|
device.Devicemac = mac.Generate(uint16(pid), uint32(start+i))
|
|
} else { // 导入模式:外部已烧录好的 MAC
|
|
norm, ok := devcode.NormalizeMac(macaddresses[i])
|
|
if !ok {
|
|
errdata = &pb.ErrorData{Code: pb.ErrorCode_ReqParameterError,
|
|
Message: fmt.Sprintf("第 %d 个 MAC 格式不对: %s", i+1, macaddresses[i])}
|
|
return
|
|
}
|
|
device.Devicemac = norm
|
|
}
|
|
device.Code = device.Devicemac
|
|
models = append(models, device)
|
|
}
|
|
|
|
// MAC 区间只在【生产模式】下有意义:MAC 由 (PID, 序号) 算出,首尾即真实边界。
|
|
// 导入模式的 MAC 来自外部列表、彼此不连续,留空——存一对按序号算出来、库里根本不存在的
|
|
// 假地址,比空着更有害。(与 modules/console 同一口径。)
|
|
var startmac, endmac string
|
|
if req.Createtype == 1 {
|
|
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,
|
|
Factoryid: req.Factoryid,
|
|
}
|
|
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
|
|
}
|
|
|