Files
iotlcm/server/internal/logic/iot/simcardbatch/addpackage/prepare.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

354 lines
10 KiB
Go

package addpackage
import (
"context"
"fmt"
"strings"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/dao"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/batchimport"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/library/xgorm/handler"
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/os/gtime"
)
type cardTarget struct {
Iccid string `orm:"iccid"`
CurrentPackageCode string `orm:"current_package_code"`
PackageStatus int `orm:"package_status"`
}
type cardLatestBindTarget struct {
Iccid string `orm:"iccid"`
PackageCode string `orm:"package_code"`
EffectiveStatus int `orm:"effective_status"`
ExpectedExpireTime *gtime.Time `orm:"expected_expire_time"`
}
type poolTarget struct {
FlowPoolCode string `orm:"flow_pool_code"`
PoolPackageCode int64 `orm:"pool_package_code"`
PoolStatus int `orm:"pool_status"`
}
func (p *Processor) PrepareRows(ctx context.Context, rows []batchimport.Row) error {
if p.config.Operation == OperationPool {
return p.preparePoolRows(ctx, rows)
}
return p.prepareCardRows(ctx, rows)
}
func (p *Processor) prepareCardRows(ctx context.Context, rows []batchimport.Row) error {
typedRows, targetCodes, err := collectTypedRows(rows)
if err != nil {
return err
}
if len(targetCodes) == 0 {
return nil
}
var cards []*cardTarget
cardColumns := dao.TSimCardInfo.Columns()
if err = handler.Model(dao.TSimCardInfo.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
Fields(cardColumns.Iccid, cardColumns.CurrentPackageCode, cardColumns.PackageStatus, cardColumns.TenantId, cardColumns.DeptId, cardColumns.CreatedDept, cardColumns.CreatedBy).
WhereIn(cardColumns.Iccid, targetCodes).
Scan(&cards); err != nil {
return gerror.Wrap(err, "查询加油包卡信息失败")
}
cardMap := make(map[string]*cardTarget, len(cards))
for _, card := range cards {
if card == nil {
continue
}
iccid := strings.TrimSpace(card.Iccid)
if iccid == "" {
continue
}
cardMap[iccid] = card
}
latestBinds, err := p.loadLatestCardBaseBinds(ctx, targetCodes)
if err != nil {
return err
}
latestBindMap := make(map[string]*cardLatestBindTarget, len(latestBinds))
for _, bind := range latestBinds {
if bind == nil {
continue
}
iccid := strings.TrimSpace(bind.Iccid)
if iccid == "" || latestBindMap[iccid] != nil {
continue
}
latestBindMap[iccid] = bind
}
basePackageCodes := make([]string, 0, len(latestBindMap))
for _, bind := range latestBindMap {
if bind == nil {
continue
}
basePackageCodes = append(basePackageCodes, bind.PackageCode)
}
packages, err := p.loadPackageMap(ctx, append(basePackageCodes, rowPackageCodes(typedRows)...))
if err != nil {
return err
}
now := gtime.Now()
for _, row := range typedRows {
card := cardMap[row.TargetCode]
if card == nil {
row.AddErrorReason("ICCID不存在或无权限")
continue
}
latestBind := latestBindMap[row.TargetCode]
if latestBind == nil || strings.TrimSpace(latestBind.PackageCode) == "" {
row.AddErrorReason("当前卡订购记录不存在")
continue
}
row.Authorized = true
if card.PackageStatus != consts.PackageStatusEffective {
row.AddErrorReason("当前卡套餐未生效,不能订购加油包")
continue
}
if bindExpired(latestBind, now) {
row.AddErrorReason("当前卡基础套餐已到期,不能订购加油包")
continue
}
if err := p.validateTargetPackage(row, packages, 103); err != nil {
row.AddErrorReason(err.Error())
continue
}
basePackage := packages[strings.TrimSpace(latestBind.PackageCode)]
if basePackage == nil {
row.AddErrorReason("当前卡基础套餐不存在")
continue
}
if !containsString(packageCodesFromJSON(basePackage.TopupPackageCodes), row.PackageCode) {
row.AddErrorReason("当前卡基础套餐不允许订购该加油包")
continue
}
}
return rowsValidationError(rows, "加油包卡信息校验失败")
}
func (p *Processor) preparePoolRows(ctx context.Context, rows []batchimport.Row) error {
typedRows, targetCodes, err := collectTypedRows(rows)
if err != nil {
return err
}
if len(targetCodes) == 0 {
return nil
}
var pools []*poolTarget
poolColumns := dao.TSimFlowPool.Columns()
if err = handler.Model(dao.TSimFlowPool.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
Fields(poolColumns.FlowPoolCode, poolColumns.PoolPackageCode, poolColumns.PoolStatus, poolColumns.TenantId, poolColumns.DeptId, poolColumns.CreatedDept, poolColumns.CreatedBy).
WhereIn(poolColumns.FlowPoolCode, targetCodes).
Scan(&pools); err != nil {
return gerror.Wrap(err, "查询流量池信息失败")
}
poolMap := make(map[string]*poolTarget, len(pools))
for _, pool := range pools {
if pool == nil {
continue
}
code := strings.TrimSpace(pool.FlowPoolCode)
poolMap[code] = pool
}
packages, err := p.loadPackageMap(ctx, rowPackageCodes(typedRows))
if err != nil {
return err
}
for _, row := range typedRows {
pool := poolMap[row.TargetCode]
if pool == nil {
row.AddErrorReason("流量池编号不存在或无权限")
continue
}
row.Authorized = true
if pool.PoolStatus != 0 {
row.AddErrorReason("流量池状态异常,不能订购加油包")
continue
}
if err := p.validateTargetPackage(row, packages, 104); err != nil {
row.AddErrorReason(err.Error())
continue
}
targetPackage := packages[row.PackageCode]
if targetPackage == nil {
row.AddErrorReason("加油包不存在")
continue
}
if targetPackage.PoolPackageCode != pool.PoolPackageCode {
row.AddErrorReason("加油包不属于当前流量池套餐")
continue
}
}
return rowsValidationError(rows, "流量池加油包校验失败")
}
func (p *Processor) loadLatestCardBaseBinds(ctx context.Context, iccids []string) ([]*cardLatestBindTarget, error) {
iccids = uniqueStrings(iccids)
if len(iccids) == 0 {
return nil, nil
}
var binds []*cardLatestBindTarget
if err := handler.Model(dao.TSimCardBindPkg.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: true}).
Fields(
dao.TSimCardBindPkg.Columns().Iccid,
dao.TSimCardBindPkg.Columns().PackageCode,
dao.TSimCardBindPkg.Columns().EffectiveStatus,
dao.TSimCardBindPkg.Columns().ExpectedExpireTime,
dao.TSimCardBindPkg.Columns().TenantId,
dao.TSimCardBindPkg.Columns().DeptId,
dao.TSimCardBindPkg.Columns().CreatedDept,
dao.TSimCardBindPkg.Columns().CreatedBy,
).
WhereIn(dao.TSimCardBindPkg.Columns().Iccid, iccids).
WhereIn(dao.TSimCardBindPkg.Columns().PackageType, []int{101, 102}).
Where(dao.TSimCardBindPkg.Columns().EffectiveStatus, consts.EffectiveStatusEffective).
OrderDesc(dao.TSimCardBindPkg.Columns().ExpectedExpireTime).
OrderDesc(dao.TSimCardBindPkg.Columns().Id).
Scan(&binds); err != nil {
return nil, gerror.Wrap(err, "查询卡最新订购记录失败")
}
return binds, nil
}
func bindExpired(bind *cardLatestBindTarget, now *gtime.Time) bool {
if bind == nil {
return true
}
if bind.EffectiveStatus == consts.EffectiveStatusExpired {
return true
}
if bind.ExpectedExpireTime == nil || now == nil {
return false
}
return !bind.ExpectedExpireTime.After(now)
}
func (p *Processor) validateTargetPackage(row *Row, packages map[string]*entity.TSimFlowPackageInfo, packageType int) error {
if row == nil {
return nil
}
pkg := packages[row.PackageCode]
if pkg == nil {
return gerror.New("加油包不存在")
}
if pkg.Enabled != 1 {
return gerror.New("加油包未启用")
}
if pkg.PackageType != packageType {
return gerror.Newf("加油包类型错误,应为%d", packageType)
}
return nil
}
func (p *Processor) loadPackageMap(ctx context.Context, codes []string) (map[string]*entity.TSimFlowPackageInfo, error) {
out := make(map[string]*entity.TSimFlowPackageInfo)
codes = uniqueStrings(codes)
if len(codes) == 0 {
return out, nil
}
var packages []*entity.TSimFlowPackageInfo
columns := dao.TSimFlowPackageInfo.Columns()
if err := handler.Model(dao.TSimFlowPackageInfo.Ctx(ctx), &handler.Option{FilterTenant: true, FilterAuth: false}).
WhereIn(columns.PackageCode, codes).
Scan(&packages); err != nil {
return nil, gerror.Wrap(err, "查询加油包套餐信息失败")
}
for _, pkg := range packages {
if pkg == nil {
continue
}
out[strings.TrimSpace(pkg.PackageCode)] = pkg
}
return out, nil
}
func collectTypedRows(rows []batchimport.Row) ([]*Row, []string, error) {
typedRows := make([]*Row, 0, len(rows))
targetCodes := make([]string, 0, len(rows))
for _, item := range rows {
row, ok := item.(*Row)
if !ok || row == nil {
return nil, nil, gerror.New("加油包批次行类型错误")
}
typedRows = append(typedRows, row)
if row.TargetCode != "" {
targetCodes = append(targetCodes, row.TargetCode)
}
}
return typedRows, uniqueStrings(targetCodes), nil
}
func rowPackageCodes(rows []*Row) []string {
codes := make([]string, 0, len(rows))
for _, row := range rows {
if row != nil {
codes = append(codes, row.PackageCode)
}
}
return codes
}
func packageCodesFromJSON(jsonValue *gjson.Json) []string {
if jsonValue == nil || jsonValue.IsNil() {
return nil
}
values := jsonValue.Interfaces()
out := make([]string, 0, len(values))
for _, raw := range values {
code := strings.TrimSpace(fmt.Sprint(raw))
if code != "" {
out = append(out, code)
}
}
return uniqueStrings(out)
}
func containsString(values []string, target string) bool {
target = strings.TrimSpace(target)
if target == "" {
return false
}
for _, value := range values {
if strings.TrimSpace(value) == target {
return true
}
}
return false
}
func uniqueStrings(in []string) []string {
if len(in) == 0 {
return nil
}
seen := make(map[string]struct{}, len(in))
out := make([]string, 0, len(in))
for _, value := range in {
value = strings.TrimSpace(value)
if value == "" {
continue
}
if _, ok := seen[value]; ok {
continue
}
seen[value] = struct{}{}
out = append(out, value)
}
return out
}