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.
1268 lines
44 KiB
1268 lines
44 KiB
package api
|
|
|
|
import (
|
|
"fmt"
|
|
"strconv"
|
|
"strings"
|
|
"time"
|
|
|
|
"yunyan/comm"
|
|
"yunyan/lego/core"
|
|
"yunyan/lego/core/cbase"
|
|
"yunyan/lego/sys/mysql"
|
|
"yunyan/lego/sys/postgres"
|
|
"yunyan/pb"
|
|
|
|
"gorm.io/gorm"
|
|
)
|
|
|
|
// 代理模型
|
|
type modelComp struct {
|
|
cbase.ModuleCompBase
|
|
module *API
|
|
}
|
|
|
|
// 组件初始化接口
|
|
func (this *modelComp) Init(service core.IService, module core.IModule, comp core.IModuleComp, opt core.IModuleOptions) (err error) {
|
|
this.ModuleCompBase.Init(service, module, comp, opt)
|
|
this.module = module.(*API)
|
|
if err = mysql.CreateTable(comm.TableAdmin, &pb.DBAdminUser{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableUserUseLog, &pb.DBUserUseLog{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableAdminResLog, &pb.DBAdminResourceLog{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableAppConfig, &pb.DBAppConfigItem{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableAppConfig, &pb.DBAppConfigItem{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = postgres.CreateTable(comm.TableGlobalConfig, &pb.DBGlobalConfigItem{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableAgent, &pb.DBAgent{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
// MCP 已并入第三方服务(svc_config 的 MCP 服务类型),不再建/读独立 mcp 表;数据由 console 迁移与建表。
|
|
if err = mysql.CreateTable(comm.TableEchomeetRecord, &pb.DBEchoMeetRecord{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableEchomeetTemplate, &pb.DBEchoMeetTemplate{}); err != nil {
|
|
this.module.Errorln(err)
|
|
} else {
|
|
mysql.Exec(fmt.Sprintf("ALTER TABLE %s AUTO_INCREMENT = %d", comm.TableEchomeetTemplate, 10000))
|
|
}
|
|
if err = postgres.CreateTable(comm.TableEchomeetTemplate, &pb.DBEchoMeetTemplate{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableWakeupVoice, &pb.DBWakeupVoice{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
|
|
if err = postgres.CreateTable(comm.TableSolutionProvider, &pb.DBSolutionProvider{}); err != nil {
|
|
this.module.Errorln(err)
|
|
} else {
|
|
//方案商 id 从 0xAB01 起。用 Floor 而非 Start:表里已经有 1、2、3… 这些历史行时
|
|
//Start 会直接跳过,新建的方案商仍然接着 4 发号;Floor 保留历史行、把序列抬到 0xAB01。
|
|
postgres.AutoIncrementFloor(comm.TableSolutionProvider, "id", 0xAB01)
|
|
}
|
|
if err = postgres.CreateTable(comm.TableBrand, &pb.DBBrand{}); err != nil {
|
|
this.module.Errorln(err)
|
|
} else {
|
|
//设置brand表的主键从0xA001开始(仅空表生效,跨 MySQL/Postgres 通用)
|
|
postgres.AutoIncrementStart(comm.TableBrand, "id", 0xA001)
|
|
}
|
|
//createtime 是后加字段:CreateTable 对已存在的表跳过 AutoMigrate,故显式补列(同 console 的 ensureDeviceTables)
|
|
if res := postgres.Exec("ALTER TABLE " + comm.TableBrand + " ADD COLUMN IF NOT EXISTS createtime bigint DEFAULT 0"); res.Error != nil {
|
|
this.module.Errorln(res.Error)
|
|
}
|
|
if err = postgres.CreateTable(comm.TableChannel, &pb.DBChannel{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = postgres.CreateTable(comm.TableProductionBatch, &pb.DBProductionBatch{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
// factoryid 后加字段(代工厂 oem_factory.id):CreateTable 对已存在的表跳过 AutoMigrate,显式补列。
|
|
if res := postgres.Exec("ALTER TABLE " + comm.TableProductionBatch + " ADD COLUMN IF NOT EXISTS factoryid bigint DEFAULT 0"); res.Error != nil {
|
|
this.module.Errorln(res.Error)
|
|
}
|
|
if err = postgres.CreateTable(comm.TableProduct, &pb.DBProduct{}); err != nil {
|
|
this.module.Errorln(err)
|
|
} else {
|
|
//设置product表的主键从0xB001开始(仅空表生效,跨 MySQL/Postgres 通用)
|
|
postgres.AutoIncrementStart(comm.TableProduct, "id", 0xB001)
|
|
}
|
|
//providerid/createtime/description 是后加字段:CreateTable 对已存在的表跳过 AutoMigrate,故显式补列
|
|
//(与 console 的 ensureDeviceTables 同口径;两边指向同一设备库时重复执行无副作用)。
|
|
for _, ddl := range []string{
|
|
"ALTER TABLE " + comm.TableProduct + " ADD COLUMN IF NOT EXISTS providerid bigint DEFAULT 0",
|
|
"ALTER TABLE " + comm.TableProduct + " ADD COLUMN IF NOT EXISTS createtime bigint DEFAULT 0",
|
|
"ALTER TABLE " + comm.TableProduct + " ADD COLUMN IF NOT EXISTS description text DEFAULT ''",
|
|
// oemfactoryid = 这款产品默认由哪家代工厂生产(oem_factory.id)。
|
|
// ⚠️ 不叫 factoryid:DBProduct 的 factoryid 是老客户端兼容字段(gorm:"-",值同 brandid),同名会打架。
|
|
"ALTER TABLE " + comm.TableProduct + " ADD COLUMN IF NOT EXISTS oemfactoryid bigint DEFAULT 0",
|
|
"CREATE INDEX IF NOT EXISTS idx_product_providerid ON " + comm.TableProduct + " (providerid)",
|
|
// devicetype 已于 2026-09-08 废弃并删列(同 console 的 ensureDeviceTables):
|
|
// 服务端无逻辑读它,后台表单已去掉。pb 里的 Devicetype 改成了 gorm:"-" 的老客户端兼容
|
|
// 字段(恒为 0,只为老 App 包能解析 user_getappconfig),新库不会建这一列。
|
|
// ⚠️ 不可逆:执行后存量列里的历史取值即消失。该值不参与任何判断,故不做备份。
|
|
"ALTER TABLE " + comm.TableProduct + " DROP COLUMN IF EXISTS devicetype",
|
|
// scanuuid 同上(2026-09-08 废弃):设备校验与绑定奖励都已改成按 MAC 判定,
|
|
// pb 里的 Scanuuid 是 gorm:"-" 的老客户端兼容字段,新库不会建这一列。
|
|
// ⚠️ 不可逆:执行后存量列里的历史取值即消失。
|
|
"ALTER TABLE " + comm.TableProduct + " DROP COLUMN IF EXISTS scanuuid",
|
|
} {
|
|
if res := postgres.Exec(ddl); res.Error != nil {
|
|
this.module.Errorln(res.Error)
|
|
}
|
|
}
|
|
if err = postgres.CreateTable(comm.TableProductVersion, &pb.DBProductVersion{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableChannelApp, &pb.DBChannelApp{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
// 不建裸 license 表:授权码只住按产品切的 license_<pid十六进制> 分表(见下方按产品建表的循环),
|
|
// 不带后缀的那张从来没有代码读写,建出来只是每个环境多一张恒空表。
|
|
if err = postgres.CreateTable(comm.TableFactoryPublicCode, &pb.DBFactoryPublicCode{}); err != nil {
|
|
this.module.Errorln(err)
|
|
return err
|
|
}
|
|
if err = mysql.CreateTable(comm.TableGoods, &pb.DBGoods{}); err != nil {
|
|
this.module.Errorln(err)
|
|
return err
|
|
}
|
|
if err = postgres.CreateTable(comm.TableWakeupVoice, &pb.DBWakeupVoice{}); err != nil {
|
|
this.module.Errorln(err)
|
|
return err
|
|
} else {
|
|
//设置wakeupvoice表的主键从0xD001开始(仅空表生效,跨 MySQL/Postgres 通用)
|
|
postgres.AutoIncrementStart(comm.TableWakeupVoice, "id", 0xD001)
|
|
}
|
|
if err = mysql.CreateTable(comm.TableProductStat, &pb.DBProductStat{}); err != nil {
|
|
this.module.Errorln(err)
|
|
}
|
|
model := &pb.DBAdminUser{
|
|
Account: this.module.options.AdninAccount,
|
|
Password: this.module.options.AdninPassword,
|
|
Identity: pb.Identity_Admin,
|
|
}
|
|
// 执行分页查询
|
|
mysql.Insert(comm.TableAdmin, model)
|
|
return
|
|
}
|
|
func (this *modelComp) Start() (err error) {
|
|
if err = this.ModuleCompBase.Start(); err != nil {
|
|
return err
|
|
}
|
|
var products []*pb.DBProduct
|
|
if err = postgres.Find(comm.TableProduct, &products, ""); err != nil && err != mysql.ErrNoDocuments {
|
|
this.module.Errorln(err)
|
|
return err
|
|
}
|
|
// 设备表已合成全局一张 device_mac(见 console/registry.go 的 migrateLicenseToDeviceMac),
|
|
// 不再按产品建分表。这里保留 products 查询——下面的统计初始化还要用。
|
|
_ = products
|
|
// product_stat 为空(首次部署 / 从未点过重建)时做一次初始扫描,
|
|
// 避免代理仪表盘在首次重建前激活数/绑定用户数全部显示 0。
|
|
var statCount int64
|
|
if cerr := mysql.Table(comm.TableProductStat).Count(&statCount).Error; cerr != nil {
|
|
this.module.Errorln(cerr)
|
|
} else if statCount == 0 {
|
|
if n, rerr := this.rebuildProductStats(); rerr != nil {
|
|
this.module.Errorln(rerr)
|
|
} else {
|
|
this.module.Infof("product_stat 初始扫描完成, rows=%d", n)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// 寻找账号
|
|
func (this *modelComp) findforaccount(account string) (user *pb.DBAdminUser, err error) {
|
|
user = &pb.DBAdminUser{}
|
|
err = mysql.FindOne(comm.TableAdmin, user, "account=?", account)
|
|
return
|
|
}
|
|
|
|
// 添加管理员账号
|
|
func (this *modelComp) addAdminUser(user *pb.DBAdminUser) (err error) {
|
|
err = mysql.Insert(comm.TableAdmin, user)
|
|
return
|
|
}
|
|
|
|
// 删除管理员账号
|
|
func (this *modelComp) delAdminUser(account string) (err error) {
|
|
err = mysql.Delete(comm.TableAdmin, "account=?", account)
|
|
return
|
|
}
|
|
|
|
// 更新管理员账号
|
|
func (this *modelComp) updateAdminUser(user *pb.DBAdminUser) (err error) {
|
|
err = mysql.Save(comm.TableAdmin, user)
|
|
return
|
|
}
|
|
|
|
// 查询管理员账号列表
|
|
func (this *modelComp) getAdminUsers() (users []*pb.DBAdminUser, err error) {
|
|
err = mysql.Find(comm.TableAdmin, &users, "")
|
|
return
|
|
}
|
|
|
|
// adjustAdminBalance 用 SQL 表达式 (col = col + ?) 原子调整指定账号的资源点余额。
|
|
// 仅写入 delta != 0 的列,避免 Save 覆盖其它字段,也避免并发派发互相覆盖。
|
|
// 返回 rowsAffected 用于校验账号是否存在。delta 可正(增加/赠送) 可负(扣减/派发)。
|
|
func (this *modelComp) adjustAdminBalance(account string, dVipday, dAichat, dTradeSec, dMeetSec int64) (rowsAffected int64, err error) {
|
|
updates := map[string]any{}
|
|
if dVipday != 0 {
|
|
updates["vipday_balance"] = gorm.Expr("vipday_balance + ?", dVipday)
|
|
}
|
|
if dAichat != 0 {
|
|
updates["aichatintegral_balance"] = gorm.Expr("aichatintegral_balance + ?", dAichat)
|
|
}
|
|
if dTradeSec != 0 {
|
|
updates["tradesecond_balance"] = gorm.Expr("tradesecond_balance + ?", dTradeSec)
|
|
}
|
|
if dMeetSec != 0 {
|
|
updates["meetsecond_balance"] = gorm.Expr("meetsecond_balance + ?", dMeetSec)
|
|
}
|
|
if len(updates) == 0 {
|
|
return
|
|
}
|
|
tx := mysql.Table(comm.TableAdmin).Where("account=?", account).Updates(updates)
|
|
rowsAffected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|
|
// 寻找用户
|
|
func (this *modelComp) finduser(uid string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "uid=?", uid)
|
|
return
|
|
}
|
|
|
|
// 寻找用户 手机号
|
|
func (this *modelComp) finduserbyphone(phone string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "phone=?", phone)
|
|
return
|
|
}
|
|
|
|
// 寻找用户 邮箱
|
|
func (this *modelComp) finduserbyemail(mail string) (user *pb.DBUser, err error) {
|
|
user = &pb.DBUser{}
|
|
err = mysql.FindOne(comm.TableUser, user, "mail=?", mail)
|
|
return
|
|
}
|
|
|
|
// 寻找用户
|
|
func (this *modelComp) finduserdevice(uid string) (models []*pb.DBUserDivice, err error) {
|
|
models = make([]*pb.DBUserDivice, 0)
|
|
err = mysql.Find(comm.TableUserdevice, &models, "uid=?", uid)
|
|
return
|
|
}
|
|
|
|
// 寻找用户
|
|
func (this *modelComp) deluserdevice(uid string) (err error) {
|
|
err = mysql.Delete(comm.TableUserdevice, "uid=?", uid)
|
|
return
|
|
}
|
|
|
|
// 更新用户信息
|
|
func (this *modelComp) updateuser(user *pb.DBUser) (err error) {
|
|
err = mysql.Save(comm.TableUser, user)
|
|
return
|
|
}
|
|
|
|
// 配置项
|
|
func (this *modelComp) config() (config []*pb.DBAppConfigItem, err error) {
|
|
config = make([]*pb.DBAppConfigItem, 0)
|
|
err = mysql.Find(comm.TableAppConfig, &config, "")
|
|
return
|
|
}
|
|
|
|
// 添加配置
|
|
func (this *modelComp) addconfig(config ...*pb.DBAppConfigItem) (err error) {
|
|
err = mysql.Insert(comm.TableAppConfig, config)
|
|
return
|
|
}
|
|
|
|
// 更新配置
|
|
func (this *modelComp) updateconfig(config ...*pb.DBAppConfigItem) (err error) {
|
|
err = mysql.Save(comm.TableAppConfig, config)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) delconfig(id uint64) (err error) {
|
|
err = mysql.Delete(comm.TableAppConfig, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 全局第三方服务配置
|
|
func (this *modelComp) globalconfig() (config []*pb.DBGlobalConfigItem, err error) {
|
|
config = make([]*pb.DBGlobalConfigItem, 0)
|
|
err = postgres.Find(comm.TableGlobalConfig, &config, "")
|
|
return
|
|
}
|
|
func (this *modelComp) globalconfigbyregion(region pb.Region) (config []*pb.DBGlobalConfigItem, err error) {
|
|
config = make([]*pb.DBGlobalConfigItem, 0)
|
|
err = postgres.Find(comm.TableGlobalConfig, &config, "region=?", region)
|
|
return
|
|
}
|
|
func (this *modelComp) addglobalconfig(config ...*pb.DBGlobalConfigItem) (err error) {
|
|
err = postgres.Insert(comm.TableGlobalConfig, config)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) updateglobalconfig(config ...*pb.DBGlobalConfigItem) (err error) {
|
|
err = postgres.Save(comm.TableGlobalConfig, config)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) delglobalconfig(ids []uint64) (err error) {
|
|
err = postgres.Delete(comm.TableGlobalConfig, "id IN ?", ids)
|
|
return
|
|
}
|
|
|
|
// 智能体
|
|
func (this *modelComp) agents() (agents []*pb.DBAgent, err error) {
|
|
agents = make([]*pb.DBAgent, 0)
|
|
err = mysql.Find(comm.TableAgent, &agents, "")
|
|
return
|
|
}
|
|
|
|
// 智能体
|
|
func (this *modelComp) agent(id string) (agent *pb.DBAgent, err error) {
|
|
agent = &pb.DBAgent{}
|
|
err = mysql.Find(comm.TableAgent, agent, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 智能体
|
|
func (this *modelComp) addagent(agent *pb.DBAgent) (err error) {
|
|
err = mysql.Insert(comm.TableAgent, agent)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) updateagent(agent *pb.DBAgent) (err error) {
|
|
err = mysql.Save(comm.TableAgent, agent)
|
|
return
|
|
}
|
|
func (this *modelComp) delagent(id string) (err error) {
|
|
err = mysql.Delete(comm.TableAgent, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// Mcp 服务:从 svc_config(MCP 服务类型) + 区域覆盖解析该区域可用的 MCP 服务器。
|
|
// MCP 已并入第三方服务(comm.SvcCatMCP),服务定义全局(app_name=”),url/tools 按区域 fork。
|
|
func (this *modelComp) mcpservers(region pb.Region) (servers []*pb.DBMcpServer, err error) {
|
|
servers = make([]*pb.DBMcpServer, 0)
|
|
svcs := make([]*comm.ThirdSvcConfig, 0)
|
|
if err = postgres.Find(comm.TableSvcConfig, &svcs, "app_name=? AND enable=?", "", true); err != nil {
|
|
return
|
|
}
|
|
for _, svc := range svcs {
|
|
if !comm.CategoriesHasMCP(svc.Categories) {
|
|
continue
|
|
}
|
|
ovr := this.mcpRegionOverride(svc.Id, int32(region))
|
|
if s := comm.ResolveMcpServer(svc, ovr, int32(region)); s != nil {
|
|
servers = append(servers, s)
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
// mcpRegionOverride 读取某 MCP 服务在指定区域的覆盖(不存在返回 nil)。
|
|
func (this *modelComp) mcpRegionOverride(svcId string, region int32) *comm.SvcRegionOverride {
|
|
ovr := &comm.SvcRegionOverride{}
|
|
if e := postgres.FindOne(comm.TableSvcRegionOverride, ovr, "app_name=? AND svc_id=? AND region=?", "", svcId, region); e != nil {
|
|
return nil
|
|
}
|
|
return ovr
|
|
}
|
|
|
|
// Mcp 服务:按 id 取单个(无区域信息,优先基础配置,回退首个有 url 的区域覆盖)。
|
|
func (this *modelComp) mcpserver(id string) (server *pb.DBMcpServer, err error) {
|
|
svc := &comm.ThirdSvcConfig{}
|
|
if err = postgres.FindOne(comm.TableSvcConfig, svc, "app_name=? AND id=?", "", id); err != nil {
|
|
return
|
|
}
|
|
if !comm.CategoriesHasMCP(svc.Categories) {
|
|
err = fmt.Errorf("服务 %s 不是 MCP 类型", id)
|
|
return
|
|
}
|
|
if s := comm.ResolveMcpServer(svc, nil, 0); s != nil {
|
|
server = s
|
|
return
|
|
}
|
|
ovrs := make([]*comm.SvcRegionOverride, 0)
|
|
if e := postgres.Find(comm.TableSvcRegionOverride, &ovrs, "app_name=? AND svc_id=?", "", id); e == nil {
|
|
for _, ovr := range ovrs {
|
|
if s := comm.ResolveMcpServer(svc, ovr, ovr.Region); s != nil {
|
|
server = s
|
|
return
|
|
}
|
|
}
|
|
}
|
|
// 无任何区域配 url:返回禁用占位(保持接口非 nil)。
|
|
server = &pb.DBMcpServer{Id: svc.Id, Name: svc.Name, Description: svc.Description, Enable: false}
|
|
return
|
|
}
|
|
|
|
// upsertMcpSvc 把一个 DBMcpServer 写入 svc_config(基础行) + 其 Region 的区域覆盖。
|
|
func (this *modelComp) upsertMcpSvc(s *pb.DBMcpServer) (err error) {
|
|
now := time.Now().UnixMilli()
|
|
svc := &comm.ThirdSvcConfig{}
|
|
miss := postgres.FindOne(comm.TableSvcConfig, svc, "app_name=? AND id=?", "", s.Id) != nil
|
|
if miss {
|
|
svc = &comm.ThirdSvcConfig{
|
|
AppName: "", Id: s.Id, Name: s.Name, Provider: "custom",
|
|
Categories: strconv.Itoa(int(comm.SvcCatMCP)), Description: s.Description, Enable: s.Enable,
|
|
Fields: comm.McpBaseFields(), Createtime: now, Updatetime: now,
|
|
}
|
|
if err = postgres.Insert(comm.TableSvcConfig, svc); err != nil {
|
|
return
|
|
}
|
|
} else {
|
|
svc.Name, svc.Description, svc.Enable, svc.Updatetime = s.Name, s.Description, s.Enable, now
|
|
if !comm.CategoriesHasMCP(svc.Categories) {
|
|
svc.Categories = strconv.Itoa(int(comm.SvcCatMCP))
|
|
}
|
|
if err = postgres.Save(comm.TableSvcConfig, svc); err != nil {
|
|
return
|
|
}
|
|
}
|
|
ovr := &comm.SvcRegionOverride{}
|
|
newOvr := postgres.FindOne(comm.TableSvcRegionOverride, ovr, "app_name=? AND svc_id=? AND region=?", "", s.Id, int32(s.Region)) != nil
|
|
if newOvr {
|
|
ovr = &comm.SvcRegionOverride{AppName: "", SvcId: s.Id, Region: int32(s.Region)}
|
|
}
|
|
ovr.Overrides = map[string]string{"url": s.Url, "type": strconv.Itoa(int(s.Stype)), "tools": s.Tools}
|
|
ovr.Updatetime = now
|
|
if newOvr {
|
|
return postgres.Insert(comm.TableSvcRegionOverride, ovr)
|
|
}
|
|
return postgres.Save(comm.TableSvcRegionOverride, ovr)
|
|
}
|
|
|
|
func (this *modelComp) addmcpservers(servers []*pb.DBMcpServer) (err error) {
|
|
for _, s := range servers {
|
|
if err = this.upsertMcpSvc(s); err != nil {
|
|
return
|
|
}
|
|
}
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) updatemcpservers(servers []*pb.DBMcpServer) (err error) {
|
|
return this.addmcpservers(servers)
|
|
}
|
|
|
|
func (this *modelComp) delmcpservers(ids []string) (err error) {
|
|
if len(ids) == 0 {
|
|
return
|
|
}
|
|
if err = postgres.Delete(comm.TableSvcConfig, "app_name=? AND id IN ?", "", ids); err != nil {
|
|
return
|
|
}
|
|
return postgres.Delete(comm.TableSvcRegionOverride, "app_name=? AND svc_id IN ?", "", ids)
|
|
}
|
|
|
|
// 厂商列表
|
|
func (this *modelComp) factorys() (models []*pb.DBBrand, err error) {
|
|
models = make([]*pb.DBBrand, 0)
|
|
err = postgres.Find(comm.TableBrand, &models, "")
|
|
return
|
|
}
|
|
func (this *modelComp) factory(id uint32) (model *pb.DBBrand, err error) {
|
|
model = &pb.DBBrand{}
|
|
err = postgres.Find(comm.TableBrand, model, "id=?", id)
|
|
return
|
|
}
|
|
func (this *modelComp) addfactory(model *pb.DBBrand) (err error) {
|
|
err = postgres.Insert(comm.TableBrand, model)
|
|
return
|
|
}
|
|
func (this *modelComp) savefactory(model *pb.DBBrand) (err error) {
|
|
err = postgres.Save(comm.TableBrand, model)
|
|
return
|
|
}
|
|
func (this *modelComp) delfactory(id uint32) (err error) {
|
|
err = postgres.Delete(comm.TableBrand, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 渠道商(挂在品牌商下,id=8位短码)。与 console 侧同表同库,两端实现须保持一致。
|
|
|
|
// channels 按条件列渠道商:brandid=0 不限品牌商;status<0 不限状态;keyword 匹配名称或 id。
|
|
func (this *modelComp) channels(brandid uint32, status int32, keyword string) (models []*pb.DBChannel, err error) {
|
|
models = make([]*pb.DBChannel, 0)
|
|
where := make([]string, 0, 3)
|
|
args := make([]interface{}, 0, 3)
|
|
if brandid != 0 {
|
|
where = append(where, "brandid=?")
|
|
args = append(args, brandid)
|
|
}
|
|
if status >= 0 {
|
|
where = append(where, "status=?")
|
|
args = append(args, status)
|
|
}
|
|
if kw := strings.TrimSpace(keyword); kw != "" {
|
|
where = append(where, "(name LIKE ? OR id LIKE ?)")
|
|
args = append(args, "%"+kw+"%", "%"+strings.ToUpper(kw)+"%")
|
|
}
|
|
err = postgres.Find(comm.TableChannel, &models, strings.Join(where, " AND "), args...)
|
|
return
|
|
}
|
|
|
|
// channel 按 id 取渠道商。用 FindOne,不存在时报错,存在性校验才有效。
|
|
func (this *modelComp) channel(id string) (model *pb.DBChannel, err error) {
|
|
model = &pb.DBChannel{}
|
|
err = postgres.FindOne(comm.TableChannel, model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) addchannel(model *pb.DBChannel) (err error) {
|
|
err = postgres.Insert(comm.TableChannel, model)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) savechannel(model *pb.DBChannel) (err error) {
|
|
err = postgres.Save(comm.TableChannel, model)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) delchannel(id string) (err error) {
|
|
err = postgres.Delete(comm.TableChannel, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// countbatchesbychannel 统计某渠道商已产生的生产批次数(删除前的引用完整性检查)。
|
|
func (this *modelComp) countbatchesbychannel(id string) (n int64, err error) {
|
|
err = postgres.Table(comm.TableProductionBatch).Where("channelid=?", id).Count(&n).Error
|
|
return
|
|
}
|
|
|
|
// genchannelid 生成未被占用的渠道商短码(8 位),重试兜底极小概率碰撞。
|
|
func (this *modelComp) genchannelid() string {
|
|
for i := 0; i < 5; i++ {
|
|
code, err := comm.GenerateChannelId()
|
|
if err != nil {
|
|
continue
|
|
}
|
|
if _, e := this.channel(code); e != nil { // 查不到 = 未占用
|
|
return code
|
|
}
|
|
}
|
|
code, _ := comm.GenerateChannelId()
|
|
return code
|
|
}
|
|
|
|
// 查询设备
|
|
func (this *modelComp) products() (models []*pb.DBProduct, err error) {
|
|
models = make([]*pb.DBProduct, 0)
|
|
err = postgres.Find(comm.TableProduct, &models, "")
|
|
return
|
|
}
|
|
|
|
// 查询设备
|
|
func (this *modelComp) product(id uint32) (model *pb.DBProduct, err error) {
|
|
model = &pb.DBProduct{}
|
|
err = postgres.FindOne(comm.TableProduct, model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 更新设备
|
|
func (this *modelComp) addproducts(products ...*pb.DBProduct) (err error) {
|
|
if err = postgres.Insert(comm.TableProduct, products); err != nil {
|
|
return err
|
|
}
|
|
// 新增产品**不再建 license 分表**:设备统一住全局 device_mac 表。
|
|
return
|
|
}
|
|
|
|
// 更新设备信息
|
|
func (this *modelComp) updateproducts(equipment *pb.DBProduct) (err error) {
|
|
err = postgres.Save(comm.TableProduct, equipment)
|
|
return
|
|
}
|
|
func (this *modelComp) delproducts(id uint32) (err error) {
|
|
err = postgres.Delete(comm.TableProduct, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 更新产品版本
|
|
func (this *modelComp) addproductversion(version *pb.DBProductVersion) (err error) {
|
|
err = postgres.Insert(comm.TableProductVersion, version)
|
|
return
|
|
}
|
|
|
|
// 查询设备
|
|
func (this *modelComp) productversions(pid uint32) (models []*pb.DBProductVersion, err error) {
|
|
models = make([]*pb.DBProductVersion, 0)
|
|
err = postgres.Find(comm.TableProductVersion, &models, "productid=?", pid)
|
|
return
|
|
}
|
|
|
|
// 查询单个产品版本
|
|
func (this *modelComp) productversion(id uint32) (model *pb.DBProductVersion, err error) {
|
|
model = &pb.DBProductVersion{}
|
|
err = postgres.FindOne(comm.TableProductVersion, model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 更新产品版本
|
|
func (this *modelComp) delproductversion(id uint32) (err error) {
|
|
err = postgres.Delete(comm.TableProductVersion, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// ChannelApp--------------------------------------------------------------
|
|
// 查询设备
|
|
func (this *modelComp) channelapps() (models []*pb.DBChannelApp, err error) {
|
|
models = make([]*pb.DBChannelApp, 0)
|
|
err = mysql.Find(comm.TableChannelApp, &models, "")
|
|
return
|
|
}
|
|
|
|
// 查询设备
|
|
func (this *modelComp) channelapp(channel int32) (model *pb.DBChannelApp, err error) {
|
|
model = &pb.DBChannelApp{}
|
|
err = mysql.FindOne(comm.TableChannelApp, model, "channel=?", channel)
|
|
return
|
|
}
|
|
|
|
// 更新设备
|
|
func (this *modelComp) addchannelapp(channelapp ...*pb.DBChannelApp) (err error) {
|
|
err = mysql.Insert(comm.TableChannelApp, channelapp)
|
|
return
|
|
}
|
|
|
|
// 更新设备信息
|
|
func (this *modelComp) updatechannelapp(channelapp *pb.DBChannelApp) (err error) {
|
|
err = mysql.Save(comm.TableChannelApp, channelapp)
|
|
return
|
|
}
|
|
func (this *modelComp) delchannelapp(channel int32) (err error) {
|
|
err = mysql.Delete(comm.TableChannelApp, "channel=?", channel)
|
|
return
|
|
}
|
|
|
|
// 生产批次台账------------------------------
|
|
func (this *modelComp) productionbatches(query interface{}, args ...interface{}) (models []*pb.DBProductionBatch, err error) {
|
|
models = make([]*pb.DBProductionBatch, 0)
|
|
err = postgres.Find(comm.TableProductionBatch, &models, query, args...)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) productionbatch(batchno string) (model *pb.DBProductionBatch, err error) {
|
|
model = &pb.DBProductionBatch{}
|
|
err = postgres.FindOne(comm.TableProductionBatch, &model, "batchno=?", batchno)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) addproductionbatch(model *pb.DBProductionBatch) (err error) {
|
|
err = postgres.Insert(comm.TableProductionBatch, model)
|
|
return
|
|
}
|
|
|
|
// 删除指定批次台账
|
|
func (this *modelComp) delproductionbatch(batchno string) (err error) {
|
|
err = postgres.Delete(comm.TableProductionBatch, "batchno=?", batchno)
|
|
return
|
|
}
|
|
|
|
// allocSerialRange 原子分配产品级序号区间,返回自增后的游标值(区间末端 end),
|
|
// 区间为 [end-total+1, end]。用 UPDATE ... RETURNING 单语句保证并发安全。
|
|
func (this *modelComp) allocSerialRange(pid uint32, total int64) (end int64, err error) {
|
|
err = postgres.Raw(
|
|
"UPDATE "+comm.TableProduct+" SET serialcursor = serialcursor + ? WHERE id = ? RETURNING serialcursor",
|
|
total, pid,
|
|
).Scan(&end).Error
|
|
return
|
|
}
|
|
|
|
// 统计指定批次中已被使用(status>0 或 uid非空)的授权码数量
|
|
func (this *modelComp) countUsedAuthCodesByBatch(pid uint32, batchno string) (used int64, err error) {
|
|
q, a := comm.ScopeToProduct(pid, "batchno=? AND (status>0 OR uid<>'')", []interface{}{batchno})
|
|
err = postgres.Table(comm.TableDeviceMac).Where(q, a...).Count(&used).Error
|
|
return
|
|
}
|
|
|
|
// 删除指定批次的全部授权码
|
|
func (this *modelComp) delAuthCodesByBatch(pid uint32, batchno string) (affected int64, err error) {
|
|
// ⚠️ 删除必须带 productid:合表后没有分表这层天然隔离了。
|
|
q, a := comm.ScopeToProduct(pid, "batchno=?", []interface{}{batchno})
|
|
tx := postgres.Table(comm.TableDeviceMac).Where(q, a...).Delete(&pb.DBAuthCode{})
|
|
affected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|
|
// 厂家设备------------------------------
|
|
func (this *modelComp) factoryDevics(pid uint32, query interface{}, args ...interface{}) (models []*pb.DBAuthCode, err error) {
|
|
models = make([]*pb.DBAuthCode, 0)
|
|
qs, _ := query.(string)
|
|
q, a := comm.ScopeToProduct(pid, qs, args)
|
|
err = postgres.Find(comm.TableDeviceMac, &models, q, a...)
|
|
return
|
|
}
|
|
|
|
// 分页查询授权码(支持 ORDER BY + LIMIT/OFFSET),同时返回命中总数
|
|
func (this *modelComp) factoryDevicsPaged(pid uint32, query, orderClause string, limit, offset int, args ...interface{}) (models []*pb.DBAuthCode, total int64, err error) {
|
|
models = make([]*pb.DBAuthCode, 0)
|
|
query, args = comm.ScopeToProduct(pid, query, args)
|
|
|
|
tx := postgres.Table(comm.TableDeviceMac)
|
|
if query != "" {
|
|
tx = tx.Where(query, args...)
|
|
}
|
|
if err = tx.Count(&total).Error; err != nil {
|
|
return
|
|
}
|
|
tx2 := postgres.Table(comm.TableDeviceMac)
|
|
if query != "" {
|
|
tx2 = tx2.Where(query, args...)
|
|
}
|
|
if orderClause != "" {
|
|
tx2 = tx2.Order(orderClause)
|
|
}
|
|
if limit > 0 {
|
|
tx2 = tx2.Limit(limit)
|
|
}
|
|
if offset > 0 {
|
|
tx2 = tx2.Offset(offset)
|
|
}
|
|
err = tx2.Find(&models).Error
|
|
return
|
|
}
|
|
|
|
// 批量设置设备码封禁状态(disabled: 0 正常, -1 禁用),返回实际更新条数
|
|
func (this *modelComp) setFactoryDevicsDisabled(pid uint32, codes []string, disabled int32) (affected int64, err error) {
|
|
// ⚠️ 批量更新必须带 productid,理由同 delAuthCodesByBatch。
|
|
q, a := comm.ScopeToProduct(pid, "code IN ?", []interface{}{codes})
|
|
tx := postgres.Table(comm.TableDeviceMac).Where(q, a...).Update("disabled", disabled)
|
|
affected = tx.RowsAffected
|
|
err = tx.Error
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) factoryDevic(pid uint32, code string) (model *pb.DBAuthCode, err error) {
|
|
model = &pb.DBAuthCode{}
|
|
// code 是全局主键,pid 只作额外校验(传 0 即不校验)。
|
|
q, a := comm.ScopeToProduct(pid, "code=?", []interface{}{code})
|
|
err = postgres.FindOne(comm.TableDeviceMac, &model, q, a...)
|
|
return
|
|
}
|
|
func (this *modelComp) factoryDevicforuid(pid uint32, uid string) (models []*pb.DBAuthCode, err error) {
|
|
models = make([]*pb.DBAuthCode, 0)
|
|
q, a := comm.ScopeToProduct(pid, "uid=?", []interface{}{uid})
|
|
err = postgres.Find(comm.TableDeviceMac, &models, q, a...)
|
|
return
|
|
}
|
|
func (this *modelComp) addfactoryDevics(pid uint32, models []*pb.DBAuthCode) (err error) {
|
|
// DBAuthCode 有 12 列,单条 INSERT 的预编译占位符上限是 65535(MySQL Error 1390)。
|
|
// 大批量会溢出,这里按 1000 行分批写入。
|
|
err = postgres.Table(comm.TableDeviceMac).CreateInBatches(models, 1000).Error
|
|
return
|
|
}
|
|
// 下面两个按主键(code)保存,主键全局唯一,不需要产品作用域。
|
|
func (this *modelComp) updatefactoryDevic(_ uint32, models *pb.DBAuthCode) (err error) {
|
|
err = postgres.Save(comm.TableDeviceMac, &models)
|
|
return
|
|
}
|
|
func (this *modelComp) updatefactoryDevics(_ uint32, models []*pb.DBAuthCode) (err error) {
|
|
err = postgres.Save(comm.TableDeviceMac, &models)
|
|
return
|
|
}
|
|
|
|
// decrProductStatActivated 将 product_stat.activated_count 原地减 1(下限为 0)。
|
|
// 在后台重置设备码(同步删除了对应 userdevice 行)时调用,保证仪表盘数据即时准确,
|
|
// 无需等待全量重建统计。
|
|
func (this *modelComp) decrProductStatActivated(productid uint32) (err error) {
|
|
err = mysql.Exec(
|
|
"UPDATE "+comm.TableProductStat+
|
|
" SET activated_count = GREATEST(0, activated_count - 1)"+
|
|
" WHERE productid = ?",
|
|
productid,
|
|
).Error
|
|
return
|
|
}
|
|
|
|
// 厂家公码---------------------------------------------------------------------
|
|
// 按品牌商 + 状态过滤查询公码列表(brandid=0 不过滤品牌商,statusFilter=-1 不过滤状态)
|
|
func (this *modelComp) factoryPublicCodes(brandid uint32, statusFilter int32) (models []*pb.DBFactoryPublicCode, err error) {
|
|
models = make([]*pb.DBFactoryPublicCode, 0)
|
|
tx := postgres.Table(comm.TableFactoryPublicCode)
|
|
if brandid != 0 {
|
|
tx = tx.Where("brandid=?", brandid)
|
|
}
|
|
if statusFilter >= 0 {
|
|
tx = tx.Where("status=?", statusFilter)
|
|
}
|
|
err = tx.Order("createtime DESC").Find(&models).Error
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) factoryPublicCode(code string) (model *pb.DBFactoryPublicCode, err error) {
|
|
model = &pb.DBFactoryPublicCode{}
|
|
err = postgres.FindOne(comm.TableFactoryPublicCode, model, "code=?", code)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) addFactoryPublicCode(model *pb.DBFactoryPublicCode) (err error) {
|
|
err = postgres.Insert(comm.TableFactoryPublicCode, model)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) saveFactoryPublicCode(model *pb.DBFactoryPublicCode) (err error) {
|
|
err = postgres.Save(comm.TableFactoryPublicCode, model)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) delFactoryPublicCode(code string) (err error) {
|
|
err = postgres.Delete(comm.TableFactoryPublicCode, "code=?", code)
|
|
return
|
|
}
|
|
|
|
// 会议记录模板管理---------------------------------------------------------------------
|
|
// 列表查询:排除 outline / template 大文本字段,避免传输过大
|
|
func (this *modelComp) gettemplates() (models []*pb.DBEchoMeetTemplate, err error) {
|
|
models = make([]*pb.DBEchoMeetTemplate, 0)
|
|
err = postgres.Table(comm.TableEchomeetTemplate).
|
|
Select("id, tid, source, title, description, language, ttype, tags, icon, sort").
|
|
Order("sort DESC").
|
|
Find(&models).Error
|
|
return
|
|
}
|
|
|
|
// 详情查询:返回完整字段(含 outline / template)
|
|
func (this *modelComp) gettemplate(id uint64) (model *pb.DBEchoMeetTemplate, err error) {
|
|
model = &pb.DBEchoMeetTemplate{}
|
|
err = postgres.FindOne(comm.TableEchomeetTemplate, &model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) deltemplates(ids []uint64) (err error) {
|
|
err = postgres.Delete(comm.TableEchomeetTemplate, "id IN ?", ids)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) addtemplates(templates []*pb.DBEchoMeetTemplate) (err error) {
|
|
err = postgres.Insert(comm.TableEchomeetTemplate, templates)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) updatetemplates(templates []*pb.DBEchoMeetTemplate) (err error) {
|
|
err = postgres.Save(comm.TableEchomeetTemplate, templates)
|
|
return
|
|
}
|
|
|
|
// 支付商品---------------------------------------------------------------------
|
|
// 查询支付商品
|
|
func (this *modelComp) goodss() (models []*pb.DBGoods, err error) {
|
|
models = make([]*pb.DBGoods, 0)
|
|
err = mysql.Find(comm.TableGoods, &models, "")
|
|
return
|
|
}
|
|
|
|
// 查询支付商品详情
|
|
func (this *modelComp) goods(id string) (model *pb.DBGoods, err error) {
|
|
model = &pb.DBGoods{}
|
|
err = mysql.FindOne(comm.TableGoods, &model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 添加支付商品
|
|
func (this *modelComp) addgoods(product *pb.DBGoods) (err error) {
|
|
err = mysql.Insert(comm.TableGoods, product)
|
|
return
|
|
}
|
|
|
|
// 更新支付商品
|
|
func (this *modelComp) updategoods(product *pb.DBGoods) (err error) {
|
|
err = mysql.Save(comm.TableGoods, product)
|
|
return
|
|
}
|
|
|
|
// 删除支付商品
|
|
func (this *modelComp) delgoods(id string) (err error) {
|
|
err = mysql.Delete(comm.TableGoods, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 唤醒词管理---------------------------------------------------------------------
|
|
// 查询唤醒语音列表
|
|
func (this *modelComp) wakeupvoices() (models []*pb.DBWakeupVoice, err error) {
|
|
models = make([]*pb.DBWakeupVoice, 0)
|
|
err = postgres.Find(comm.TableWakeupVoice, &models, "")
|
|
return
|
|
}
|
|
|
|
// 查询唤醒语音详情
|
|
func (this *modelComp) wakeupvoice(id uint32) (model *pb.DBWakeupVoice, err error) {
|
|
model = &pb.DBWakeupVoice{}
|
|
err = postgres.FindOne(comm.TableWakeupVoice, &model, "id=?", id)
|
|
return
|
|
}
|
|
|
|
// 添加唤醒语音
|
|
func (this *modelComp) addwakeupvoice(voice *pb.DBWakeupVoice) (err error) {
|
|
err = postgres.Insert(comm.TableWakeupVoice, voice)
|
|
return
|
|
}
|
|
|
|
// 更新唤醒语音
|
|
func (this *modelComp) updatewakeupvoice(voice *pb.DBWakeupVoice) (err error) {
|
|
err = postgres.Save(comm.TableWakeupVoice, voice)
|
|
return
|
|
}
|
|
|
|
// 删除唤醒语音
|
|
func (this *modelComp) delwakeupvoice(id uint32) (err error) {
|
|
err = postgres.Delete(comm.TableWakeupVoice, "id=?", id)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) getPayOrders(where string, page, size int32, args ...interface{}) (models []*pb.DBPayOrder, total int64, err error) {
|
|
models = make([]*pb.DBPayOrder, 0)
|
|
tx := mysql.Table(comm.TablePayOrder)
|
|
if where != "" {
|
|
tx = tx.Where(where, args...)
|
|
}
|
|
if err = tx.Count(&total).Error; err != nil {
|
|
this.module.Errorln(err)
|
|
return
|
|
}
|
|
offset := (page - 1) * size
|
|
if offset < 0 {
|
|
offset = 0
|
|
}
|
|
if err = tx.Order("create_time DESC").Offset(int(offset)).Limit(int(size)).Find(&models).Error; err != nil {
|
|
this.module.Errorln(err)
|
|
return
|
|
}
|
|
return
|
|
}
|
|
|
|
// 查询用户会议记录
|
|
func (this *modelComp) getUserEchomeetRecords(uid string, start, end int64) (models []*pb.DBEchoMeetRecord, err error) {
|
|
models = make([]*pb.DBEchoMeetRecord, 0)
|
|
where := "uid = ?"
|
|
args := []interface{}{uid}
|
|
if start > 0 {
|
|
where += " AND creationtime >= ?"
|
|
args = append(args, start)
|
|
}
|
|
if end > 0 {
|
|
where += " AND creationtime <= ?"
|
|
args = append(args, end)
|
|
}
|
|
err = mysql.Table(comm.TableEchomeetRecord).
|
|
Omit("original", "translate", "summary").
|
|
Where(where, args...).
|
|
Order("creationtime DESC").
|
|
Find(&models).Error
|
|
return
|
|
}
|
|
|
|
// 查询用户使用量日志
|
|
func (this *modelComp) getUserUseLog(query string, args ...interface{}) (models []*pb.DBUserUseLog, err error) {
|
|
models = make([]*pb.DBUserUseLog, 0)
|
|
err = mysql.Find(comm.TableUserUseLog, &models, query, args...)
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) addIntegralLog(log *pb.DBUserUseLog) (err error) {
|
|
err = mysql.Insert(comm.TableUserUseLog, log)
|
|
return
|
|
}
|
|
|
|
// 累计注册人数(用户表 COUNT(*))
|
|
func (this *modelComp) countTotalUsers() (total int64, err error) {
|
|
err = mysql.Table(comm.TableUser).Count(&total).Error
|
|
return
|
|
}
|
|
|
|
// 累计充值人数(付费订单表 COUNT DISTINCT uid, status=已支付)
|
|
func (this *modelComp) countTotalPayUsers() (total int64, err error) {
|
|
err = mysql.Table(comm.TablePayOrder).
|
|
Where("status=?", int32(pb.PayOrderStatus_PAY_ORDER_PAID)).
|
|
Distinct("uid").
|
|
Count(&total).Error
|
|
return
|
|
}
|
|
|
|
// agentBatchStats 代理的累计生产批次和设备码总量(production_batch 维度)。
|
|
// 入参 brandIDs 为品牌商id集合(原厂家维度),productIDs 为产品id集合。
|
|
func (this *modelComp) agentBatchStats(brandIDs, productIDs []uint32) (batchCount, deviceCount int64, err error) {
|
|
type sumResult struct{ Total int64 }
|
|
var cond string
|
|
var args []interface{}
|
|
if len(brandIDs) > 0 && len(productIDs) > 0 {
|
|
cond = "brandid IN ? OR productid IN ?"
|
|
args = []interface{}{brandIDs, productIDs}
|
|
} else if len(brandIDs) > 0 {
|
|
cond = "brandid IN ?"
|
|
args = []interface{}{brandIDs}
|
|
} else if len(productIDs) > 0 {
|
|
cond = "productid IN ?"
|
|
args = []interface{}{productIDs}
|
|
}
|
|
txCount := postgres.Table(comm.TableProductionBatch)
|
|
txSum := postgres.Table(comm.TableProductionBatch)
|
|
if cond != "" {
|
|
txCount = txCount.Where(cond, args...)
|
|
txSum = txSum.Where(cond, args...)
|
|
}
|
|
if err = txCount.Count(&batchCount).Error; err != nil {
|
|
return
|
|
}
|
|
var r sumResult
|
|
if err = txSum.Select("COALESCE(SUM(devicenum), 0) as total").Scan(&r).Error; err != nil {
|
|
return
|
|
}
|
|
deviceCount = r.Total
|
|
return
|
|
}
|
|
|
|
// agentDeviceUIDs 返回绑定了指定产品设备的去重用户 uid 列表。
|
|
// 按 ServiceDB 的 userdevice 表实时统计(per-后台口径),不读共享 AdminDB 的 license 分表,
|
|
// 供充值 / 消耗统计按用户聚合使用。
|
|
// 注:激活数 / 绑定用户数的展示值改读 product_stat 快照(见 sumProductStats)。
|
|
func (this *modelComp) agentDeviceUIDs(productIDs []uint32) (uids []string, err error) {
|
|
uids = make([]string, 0)
|
|
if len(productIDs) == 0 {
|
|
return
|
|
}
|
|
err = mysql.Table(comm.TableUserdevice).
|
|
Where("productid IN ? AND uid != ''", productIDs).
|
|
Distinct("uid").Pluck("uid", &uids).Error
|
|
return
|
|
}
|
|
|
|
// sumProductStats 读取 product_stat 快照,汇总指定产品的激活设备数与绑定用户数。
|
|
// product_stat 由「重建统计」时全量扫描 userdevice 落库(见 rebuildProductStats)。
|
|
// 注:bound_user_count 为各产品独立去重值,跨产品求和时同时绑定多产品的用户会被重复计数;
|
|
// 单产品(仪表盘按 product_id 查看)口径精确。
|
|
func (this *modelComp) sumProductStats(productIDs []uint32) (activatedCount, boundUserCount int64, err error) {
|
|
if len(productIDs) == 0 {
|
|
return
|
|
}
|
|
type row struct {
|
|
Activated int64 `gorm:"column:activated"`
|
|
Bound int64 `gorm:"column:bound"`
|
|
}
|
|
var r row
|
|
err = mysql.Table(comm.TableProductStat).
|
|
Select("COALESCE(SUM(activated_count),0) AS activated, COALESCE(SUM(bound_user_count),0) AS bound").
|
|
Where("productid IN ?", productIDs).Scan(&r).Error
|
|
activatedCount, boundUserCount = r.Activated, r.Bound
|
|
return
|
|
}
|
|
|
|
// agentPayStats 代理用户的充值订单数和累计金额
|
|
func (this *modelComp) agentPayStats(uids []string) (orderCount, totalAmount int64, err error) {
|
|
if len(uids) == 0 {
|
|
return
|
|
}
|
|
type sumResult struct{ Total int64 }
|
|
paid := int32(pb.PayOrderStatus_PAY_ORDER_PAID)
|
|
if err = mysql.Table(comm.TablePayOrder).
|
|
Where("uid IN ? AND status = ?", uids, paid).Count(&orderCount).Error; err != nil {
|
|
return
|
|
}
|
|
var r sumResult
|
|
if err = mysql.Table(comm.TablePayOrder).
|
|
Where("uid IN ? AND status = ?", uids, paid).
|
|
Select("COALESCE(SUM(amount), 0) as total").Scan(&r).Error; err != nil {
|
|
return
|
|
}
|
|
totalAmount = r.Total
|
|
return
|
|
}
|
|
|
|
// userstatistics 表实际列名(gorm 默认命名策略将 Trademodel1Time 转为 trademodel1_time)
|
|
const (
|
|
colTradeTimeSum = "IFNULL(trademodel1_time,0)+IFNULL(trademodel2_time,0)+IFNULL(trademodel3_time,0)+IFNULL(trademodel4_time,0)"
|
|
colTradeNumSum = "IFNULL(trademodel1_num,0)+IFNULL(trademodel2_num,0)+IFNULL(trademodel3_num,0)+IFNULL(trademodel4_num,0)"
|
|
)
|
|
|
|
// agentUsageStats 名下用户累计翻译/会议消耗汇总。uids 为空返回 0。
|
|
func (this *modelComp) agentUsageStats(uids []string) (tradeTime, tradeCount, meetTime, meetCount int64, err error) {
|
|
if len(uids) == 0 {
|
|
return
|
|
}
|
|
type sumRow struct {
|
|
TradeTime int64 `gorm:"column:trade_time"`
|
|
TradeCount int64 `gorm:"column:trade_count"`
|
|
MeetTime int64 `gorm:"column:meet_time"`
|
|
MeetCount int64 `gorm:"column:meet_count"`
|
|
}
|
|
var r sumRow
|
|
err = mysql.Table(comm.TableUserStatistics).
|
|
Select("COALESCE(SUM("+colTradeTimeSum+"),0) AS trade_time, "+
|
|
"COALESCE(SUM("+colTradeNumSum+"),0) AS trade_count, "+
|
|
"COALESCE(SUM(meettime),0) AS meet_time, "+
|
|
"COALESCE(SUM(meetnum),0) AS meet_count").
|
|
Where("uid IN ?", uids).Scan(&r).Error
|
|
if err != nil {
|
|
return
|
|
}
|
|
tradeTime, tradeCount, meetTime, meetCount = r.TradeTime, r.TradeCount, r.MeetTime, r.MeetCount
|
|
return
|
|
}
|
|
|
|
// userRankRow 排行榜单行原始数据(不含 user 信息,需另查 user 表填充)
|
|
type userRankRow struct {
|
|
Uid string `gorm:"column:uid"`
|
|
TradeTime int64 `gorm:"column:trade_time"`
|
|
TradeCount int64 `gorm:"column:trade_count"`
|
|
MeetTime int64 `gorm:"column:meet_time"`
|
|
MeetCount int64 `gorm:"column:meet_count"`
|
|
}
|
|
|
|
// topUserRankByTrade 按翻译总时长降序取 Top N(4 个翻译模式时长合计)
|
|
func (this *modelComp) topUserRankByTrade(limit int) (rows []*userRankRow, err error) {
|
|
rows = make([]*userRankRow, 0)
|
|
err = mysql.Table(comm.TableUserStatistics).
|
|
Select("uid, (" + colTradeTimeSum + ") AS trade_time, (" + colTradeNumSum + ") AS trade_count, IFNULL(meettime,0) AS meet_time, IFNULL(meetnum,0) AS meet_count").
|
|
Where("(" + colTradeTimeSum + ") > 0").
|
|
Order("trade_time DESC").
|
|
Limit(limit).
|
|
Scan(&rows).Error
|
|
return
|
|
}
|
|
|
|
// topUserRankByMeet 按会议时长降序取 Top N
|
|
func (this *modelComp) topUserRankByMeet(limit int) (rows []*userRankRow, err error) {
|
|
rows = make([]*userRankRow, 0)
|
|
err = mysql.Table(comm.TableUserStatistics).
|
|
Select("uid, (" + colTradeTimeSum + ") AS trade_time, (" + colTradeNumSum + ") AS trade_count, IFNULL(meettime,0) AS meet_time, IFNULL(meetnum,0) AS meet_count").
|
|
Where("IFNULL(meettime,0) > 0").
|
|
Order("meet_time DESC").
|
|
Limit(limit).
|
|
Scan(&rows).Error
|
|
return
|
|
}
|
|
|
|
// findUsersByUids 批量查 user 表,返回 uid -> DBUser
|
|
func (this *modelComp) findUsersByUids(uids []string) (m map[string]*pb.DBUser, err error) {
|
|
m = make(map[string]*pb.DBUser, len(uids))
|
|
if len(uids) == 0 {
|
|
return
|
|
}
|
|
var users []*pb.DBUser
|
|
if err = mysql.Table(comm.TableUser).Where("uid IN ?", uids).Find(&users).Error; err != nil {
|
|
return
|
|
}
|
|
for _, u := range users {
|
|
m[u.Uid] = u
|
|
}
|
|
return
|
|
}
|
|
|
|
// addAdminResourceLog 写入超管→后台账号 的资源点流水
|
|
func (this *modelComp) addAdminResourceLog(log *pb.DBAdminResourceLog) (err error) {
|
|
err = mysql.Insert(comm.TableAdminResLog, log)
|
|
return
|
|
}
|
|
|
|
// getAdminResourceLogs 查询资源流水(按账号 + 分页)
|
|
func (this *modelComp) getAdminResourceLogs(account string, page, size int32) (logs []*pb.DBAdminResourceLog, total int64, err error) {
|
|
logs = make([]*pb.DBAdminResourceLog, 0)
|
|
tx := mysql.Table(comm.TableAdminResLog)
|
|
if account != "" {
|
|
tx = tx.Where("from_account=? OR to_account=?", account, account)
|
|
}
|
|
if err = tx.Count(&total).Error; err != nil {
|
|
return
|
|
}
|
|
if size <= 0 {
|
|
size = 20
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
offset := (page - 1) * size
|
|
err = tx.Order("ts DESC").Offset(int(offset)).Limit(int(size)).Find(&logs).Error
|
|
return
|
|
}
|
|
|
|
// sumAdminResourceFlow 汇总某账号的累计接收/派发量(vipday/aichat/trade-sec/meet-sec)
|
|
// 接收: to_account=account 的 SUM
|
|
// 派发: extra=account 的 useruselog SUM(即名下用户被赠送的)
|
|
func (this *modelComp) sumAdminReceivedResource(account string) (vipday, aichat, tradesec, meetsec int64, err error) {
|
|
type row struct {
|
|
Vipday int64 `gorm:"column:vipday"`
|
|
Aichat int64 `gorm:"column:aichat"`
|
|
Tradesec int64 `gorm:"column:tradesec"`
|
|
Meetsec int64 `gorm:"column:meetsec"`
|
|
}
|
|
var r row
|
|
err = mysql.Table(comm.TableAdminResLog).
|
|
Select("COALESCE(SUM(vipday),0) AS vipday, COALESCE(SUM(aichatintegral),0) AS aichat, "+
|
|
"COALESCE(SUM(tradesecond),0) AS tradesec, COALESCE(SUM(meetsecond),0) AS meetsec").
|
|
Where("to_account=?", account).Scan(&r).Error
|
|
if err != nil {
|
|
return
|
|
}
|
|
vipday, aichat, tradesec, meetsec = r.Vipday, r.Aichat, r.Tradesec, r.Meetsec
|
|
return
|
|
}
|
|
|
|
func (this *modelComp) sumAdminDispatchedResource(account string) (vipday, aichat, tradesec, meetsec int64, err error) {
|
|
type row struct {
|
|
Vipday int64 `gorm:"column:vipday"`
|
|
Aichat int64 `gorm:"column:aichat"`
|
|
Tradesec int64 `gorm:"column:tradesec"`
|
|
Meetsec int64 `gorm:"column:meetsec"`
|
|
}
|
|
var r row
|
|
// 注意:DBUserUseLog.Addvipday 的 gorm tag 写法 `gorm:"viptime"` 不是有效的自定义列名语法
|
|
// (正确应为 `gorm:"column:viptime"`),GORM 会忽略该 tag 并按默认命名策略,
|
|
// 表中实际列名为 addvipday;此处以实际列名为准。
|
|
err = mysql.Table(comm.TableUserUseLog).
|
|
Select("COALESCE(SUM(addvipday),0) AS vipday, COALESCE(SUM(addagentintegral),0) AS aichat, "+
|
|
"COALESCE(SUM(addtradesecond),0) AS tradesec, COALESCE(SUM(addmeetsecond),0) AS meetsec").
|
|
Where("logtype=? AND extra=?", int32(pb.UserLogType_AdminGive), account).Scan(&r).Error
|
|
if err != nil {
|
|
return
|
|
}
|
|
vipday, aichat, tradesec, meetsec = r.Vipday, r.Aichat, r.Tradesec, r.Meetsec
|
|
return
|
|
}
|
|
|
|
// getUserUseLogsPaged 用户使用量日志分页查询(流水)
|
|
// operator 非空时按 extra=operator 过滤(仅 AdminGive 类型的 extra 是操作人账号)
|
|
func (this *modelComp) getUserUseLogsPaged(uid string, logtype pb.UserLogType, operator string, page, size int32) (logs []*pb.DBUserUseLog, total int64, err error) {
|
|
logs = make([]*pb.DBUserUseLog, 0)
|
|
tx := mysql.Table(comm.TableUserUseLog)
|
|
if uid != "" {
|
|
tx = tx.Where("uid=?", uid)
|
|
}
|
|
if logtype != pb.UserLogType_UserLogTypeUnknown {
|
|
tx = tx.Where("logtype=?", int32(logtype))
|
|
}
|
|
if operator != "" {
|
|
tx = tx.Where("extra=?", operator)
|
|
}
|
|
if err = tx.Count(&total).Error; err != nil {
|
|
return
|
|
}
|
|
if size <= 0 {
|
|
size = 10
|
|
}
|
|
if page <= 0 {
|
|
page = 1
|
|
}
|
|
offset := (page - 1) * size
|
|
err = tx.Order("ts DESC").Offset(int(offset)).Limit(int(size)).Find(&logs).Error
|
|
return
|
|
}
|
|
|