Files
iotlcm/server/internal/logic/iot/t_sim_flow_pool.go
T
MiFaZhan 59eb34976a chore: 初始开源发布
iotlcm-admin-svr:管理后台服务。

- Go module:iotlcm → git.5iot.com/iotlcm-public/iotlcm-admin-svr(729 文件)
- 凭据与地址脱敏:JWT signingKey(含代码里的 MustGet 默认值)、
  MySQL DSN、Redis 口令、告警邮件发件人邮箱全部改为占位;
  私网 IP → 文档地址段;库名 xiujie_admin → iotlcm
- 真实手机号/邮箱 → 合法 11 位编造号 / example.com
- 移除内部资产:CI 触发器、过时 DDL(sql/db_iotlcm.sql)、
  私有依赖 go get 行、明文 registry 登录口令
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- Dockerfile 改用公共基础镜像
- 拼错的 RADEME.md → README.md,README 重写
- 需要真实数据库的用例改为可用 IOTLCM_TEST_WITH_DB=1 显式启用
- 补 Apache-2.0 LICENSE,LF 行尾 + .gitattributes

数据库 schema 不随本仓发布(生产 DDL 已过时),README 已说明。
2026-10-23 14:48:30 +08:00

465 lines
18 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Package gen
// @Link https://github.com/xiujiecn/xiu-admin
// @Copyright Copyright (c) 2026 LiXiujie
// @Author Lxj <li@xiujie.cn>
// @License https://github.com/xiujiecn/xiu-admin/blob/master/LICENSE
// @AutoGenerate Version
package iot
import (
"context"
"fmt"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/contexts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/hook"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/service"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/convert"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/utility/excel"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
"github.com/gogf/gf/v2/os/gctx"
"github.com/gogf/gf/v2/os/gtime"
"github.com/gogf/gf/v2/util/gconv"
)
type sIotTSimFlowPool struct{}
func NewIotTSimFlowPool() *sIotTSimFlowPool {
return &sIotTSimFlowPool{}
}
func init() {
service.RegisterIotTSimFlowPool(NewIotTSimFlowPool())
}
// Model 流量池表ORM模型
func (s *sIotTSimFlowPool) Model(ctx context.Context, option ...*handler.Option) *gdb.Model {
if len(option) == 0 {
option = append(option, &handler.Option{
FilterTenant: true,
FilterAuth: true,
})
}
return handler.Model(dao.TSimFlowPool.Ctx(ctx), option...)
}
// List 获取流量池表列表
func (s *sIotTSimFlowPool) List(ctx context.Context, in *model.TSimFlowPoolListParam) (list []*model.TSimFlowPoolListModel, totalCount int, err error) {
mod := s.Model(ctx)
poolPackage := dao.TSimFlowPackageInfo.Table()
flowPool := dao.TSimFlowPool.Table()
sysDept := dao.SysDept.Table()
mod = mod.FieldsPrefix(flowPool, model.TSimFlowPoolListModel{})
// package_codes stores the actual pool package codes. pool_package_code is
// a pool dimension/configuration value and must not be used to collect names.
mod = mod.Fields("GROUP_CONCAT(DISTINCT " + poolPackage + "." + dao.TSimFlowPackageInfo.Columns().PackageName + " ORDER BY " + poolPackage + "." + dao.TSimFlowPackageInfo.Columns().PackageName + " SEPARATOR ',') as poolPackageName")
mod = mod.Fields(sysDept + "." + dao.SysDept.Columns().DeptName + " as deptName")
// 查询主键ID
if in.Id > 0 {
mod = mod.Where(dao.TSimFlowPool.Columns().Id, in.Id)
}
// 查询客户编号
if in.DeptId > 0 {
mod = mod.Where(dao.TSimFlowPool.Columns().DeptId, in.DeptId)
}
// 查询流量池编号
if in.FlowPoolCode != "" {
mod = mod.Where(dao.TSimFlowPool.Columns().FlowPoolCode, in.FlowPoolCode)
}
// 查询状态:0 正常、1 停用、2 锁定
if in.PoolStatus != nil {
mod = mod.Where(dao.TSimFlowPool.Columns().PoolStatus, *in.PoolStatus)
}
// 查询创建时间
if len(in.CreatedAt) == 2 {
in.CreatedAt[1] = in.CreatedAt[1].EndOfDay()
}
if len(in.CreatedAt) == 2 {
mod = mod.WhereBetween(dao.TSimFlowPool.Columns().CreatedAt, in.CreatedAt[0], in.CreatedAt[1])
}
// 分页
mod = mod.Page(in.Page, in.PageSize)
// 排序
mod = mod.OrderDesc(dao.TSimFlowPool.Columns().Id)
mod = mod.LeftJoin(poolPackage, fmt.Sprintf(
"%s.%s = 102 AND JSON_CONTAINS(%s.%s, JSON_QUOTE(%s.%s))",
poolPackage,
dao.TSimFlowPackageInfo.Columns().PackageType,
flowPool,
dao.TSimFlowPool.Columns().PackageCodes,
poolPackage,
dao.TSimFlowPackageInfo.Columns().PackageCode,
))
mod = mod.LeftJoin(sysDept, sysDept+"."+dao.SysDept.Columns().DeptId+"="+flowPool+"."+dao.TSimFlowPool.Columns().DeptId)
// 按流量池主键分组(因为使用了聚合函数 GROUP_CONCAT)
mod = mod.Group(flowPool + ".id")
// 操作人摘要信息
mod = mod.Hook(hook.MemberSummary)
// 查询数据
if err = mod.ScanAndCount(&list, &totalCount, false); err != nil {
err = gerror.Wrap(err, "获取流量池表列表失败,请稍后重试!")
return
}
return
}
// Export 导出流量池表
func (s *sIotTSimFlowPool) Export(ctx context.Context, in *model.TSimFlowPoolListParam) (err error) {
list, totalCount, err := s.List(ctx, in)
if err != nil {
return
}
// 字段的排序是依据tags的字段顺序,如果你不想使用默认的排序方式,可以直接定义 tags = []string{"字段名称", "字段名称2", ...}
tags, err := convert.GetEntityDescTags(model.TSimFlowPoolExportModel{})
if err != nil {
return
}
var (
fileName = "导出流量池表-" + gctx.CtxId(ctx)
sheetName = fmt.Sprintf("索引条件共%v行,共%v页,当前导出是第%v页,本页共%v行", totalCount, (totalCount+in.PageSize-1)/in.PageSize, in.Page, len(list))
exports []model.TSimFlowPoolExportModel
)
if err = gconv.Scan(list, &exports); err != nil {
return
}
err = excel.ExportByStructs(ctx, tags, exports, fileName, sheetName)
return
}
// Edit 修改/新增流量池表
func (s *sIotTSimFlowPool) Edit(ctx context.Context, in *model.TSimFlowPoolEditParam) (err error) {
// 验证'FlowPoolCode'唯一
if err = xgorm.IsUnique(ctx, &dao.TSimFlowPool, g.Map{dao.TSimFlowPool.Columns().FlowPoolCode: in.FlowPoolCode}, "流量池编号已存在", in.Id); err != nil {
return
}
return g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) (err error) {
// 修改
if in.Id > 0 {
updateBy := contexts.GetUserId(ctx)
in.UpdatedBy = &updateBy
in.UpdatedAt = gtime.Now()
if _, err = s.Model(ctx).
Fields(model.TSimFlowPoolUpdateFields{}).
WherePri(in.Id).Data(in).Update(); err != nil {
err = gerror.Wrap(err, "修改流量池表失败,请稍后重试!")
}
return
}
// 新增
tenantId := contexts.GetTenantId(ctx)
in.TenantId = &tenantId
deptId := contexts.GetDeptId(ctx)
in.CreatedDept = &deptId
userid := contexts.GetUserId(ctx)
in.CreatedBy = &userid
in.CreatedAt = gtime.Now()
if _, err = s.Model(ctx, &handler.Option{FilterAuth: false}).
Fields(model.TSimFlowPoolInsertFields{}).
Data(in).Insert(); err != nil {
err = gerror.Wrap(err, "新增流量池表失败,请稍后重试!")
}
return
})
}
// Delete 删除流量池表
func (s *sIotTSimFlowPool) Delete(ctx context.Context, in *model.TSimFlowPoolDeleteParam) (err error) {
if _, err = s.Model(ctx).WherePri(in.Id).Unscoped().Delete(); err != nil {
err = gerror.Wrap(err, "删除流量池表失败,请稍后重试!")
return
}
return
}
// View 获取流量池表指定信息
func (s *sIotTSimFlowPool) View(ctx context.Context, in *model.TSimFlowPoolViewParam) (res *model.TSimFlowPoolViewModel, err error) {
mod := s.Model(ctx)
poolPackage := dao.TSimFlowPackageInfo.Table()
flowPool := dao.TSimFlowPool.Table()
sysDept := dao.SysDept.Table()
mod = mod.WherePri(in.Id)
mod = mod.LeftJoin(poolPackage, fmt.Sprintf(
"%s.%s = 102 AND JSON_CONTAINS(%s.%s, JSON_QUOTE(%s.%s))",
poolPackage,
dao.TSimFlowPackageInfo.Columns().PackageType,
flowPool,
dao.TSimFlowPool.Columns().PackageCodes,
poolPackage,
dao.TSimFlowPackageInfo.Columns().PackageCode,
))
mod = mod.LeftJoin(sysDept, sysDept+"."+dao.SysDept.Columns().DeptId+"="+flowPool+"."+dao.TSimFlowPool.Columns().DeptId)
mod = mod.FieldsPrefix(flowPool, model.TSimFlowPoolListModel{})
mod = mod.Fields("GROUP_CONCAT(DISTINCT " + poolPackage + "." + dao.TSimFlowPackageInfo.Columns().PackageName + " ORDER BY " + poolPackage + "." + dao.TSimFlowPackageInfo.Columns().PackageName + " SEPARATOR ',') as poolPackageName")
mod = mod.Fields(sysDept + "." + dao.SysDept.Columns().DeptName + " as deptName")
mod = mod.Group(flowPool + ".id")
if err = mod.Hook(hook.MemberSummary).Scan(&res); err != nil {
err = gerror.Wrap(err, "获取流量池表信息,请稍后重试!")
return
}
return
}
// SyncPoolCard 流量池同步卡信息(调用计算方法)
func (s *sIotTSimFlowPool) SyncPoolCard(ctx context.Context, deptId int64) (err error) {
// 调用卡信息表获取套餐选项
packageOptions, err := service.IotTSimCardInfo().GetCardPackageOptions(ctx, deptId)
if err != nil {
return gerror.Wrap(err, "获取卡套餐选项失败,请稍后重试!")
}
g.Log().Info(ctx, "packageOptions 卡套餐列表长度(当前所有生效中的套餐):%v", len(packageOptions))
g.Log().Info(ctx, "packageOptions 卡套餐列表:%v", packageOptions)
// 提取套餐编码列表
var cardPackageCodes []string
for _, pkg := range packageOptions {
cardPackageCodes = append(cardPackageCodes, pkg.CurrentPackageCode)
}
g.Log().Info(ctx, "cardPackageCodes 卡套餐编码列表长度:%v", len(cardPackageCodes))
g.Log().Info(ctx, "cardPackageCodes 卡套餐编码列表(卡信息表该用户当前所有生效中的套餐):%v", cardPackageCodes)
if len(cardPackageCodes) == 0 {
g.Log().Info(ctx, "SyncPoolCard 卡套餐列表为空,跳过同步")
return nil
}
// 调用流量套餐服务获取对应的池套餐编码列表
poolPackageMap102, poolPackageMap104, err := service.IotTSimFlowPackageInfo().GetPoolPackagesByCardPackages(ctx, cardPackageCodes)
if err != nil {
return gerror.Wrap(err, "获取池套餐列表失败,请稍后重试!")
}
g.Log().Info(ctx, "poolPackageMap102 102池套餐编码列表:%v", poolPackageMap102) //{"dx-1g-1y":400,"dx-1g-1y-130":400,"dx-300m-3y":300}池套餐
g.Log().Info(ctx, "poolPackageMap104 104池套餐编码列表:%v", poolPackageMap104) //{"dx-30g-1y":400}加油包
// 合并所有池套餐编码
var poolPackageCodes []int64
seenPoolPackageCodes := make(map[int64]bool)
for _, code := range poolPackageMap102 {
if !seenPoolPackageCodes[code] {
seenPoolPackageCodes[code] = true
poolPackageCodes = append(poolPackageCodes, code)
}
}
for _, code := range poolPackageMap104 {
if !seenPoolPackageCodes[code] {
seenPoolPackageCodes[code] = true
poolPackageCodes = append(poolPackageCodes, code)
}
}
g.Log().Info(ctx, "poolPackageCodes 池套餐列表:%v", poolPackageCodes) // [400,300]
if len(poolPackageCodes) == 0 {
g.Log().Info(ctx, "SyncPoolCard 池套餐列表为空,跳过同步")
return nil
}
// 根据 deptId 查询已有的流量池
var existingPools []*model.TSimFlowPoolListModel
existingPoolMod := dao.TSimFlowPool.Ctx(ctx).
Where(dao.TSimFlowPool.Columns().DeptId, deptId).
WhereIn(dao.TSimFlowPool.Columns().PoolPackageCode, poolPackageCodes).
OrderDesc(dao.TSimFlowPool.Columns().Id)
if tenantId := contexts.GetTenantId(ctx); tenantId != "" {
existingPoolMod = existingPoolMod.Where(dao.TSimFlowPool.Columns().TenantId, tenantId)
}
err = existingPoolMod.Scan(&existingPools)
if err != nil {
return gerror.Wrap(err, "获取已有流量池列表失败,请稍后重试!")
}
// 构建已有流量池的 map,key 为 poolPackageCode
existingPoolMap := make(map[int64]*model.TSimFlowPoolListModel)
duplicatePoolIdsMap := make(map[int64][]int64)
for _, pool := range existingPools {
if _, ok := existingPoolMap[pool.PoolPackageCode]; ok {
duplicatePoolIdsMap[pool.PoolPackageCode] = append(duplicatePoolIdsMap[pool.PoolPackageCode], pool.Id)
continue
}
existingPoolMap[pool.PoolPackageCode] = pool
}
// 生成流量池编号前缀:POOL + 毫秒时间戳
flowPoolCodePrefix := fmt.Sprintf("POOL%d", gtime.TimestampMilli())
// 开启事务
err = s.Model(ctx).Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
for i, poolPackageCode := range poolPackageCodes { // [400,300]
now := gtime.Now()
var keepPoolId int64
// 分别收集102套餐编码和104加油包套餐编码
var codesForPackage102 []string
var codesForPackage104 []string
for cardCode, pcCode := range poolPackageMap102 { //{"dx-1g-1y":400,"dx-1g-1y-130":400,"dx-300m-3y":300}池套餐
if pcCode == poolPackageCode {
codesForPackage102 = append(codesForPackage102, cardCode)
}
}
for cardCode, pcCode := range poolPackageMap104 { //{"dx-30g-1y":400}加油包
if pcCode == poolPackageCode {
codesForPackage104 = append(codesForPackage104, cardCode)
}
}
g.Log().Info(ctx, "池套餐编码:", poolPackageCode, " 102套餐列表:", codesForPackage102, " 104加油包列表:", codesForPackage104)
// 调用计算流量池统计数据方法
poolStats, err := service.IotTSimCardInfo().CalculatePoolStatistics(ctx, &model.CalculatePoolStatisticsReq{
DeptId: deptId,
PoolPackageCode: poolPackageCode,
CardCodes102: codesForPackage102,
CardCodes104: codesForPackage104,
})
if err != nil {
return gerror.Wrap(err, "计算流量池统计数据失败,请稍后重试!")
}
// 生成流量池编号
flowPoolCode := fmt.Sprintf("%s%02d", flowPoolCodePrefix, i+1)
// 判断是否存在
// 只存储102套餐编码,不存储104加油包编码
if existingPool, ok := existingPoolMap[poolPackageCode]; ok {
keepPoolId = existingPool.Id
// 存在则更新
g.Log().Info(ctx, "准备更新流量池, poolId:", existingPool.Id, "poolPackageCode:", poolPackageCode)
g.Log().Info(ctx, "更新数据: PoolPackageFlowMb:", poolStats.PoolPackageFlowMb, "AddOnFlowMb:", poolStats.AddOnFlowMb, "TotalFlowMb:", poolStats.TotalFlowMb)
result, err := tx.Model(dao.TSimFlowPool.Table()).WherePri(existingPool.Id).Data(map[string]interface{}{
dao.TSimFlowPool.Columns().FlowPoolCode: flowPoolCode,
dao.TSimFlowPool.Columns().DeptId: deptId,
dao.TSimFlowPool.Columns().PackageCodes: gjson.New(codesForPackage102),
dao.TSimFlowPool.Columns().CardCount: poolStats.TotalCardCount,
dao.TSimFlowPool.Columns().CardFlowMb: poolStats.CardFlowMb,
dao.TSimFlowPool.Columns().PoolPackageFlowMb: poolStats.PoolPackageFlowMb,
dao.TSimFlowPool.Columns().AddOnFlowMb: poolStats.AddOnFlowMb,
dao.TSimFlowPool.Columns().TotalFlowMb: poolStats.TotalFlowMb,
dao.TSimFlowPool.Columns().UsedFlowMb: poolStats.TotalUsedFlowMb,
dao.TSimFlowPool.Columns().RemainFlowMb: poolStats.RemainFlowMb,
dao.TSimFlowPool.Columns().PoolStatus: poolStats.PoolStatus,
}).Update()
if err != nil {
return gerror.Wrap(err, "更新流量池失败,请稍后重试!")
}
affected, _ := result.RowsAffected()
g.Log().Info(ctx, "更新流量池成功, poolId:", existingPool.Id, "affected:", affected, "poolPackageCode:", poolPackageCode, "remainFlowMb:", poolStats.RemainFlowMb)
} else {
// 不存在则插入
result, err := tx.Model(dao.TSimFlowPool.Table()).Data(map[string]interface{}{
dao.TSimFlowPool.Columns().FlowPoolCode: flowPoolCode,
dao.TSimFlowPool.Columns().DeptId: deptId,
dao.TSimFlowPool.Columns().TenantId: contexts.GetTenantId(ctx),
dao.TSimFlowPool.Columns().CreatedDept: contexts.GetDeptId(ctx),
dao.TSimFlowPool.Columns().CreatedBy: contexts.GetUserId(ctx),
dao.TSimFlowPool.Columns().CreatedAt: now,
dao.TSimFlowPool.Columns().UpdatedAt: now,
dao.TSimFlowPool.Columns().PackageCodes: gjson.New(codesForPackage102),
dao.TSimFlowPool.Columns().PoolPackageCode: poolPackageCode,
dao.TSimFlowPool.Columns().CardCount: poolStats.TotalCardCount,
dao.TSimFlowPool.Columns().CardFlowMb: poolStats.CardFlowMb,
dao.TSimFlowPool.Columns().PoolPackageFlowMb: poolStats.PoolPackageFlowMb,
dao.TSimFlowPool.Columns().AddOnFlowMb: poolStats.AddOnFlowMb,
dao.TSimFlowPool.Columns().TotalFlowMb: poolStats.TotalFlowMb,
dao.TSimFlowPool.Columns().UsedFlowMb: poolStats.TotalUsedFlowMb,
dao.TSimFlowPool.Columns().RemainFlowMb: poolStats.RemainFlowMb,
dao.TSimFlowPool.Columns().PoolStatus: poolStats.PoolStatus,
}).Insert()
if err != nil {
return gerror.Wrap(err, "插入流量池失败,请稍后重试!")
}
keepPoolId, _ = result.LastInsertId()
g.Log().Info(ctx, "插入流量池成功, poolPackageCode:", poolPackageCode, "remainFlowMb:", poolStats.RemainFlowMb)
}
if keepPoolId > 0 && len(duplicatePoolIdsMap[poolPackageCode]) > 0 {
_, err = tx.Model(dao.TSimFlowPool.Table()).
WhereIn(dao.TSimFlowPool.Columns().Id, duplicatePoolIdsMap[poolPackageCode]).
Delete()
if err != nil {
return gerror.Wrap(err, "清理重复流量池失败,请稍后重试!")
}
}
if len(codesForPackage102) > 0 {
_, err = tx.Model(dao.TSimCardInfo.Table()).
Where(dao.TSimCardInfo.Columns().DeptId, deptId).
WhereIn(dao.TSimCardInfo.Columns().CurrentPackageCode, codesForPackage102).
Data(g.Map{
dao.TSimCardInfo.Columns().IsFlowPool: 1,
dao.TSimCardInfo.Columns().FlowPoolCode: flowPoolCode,
dao.TSimCardInfo.Columns().UpdatedBy: contexts.GetUserId(ctx),
dao.TSimCardInfo.Columns().UpdatedAt: now,
}).Update()
if err != nil {
return gerror.Wrap(err, "同步流量池卡归属失败,请稍后重试!")
}
}
}
return nil
})
if err != nil {
return err
}
g.Log().Info(ctx, "SyncPoolCard 同步完成, 共处理:", len(poolPackageCodes), "个流量池")
return nil
}
// GetCardsByPackageCodes 根据套餐编码列表获取卡列表
func (s *sIotTSimFlowPool) GetCardsByPackageCodes(ctx context.Context, req *model.GetCardsByPackageCodesParam) (list []*model.TSimCardInfoListModel, totalCount int, err error) {
deptId := contexts.GetDeptId(ctx)
g.Log().Info(ctx, "GetCardsByPackageCodes - deptId:", deptId, "FlowPoolCode:", req.FlowPoolCode)
// 从流量池表查询套餐编码列表和部门ID
type FlowPoolInfo struct {
DeptId int64 `json:"deptId" dc:"部门ID"`
PackageCodes *gjson.Json `json:"packageCodes" dc:"套餐编号列表"`
}
var flowPool FlowPoolInfo
// 先用 FlowPoolCode 查询
err = dao.TSimFlowPool.Ctx(ctx).
Where(dao.TSimFlowPool.Columns().FlowPoolCode, req.FlowPoolCode).
Fields(dao.TSimFlowPool.Columns().DeptId, dao.TSimFlowPool.Columns().PackageCodes).
Scan(&flowPool)
if err != nil || flowPool.PackageCodes == nil {
g.Log().Warning(ctx, "未找到流量池记录, FlowPoolCode:", req.FlowPoolCode, "err:", err)
return
}
g.Log().Info(ctx, "GetCardsByPackageCodes - flowPool.DeptId:", flowPool.DeptId)
// 解析 JSON 为字符串数组
if flowPool.PackageCodes != nil {
var packageCodes []string
_ = flowPool.PackageCodes.Scan(&packageCodes)
req.PackageCodes = packageCodes
g.Log().Info(ctx, "GetCardsByPackageCodes - packageCodes:", req.PackageCodes)
}
// 将流量池的 deptId 放到请求参数中
req.DeptId = flowPool.DeptId
return service.IotTSimCardInfo().GetCardsByPackageCodes(ctx, req)
}