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.
 
 
 
 
 
 

473 lines
16 KiB

package console
import (
"fmt"
"math"
"strings"
"time"
"yunyan/comm"
"yunyan/pb"
"yunyan/sys/tencentyun/cos"
"github.com/gin-gonic/gin"
)
// defaultFactoryContactMail 厂家联系邮箱缺省必含项(与 modules/api 一致)。
const defaultFactoryContactMail = "358030594@qq.com"
// 设备管理域写操作 handlers。逻辑从 modules/api 对应 handler 移植,去掉 RPCX session 与
// scope 白名单(console 仅超管),数据访问改为作用于传入的"选中应用"连接 sys。
// 覆盖老 console 前端调用的写接口:updateproduct/delproduct/(add|del)productversion/
// createfactorydevics/disablefactorydevics/revokefactorydevics,以及全局的 getcostoken。
// ---- 产品 / 版本 ----
func (this *serverComp) updateProduct(c *gin.Context, sys *appConn) {
var req pb.ApiUpdateProductReq
_ = c.ShouldBindJSON(&req)
if req.Product == nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "product 必填")
return
}
if _, err := dvProduct(sys, req.Product.Id); err != nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "产品不存在")
return
}
// 指定 versionid 时,从版本表同步版本/描述/升级包地址,前端无需重复传。
if req.Product.Versionid != 0 {
ver, verr := dvProductVersion(sys, req.Product.Versionid)
if verr != nil || ver == nil || ver.Productid != req.Product.Id {
writeErr(c, pb.ErrorCode_ReqParameterError, "versionid 无效")
return
}
req.Product.Version = ver.Version
req.Product.Updatedescription = ver.Description
req.Product.Updatepackageaddress = ver.Updatepackageaddress
}
if err := dvUpdateProduct(sys, req.Product); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshProducts() // 写后刷新产品缓存
writeOK(c, &pb.ApiUpdateProductResp{})
}
func (this *serverComp) delProduct(c *gin.Context, sys *appConn) {
var req pb.ApiDelProductReq
_ = c.ShouldBindJSON(&req)
if err := dvDelProduct(sys, req.Id); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshProducts() // 写后刷新产品缓存
writeOK(c, &pb.ApiDelProductResp{})
}
func (this *serverComp) addProductVersion(c *gin.Context, sys *appConn) {
var req pb.ApiAddProductVersionReq
_ = c.ShouldBindJSON(&req)
if req.Version == nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "version 必填")
return
}
if err := dvAddProductVersion(sys, req.Version); err != nil { // Insert 后回填 Version.Id
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
// Isuse: 把该版本设为产品当前版本并持久化。
// (modules/api 原 handler 这里只改内存对象、漏了落库,console 补上以让"启用"生效。)
if req.Isuse {
product, err := dvProduct(sys, req.Version.Productid)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
product.Version = req.Version.Version
product.Updatedescription = req.Version.Description
product.Updatepackageaddress = req.Version.Updatepackageaddress
product.Versionid = req.Version.Id
if err = dvUpdateProduct(sys, product); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
}
this.module.deviceCache.refreshProductVersions() // 写后刷新版本缓存
if req.Isuse {
this.module.deviceCache.refreshProducts() // Isuse 改了产品当前版本,连带刷新产品缓存
}
writeOK(c, &pb.ApiAddProductVersionResp{Version: req.Version})
}
func (this *serverComp) delProductVersion(c *gin.Context, sys *appConn) {
var req pb.ApiDelProductVersionReq
_ = c.ShouldBindJSON(&req)
if err := dvDelProductVersion(sys, req.Id); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshProductVersions() // 写后刷新版本缓存
writeOK(c, &pb.ApiDelProductVersionResp{})
}
func (this *serverComp) addProduct(c *gin.Context, sys *appConn) {
var req pb.ApiAddProductReq
_ = c.ShouldBindJSON(&req)
if req.Product == nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "product 必填")
return
}
factory, err := dvFactory(sys, req.Product.Factoryid)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, "厂家不存在: "+err.Error())
return
}
if err = dvAddProduct(sys, req.Product); err != nil { // Insert 后回填 Product.Id + 建 license 分表
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
// 把新产品 id 追加进厂家的产品列表。
if len(factory.Productlists) == 0 {
factory.Productlists = fmt.Sprintf("%d", req.Product.Id)
} else {
factory.Productlists = fmt.Sprintf("%s,%d", factory.Productlists, req.Product.Id)
}
if err = dvSaveFactory(sys, factory); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
// 注:modules/api 这里还会 RpcBroadcast 通知 home 热重载配置;console 不接 RPCX,
// 如需应用感知,走 /console/api/config/notify 发 NATS 事件。
this.module.deviceCache.refreshProducts() // 新增产品 + 改了厂家 Productlists,刷新两者缓存
this.module.deviceCache.refreshFactorys()
writeOK(c, &pb.ApiAddProductResp{Product: req.Product})
}
// ---- 厂家(写)----
func (this *serverComp) addFactory(c *gin.Context, sys *appConn) {
var req pb.ApiAddFactoryReq
_ = c.ShouldBindJSON(&req)
if req.Factory == nil {
writeErr(c, pb.ErrorCode_ReqParameterError, "factory 必填")
return
}
req.Factory.Contactmails = ensureDefaultContactMail(req.Factory.Contactmails)
if err := dvAddFactory(sys, req.Factory); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshFactorys() // 写后刷新厂家缓存
writeOK(c, &pb.ApiAddFactoryResp{Factory: req.Factory})
}
func (this *serverComp) updateFactory(c *gin.Context, sys *appConn) {
var req pb.ApiUpdateFactoryReq
_ = c.ShouldBindJSON(&req)
if req.Factory == nil || req.Factory.Id == 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "factory id is required")
return
}
old, err := dvFactory(sys, req.Factory.Id)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
// 只覆盖可编辑字段;probatch / productlists 等服务端维护字段不接受前端写入,
// 防止表单缺字段或携旧值把生产批次号/产品列表覆盖回旧值,导致后续生产撞批次撞码。
old.Factory = req.Factory.Factory
old.Factoryflag = req.Factory.Factoryflag
old.Description = req.Factory.Description
old.Contactmails = req.Factory.Contactmails
if err = dvSaveFactory(sys, old); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshFactorys() // 写后刷新厂家缓存
writeOK(c, &pb.ApiUpdateFactoryResp{Factory: old})
}
// ensureDefaultContactMail 确保厂家联系邮箱列表里含默认邮箱(去空、去重,默认置首)。
func ensureDefaultContactMail(mails string) string {
parts := strings.Split(mails, ",")
cleaned := make([]string, 0, len(parts)+1)
has := false
for _, p := range parts {
m := strings.TrimSpace(p)
if m == "" {
continue
}
if strings.EqualFold(m, defaultFactoryContactMail) {
has = true
}
cleaned = append(cleaned, m)
}
if !has {
cleaned = append([]string{defaultFactoryContactMail}, cleaned...)
}
return strings.Join(cleaned, ",")
}
// ---- 厂家设备(授权码批次)----
func (this *serverComp) createFactoryDevics(c *gin.Context, sys *appConn) {
var req pb.ApiCreateFactoryDevicsReq
_ = c.ShouldBindJSON(&req)
var (
startmac, endmac, code string
err error
models = make([]*pb.DBFactoryDevics, 0, req.Number)
)
modelFactory, err := dvFactory(sys, req.Factoryid)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
product, err := dvProduct(sys, req.Productid)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
modelFactory.Probatch++
// 每副产品需要生产的设备码份数(0或1视为单份)。
codeCount := product.Devicecodecount
if codeCount == 0 {
codeCount = 1
}
switch req.Createtype {
case 1: // 生产模式
total := req.Number * codeCount
if startmac, err = generateCustomMAC(uint16(req.Productid), byte(modelFactory.Probatch), uint32(1)); err != nil {
writeErr(c, pb.ErrorCode_SystemError, err.Error())
return
}
if endmac, err = generateCustomMAC(uint16(req.Productid), byte(modelFactory.Probatch), total*2+uint32(math.Ceil(float64(total)/float64(200)))); err != nil {
writeErr(c, pb.ErrorCode_SystemError, err.Error())
return
}
for i := 1; i <= int(total); i++ {
if code, err = comm.GenerateLicense(uint16(req.Productid), byte(modelFactory.Probatch), uint32(i)); err != nil {
writeErr(c, pb.ErrorCode_SystemError, err.Error())
return
}
device := &pb.DBFactoryDevics{
Code: code,
Codetype: pb.Codetype_AUTHCODE,
Productid: req.Productid,
Factoryid: req.Factoryid,
Probatch: modelFactory.Probatch,
Number: uint32(i),
Createtime: time.Now().Unix(),
}
if product.Devicetype == pb.DeviceType_Classic_bluetooth_Headset { // 经典蓝牙:码即 MAC
if endmac, err = generateCustomMAC(uint16(req.Productid), byte(modelFactory.Probatch), uint32(i)); err != nil {
writeErr(c, pb.ErrorCode_SystemError, err.Error())
return
}
device.Codetype = pb.Codetype_DEVICECODE
device.Devicemac = endmac
}
models = append(models, device)
}
case 2: // 导入模式
for i, v := range strings.Split(req.Macaddresses, ",") {
if code, err = comm.GenerateLicense(uint16(req.Productid), byte(modelFactory.Probatch), uint32(i+1)); err != nil {
writeErr(c, pb.ErrorCode_SystemError, err.Error())
return
}
models = append(models, &pb.DBFactoryDevics{
Code: code,
Codetype: pb.Codetype_DEVICECODE,
Productid: req.Productid,
Factoryid: req.Factoryid,
Probatch: modelFactory.Probatch,
Number: uint32(i + 1),
Createtime: time.Now().Unix(),
Devicemac: v,
})
}
}
if err = dvAddFactoryDevics(sys, product.Id, models); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
if err = dvSaveFactory(sys, modelFactory); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
this.module.deviceCache.refreshFactorys() // 批次号 Probatch 变更,刷新厂家缓存
if err = dvAddFactoryDeliveryNote(sys, &pb.DBFactoryDeliveryNote{
Ts: time.Now().Unix(),
Factoryid: req.Factoryid,
Productid: req.Productid,
Probatch: modelFactory.Probatch,
Startmac: startmac,
Endmac: endmac,
Devicetnum: uint32(len(models)),
}); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
writeOK(c, &pb.ApiCreateFactoryDevicsResp{
Factoryid: req.Factoryid,
Productid: req.Productid,
Startmac: startmac,
Endmac: endmac,
Probatch: modelFactory.Probatch,
Number: uint32(len(models)),
Devices: models,
})
}
func (this *serverComp) disableFactoryDevics(c *gin.Context, sys *appConn) {
var req pb.ApiDisableFactoryDevicsReq
_ = c.ShouldBindJSON(&req)
if req.Productid == 0 || len(req.Codes) == 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "productid/codes 不能为空")
return
}
// 只接受 0(正常) 或 -1(禁用)。
disabled := int32(0)
if req.Disabled != 0 {
disabled = -1
}
affected, err := dvSetFactoryDevicsDisabled(sys, req.Productid, req.Codes, disabled)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
writeOK(c, &pb.ApiDisableFactoryDevicsResp{Affected: affected})
}
// revokeFactoryDevics 撤销一个生产批次:校验出货单存在 → 批次内无已用设备码 →
// 删除该批次全部设备码 + 出货单。不回退 factory.probatch(已分发批次号不复用)。
func (this *serverComp) revokeFactoryDevics(c *gin.Context, sys *appConn) {
var req pb.ApiRevokeFactoryDevicsReq
_ = c.ShouldBindJSON(&req)
if req.Factoryid == 0 || req.Productid == 0 || req.Probatch == 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "factoryid/productid/probatch 不能为空")
return
}
note, err := dvFactoryDeliveryNote(sys, req.Factoryid, req.Probatch)
if err != nil || note == nil || note.Productid != req.Productid {
writeErr(c, pb.ErrorCode_DBError, "未找到指定生产批次")
return
}
used, err := dvCountUsedFactoryDevicsByBatch(sys, req.Productid, req.Probatch)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
if used > 0 {
writeErr(c, pb.ErrorCode_ReqParameterError, "该批次已有设备码被激活/绑定,无法撤销")
return
}
affected, err := dvDelFactoryDevicsByBatch(sys, req.Productid, req.Probatch)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
if err = dvDelFactoryDeliveryNote(sys, req.Factoryid, req.Productid, req.Probatch); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
writeOK(c, &pb.ApiRevokeFactoryDevicsResp{Deleted: uint32(affected)})
}
// ---- 设备码重置(单条 / 批量 / 按用户)----
// resetOneLicense 把一条 License 重置到初始未用状态(单条/批量共享):
// 校验 License 取 pid(旧码对不上则回退固定 45058)→ 若已绑定用户则删其 userdevice →
// 清 license 行(status/uid/devicemac/usedtime) → 若原为激活态则扣减 product_stat 激活数。
// 与 modules/api 的 resetOneLicense 同口径——console 旧版只清 license 行、漏了后两步,已统一修正。
func resetOneLicense(sys *appConn, license string) error {
pid, _, _, err := comm.ValidateLicense(license)
if err != nil {
pid = uint16(45058)
}
model, err := dvFactoryDevic(sys, uint32(pid), license)
if err != nil {
return err
}
wasActivated := model.Uid != ""
if wasActivated {
if err = dvDelUserDevice(sys, model.Uid); err != nil {
return err
}
}
model.Status = 0
model.Uid = ""
model.Devicemac = ""
model.Usedtime = 0
if err = dvUpdateFactoryDevic(sys, uint32(pid), model); err != nil {
return err
}
if wasActivated && model.Productid > 0 {
_ = dvDecrProductStatActivated(sys, model.Productid)
}
return nil
}
// batchResetLicenseStatus 批量重置 License,逐条统计成功/失败数(单条失败不阻断其余)。
func (this *serverComp) batchResetLicenseStatus(c *gin.Context, sys *appConn) {
var req pb.ApiBatchResetLicenseStatusReq
_ = c.ShouldBindJSON(&req)
resp := &pb.ApiBatchResetLicenseStatusResp{}
for _, license := range req.Licenses {
if err := resetOneLicense(sys, license); err != nil {
resp.FailCount++
} else {
resp.SuccessCount++
}
}
writeOK(c, resp)
}
// restFactoryDevics 按用户重置:删该用户的 userdevice,并把他在该产品下持有的设备码全部清回未用态。
func (this *serverComp) restFactoryDevics(c *gin.Context, sys *appConn) {
var req pb.ApiRestUserDevicsReq
_ = c.ShouldBindJSON(&req)
if err := dvDelUserDevice(sys, req.Uid); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
models, err := dvFactoryDevicForUid(sys, req.Productid, req.Uid)
if err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
for _, m := range models {
m.Status = 0
m.Uid = ""
m.Devicemac = ""
}
if err = dvUpdateFactoryDevics(sys, req.Productid, models); err != nil {
writeErr(c, pb.ErrorCode_DBError, err.Error())
return
}
writeOK(c, &pb.ApiRestUserDevicsResp{})
}
// ---- COS 上传凭证(全局,与选中应用无关)----
func (this *serverComp) getCosToken(c *gin.Context) {
result, err := cos.Sts()
if err != nil {
writeErr(c, pb.ErrorCode_SystemError, "获取上传凭证失败: "+err.Error())
return
}
config := cos.GetConfig()
writeOK(c, &pb.ApiGetCosTokenResp{
Bucket: config.BucketName,
Region: config.Region,
BucketUrl: config.BucketURL,
TmpSecretId: result.Credentials.TmpSecretID,
TmpSecretKey: result.Credentials.TmpSecretKey,
SessionToken: result.Credentials.SessionToken,
Expiration: result.Expiration,
Domain: config.DomainName,
})
}