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 已说明。
This commit is contained in:
@@ -0,0 +1,492 @@
|
||||
package iot
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"time"
|
||||
|
||||
"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/contexts"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model"
|
||||
"git.5iot.com/iotlcm-public/iotlcm-admin-svr/internal/model/entity"
|
||||
|
||||
"github.com/gogf/gf/v2/database/gdb"
|
||||
"github.com/gogf/gf/v2/encoding/gjson"
|
||||
"github.com/gogf/gf/v2/errors/gerror"
|
||||
)
|
||||
|
||||
type stockBusinessFilter struct {
|
||||
ChanName string
|
||||
CardType string
|
||||
UpPkgKeyword string
|
||||
AgreementExpiryMonths int
|
||||
}
|
||||
|
||||
type stockBatchBusinessFields struct {
|
||||
ChanKey string
|
||||
CardType string
|
||||
UpPkgCode string
|
||||
UpPkgName string
|
||||
AgreementExpiryDate string
|
||||
}
|
||||
|
||||
func applyStockBusinessFilters(ctx context.Context, mod *gdb.Model, filter stockBusinessFilter) *gdb.Model {
|
||||
if mod == nil {
|
||||
return mod
|
||||
}
|
||||
|
||||
stockTable := dao.TSimCardStock.Table()
|
||||
stockColumns := dao.TSimCardStock.Columns()
|
||||
|
||||
if value := strings.TrimSpace(filter.ChanName); value != "" {
|
||||
channelColumns := dao.TSimChannels.Columns()
|
||||
like := "%" + value + "%"
|
||||
mod = mod.Where(
|
||||
fmt.Sprintf(
|
||||
"EXISTS (SELECT 1 FROM `%s` c WHERE c.`%s` = `%s`.`%s` AND c.`%s` = ? AND (c.`%s` LIKE ? OR c.`%s` LIKE ?))",
|
||||
dao.TSimChannels.Table(),
|
||||
channelColumns.ChanKey,
|
||||
stockTable,
|
||||
stockColumns.ChanKey,
|
||||
channelColumns.TenantId,
|
||||
channelColumns.ChanName,
|
||||
channelColumns.ChanKey,
|
||||
),
|
||||
contexts.GetTenantId(ctx),
|
||||
like,
|
||||
like,
|
||||
)
|
||||
}
|
||||
|
||||
if value := strings.TrimSpace(filter.CardType); value != "" {
|
||||
cardTypeExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "cardType")
|
||||
mod = mod.Where(stockInboundBatchExistsSQL(cardTypeExpr+" = ?"), consts.BatchTypeInbound, value)
|
||||
}
|
||||
|
||||
if value := strings.TrimSpace(filter.UpPkgKeyword); value != "" {
|
||||
upstreamColumns := dao.TUpstreamPackage.Columns()
|
||||
upPkgExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "upPkgCode")
|
||||
like := "%" + value + "%"
|
||||
mod = mod.Where(
|
||||
stockInboundBatchJoinExistsSQL(
|
||||
fmt.Sprintf("LEFT JOIN `%s` up ON up.`%s` = %s", dao.TUpstreamPackage.Table(), upstreamColumns.UpPkgCode, upPkgExpr),
|
||||
fmt.Sprintf("(up.`%s` LIKE ? OR %s LIKE ?)", upstreamColumns.UpPkgName, upPkgExpr),
|
||||
),
|
||||
consts.BatchTypeInbound,
|
||||
like,
|
||||
like,
|
||||
)
|
||||
}
|
||||
|
||||
if filter.AgreementExpiryMonths > 0 {
|
||||
months := filter.AgreementExpiryMonths
|
||||
if months != 1 && months != 3 && months != 6 {
|
||||
months = 6
|
||||
}
|
||||
now := time.Now()
|
||||
start := now.Format("2006-01-02")
|
||||
end := now.AddDate(0, months, 0).Format("2006-01-02")
|
||||
agreementExpr := batchInfoTextSQL("bm.`"+dao.BatchMain.Columns().BatchInfo+"`", "agreementExpiryDate")
|
||||
mod = mod.Where(
|
||||
stockInboundBatchExistsSQL("DATE("+agreementExpr+") BETWEEN ? AND ?"),
|
||||
consts.BatchTypeInbound,
|
||||
start,
|
||||
end,
|
||||
)
|
||||
}
|
||||
|
||||
return mod
|
||||
}
|
||||
|
||||
func stockInboundBatchExistsSQL(extra string) string {
|
||||
return stockInboundBatchJoinExistsSQL("", extra)
|
||||
}
|
||||
|
||||
func stockInboundBatchJoinExistsSQL(joinSQL, extra string) string {
|
||||
stockTable := dao.TSimCardStock.Table()
|
||||
stockColumns := dao.TSimCardStock.Columns()
|
||||
batchColumns := dao.BatchMain.Columns()
|
||||
joinPart := ""
|
||||
if strings.TrimSpace(joinSQL) != "" {
|
||||
joinPart = " " + joinSQL
|
||||
}
|
||||
return fmt.Sprintf(
|
||||
"EXISTS (SELECT 1 FROM `%s` bm%s WHERE bm.`%s` = `%s`.`%s` AND bm.`%s` = `%s`.`%s` AND bm.`%s` = ? AND %s)",
|
||||
dao.BatchMain.Table(),
|
||||
joinPart,
|
||||
batchColumns.BatchNo,
|
||||
stockTable,
|
||||
stockColumns.BatchNo,
|
||||
batchColumns.TenantId,
|
||||
stockTable,
|
||||
stockColumns.TenantId,
|
||||
batchColumns.BatchType,
|
||||
extra,
|
||||
)
|
||||
}
|
||||
|
||||
func batchInfoTextSQL(columnExpr, field string) string {
|
||||
return fmt.Sprintf(
|
||||
"COALESCE(NULLIF(JSON_UNQUOTE(JSON_EXTRACT(%s, '$.inboundOptions.%s')), ''), JSON_UNQUOTE(JSON_EXTRACT(%s, '$.%s')))",
|
||||
columnExpr,
|
||||
field,
|
||||
columnExpr,
|
||||
field,
|
||||
)
|
||||
}
|
||||
|
||||
func enrichStockListBusinessFields(ctx context.Context, list []*model.TSimCardStockListModel) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
batchNos := make([]string, 0, len(list))
|
||||
chanKeys := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
||||
}
|
||||
|
||||
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fields := range detailMap {
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
||||
}
|
||||
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
||||
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
||||
item.CardType = fields.CardType
|
||||
item.UpPkgCode = fields.UpPkgCode
|
||||
item.UpPkgName = fields.UpPkgName
|
||||
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enrichOutboundBatchBusinessFields(ctx context.Context, list []*model.TSimCardOutboundInboundBatchListItem) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
batchNos := make([]string, 0, len(list))
|
||||
chanKeys := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
||||
}
|
||||
|
||||
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fields := range detailMap {
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
||||
}
|
||||
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
||||
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
||||
item.CardType = fields.CardType
|
||||
item.UpPkgCode = fields.UpPkgCode
|
||||
item.UpPkgName = fields.UpPkgName
|
||||
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func enrichOutboundBatchCardBusinessFields(ctx context.Context, list []*model.TSimCardOutboundInboundBatchCardItem) error {
|
||||
if len(list) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
batchNos := make([]string, 0, len(list))
|
||||
chanKeys := make([]string, 0, len(list))
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
batchNos = appendUniqueNonBlank(batchNos, item.BatchNo)
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, item.ChanKey)
|
||||
}
|
||||
|
||||
batchMap, err := loadStockBatchBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
detailMap, err := loadStockDetailBusinessFields(ctx, batchNos)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, fields := range detailMap {
|
||||
chanKeys = appendUniqueNonBlank(chanKeys, fields.ChanKey)
|
||||
}
|
||||
channelMap, err := loadStockChannelNames(ctx, chanKeys)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
for _, item := range list {
|
||||
if item == nil {
|
||||
continue
|
||||
}
|
||||
fields := mergeStockBusinessFields(batchMap[item.BatchNo], detailMap[item.BatchNo])
|
||||
item.ChanName = channelMap[firstNonBlankString(item.ChanKey, fields.ChanKey)]
|
||||
item.CardType = fields.CardType
|
||||
item.UpPkgCode = fields.UpPkgCode
|
||||
item.UpPkgName = fields.UpPkgName
|
||||
item.AgreementExpiryDate = fields.AgreementExpiryDate
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func loadStockBatchBusinessFields(ctx context.Context, batchNos []string) (map[string]stockBatchBusinessFields, error) {
|
||||
result := make(map[string]stockBatchBusinessFields)
|
||||
batchNos = uniqueNonBlank(batchNos)
|
||||
if len(batchNos) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var batches []*entity.BatchMain
|
||||
columns := dao.BatchMain.Columns()
|
||||
if err := dao.BatchMain.Ctx(ctx).
|
||||
Fields(columns.BatchNo, columns.BatchInfo).
|
||||
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(columns.BatchType, consts.BatchTypeInbound).
|
||||
WhereIn(columns.BatchNo, batchNos).
|
||||
Scan(&batches); err != nil {
|
||||
return nil, gerror.Wrap(err, "补充入库批次业务字段失败")
|
||||
}
|
||||
|
||||
upPkgCodes := make([]string, 0, len(batches))
|
||||
for _, batch := range batches {
|
||||
if batch == nil {
|
||||
continue
|
||||
}
|
||||
fields := stockBatchBusinessFields{
|
||||
ChanKey: batchInfoString(batch.BatchInfo, "chanKey"),
|
||||
CardType: batchInfoString(batch.BatchInfo, "cardType"),
|
||||
UpPkgCode: batchInfoString(batch.BatchInfo, "upPkgCode"),
|
||||
AgreementExpiryDate: batchInfoString(batch.BatchInfo, "agreementExpiryDate"),
|
||||
}
|
||||
result[batch.BatchNo] = fields
|
||||
upPkgCodes = appendUniqueNonBlank(upPkgCodes, fields.UpPkgCode)
|
||||
}
|
||||
|
||||
upPkgNames, err := loadStockUpstreamPackageNames(ctx, upPkgCodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for batchNo, fields := range result {
|
||||
fields.UpPkgName = upPkgNames[fields.UpPkgCode]
|
||||
if fields.UpPkgName == "" {
|
||||
fields.UpPkgName = fields.UpPkgCode
|
||||
}
|
||||
result[batchNo] = fields
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadStockDetailBusinessFields(ctx context.Context, batchNos []string) (map[string]stockBatchBusinessFields, error) {
|
||||
result := make(map[string]stockBatchBusinessFields)
|
||||
batchNos = uniqueNonBlank(batchNos)
|
||||
if len(batchNos) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
var details []*entity.BatchDetail
|
||||
columns := dao.BatchDetail.Columns()
|
||||
if err := dao.BatchDetail.Ctx(ctx).
|
||||
Fields(columns.BatchNo, columns.Snapshot).
|
||||
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
||||
Where(columns.BatchType, consts.BatchTypeInbound).
|
||||
Where(columns.DetailStatus, consts.BatchDetailStatusSuccess).
|
||||
WhereIn(columns.BatchNo, batchNos).
|
||||
OrderAsc(columns.RowNo).
|
||||
Scan(&details); err != nil {
|
||||
return nil, gerror.Wrap(err, "load inbound batch detail business fields failed")
|
||||
}
|
||||
|
||||
upPkgCodes := make([]string, 0, len(details))
|
||||
for _, detail := range details {
|
||||
if detail == nil || detail.Snapshot == nil {
|
||||
continue
|
||||
}
|
||||
current := result[detail.BatchNo]
|
||||
fields := stockSnapshotBusinessFields(detail.Snapshot)
|
||||
current = mergeStockBusinessFields(current, fields)
|
||||
result[detail.BatchNo] = current
|
||||
upPkgCodes = appendUniqueNonBlank(upPkgCodes, current.UpPkgCode)
|
||||
}
|
||||
|
||||
upPkgNames, err := loadStockUpstreamPackageNames(ctx, upPkgCodes)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
for batchNo, fields := range result {
|
||||
fields.UpPkgName = upPkgNames[fields.UpPkgCode]
|
||||
if fields.UpPkgName == "" {
|
||||
fields.UpPkgName = fields.UpPkgCode
|
||||
}
|
||||
result[batchNo] = fields
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func stockSnapshotBusinessFields(snapshot *gjson.Json) stockBatchBusinessFields {
|
||||
if snapshot == nil {
|
||||
return stockBatchBusinessFields{}
|
||||
}
|
||||
return stockBatchBusinessFields{
|
||||
ChanKey: snapshotString(snapshot, "chanKey", "rawFields.chanKey"),
|
||||
CardType: snapshotString(snapshot, "cardType", "rawFields.cardType"),
|
||||
UpPkgCode: snapshotString(snapshot, "upPkgCode", "rawFields.upPkgCode"),
|
||||
AgreementExpiryDate: snapshotString(snapshot, "agreementExpiryDate", "rawFields.agreementExpiryDate"),
|
||||
}
|
||||
}
|
||||
|
||||
func snapshotString(snapshot *gjson.Json, paths ...string) string {
|
||||
for _, path := range paths {
|
||||
if value := strings.TrimSpace(snapshot.Get(path).String()); value != "" {
|
||||
return value
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func mergeStockBusinessFields(primary, fallback stockBatchBusinessFields) stockBatchBusinessFields {
|
||||
return stockBatchBusinessFields{
|
||||
ChanKey: firstNonBlankString(primary.ChanKey, fallback.ChanKey),
|
||||
CardType: firstNonBlankString(primary.CardType, fallback.CardType),
|
||||
UpPkgCode: firstNonBlankString(primary.UpPkgCode, fallback.UpPkgCode),
|
||||
UpPkgName: firstNonBlankString(primary.UpPkgName, fallback.UpPkgName),
|
||||
AgreementExpiryDate: firstNonBlankString(primary.AgreementExpiryDate, fallback.AgreementExpiryDate),
|
||||
}
|
||||
}
|
||||
|
||||
func firstNonBlankString(values ...string) string {
|
||||
for _, value := range values {
|
||||
if trimmed := strings.TrimSpace(value); trimmed != "" {
|
||||
return trimmed
|
||||
}
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func loadStockChannelNames(ctx context.Context, chanKeys []string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
chanKeys = uniqueNonBlank(chanKeys)
|
||||
if len(chanKeys) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type channelRow struct {
|
||||
ChanKey string `orm:"chan_key"`
|
||||
ChanName string `orm:"chan_name"`
|
||||
}
|
||||
var rows []*channelRow
|
||||
columns := dao.TSimChannels.Columns()
|
||||
if err := dao.TSimChannels.Ctx(ctx).
|
||||
Fields(columns.ChanKey, columns.ChanName).
|
||||
Where(columns.TenantId, contexts.GetTenantId(ctx)).
|
||||
WhereIn(columns.ChanKey, chanKeys).
|
||||
Scan(&rows); err != nil {
|
||||
return nil, gerror.Wrap(err, "补充通道名称失败")
|
||||
}
|
||||
for _, row := range rows {
|
||||
if row != nil {
|
||||
result[row.ChanKey] = row.ChanName
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func loadStockUpstreamPackageNames(ctx context.Context, upPkgCodes []string) (map[string]string, error) {
|
||||
result := make(map[string]string)
|
||||
upPkgCodes = uniqueNonBlank(upPkgCodes)
|
||||
if len(upPkgCodes) == 0 {
|
||||
return result, nil
|
||||
}
|
||||
|
||||
type upstreamPackageRow struct {
|
||||
UpPkgCode string `orm:"up_pkg_code"`
|
||||
UpPkgName string `orm:"up_pkg_name"`
|
||||
}
|
||||
var rows []*upstreamPackageRow
|
||||
columns := dao.TUpstreamPackage.Columns()
|
||||
if err := dao.TUpstreamPackage.Ctx(ctx).
|
||||
Fields(columns.UpPkgCode, columns.UpPkgName).
|
||||
WhereIn(columns.UpPkgCode, upPkgCodes).
|
||||
Scan(&rows); err != nil {
|
||||
return nil, gerror.Wrap(err, "补充上游套餐名称失败")
|
||||
}
|
||||
for _, row := range rows {
|
||||
if row != nil {
|
||||
result[row.UpPkgCode] = row.UpPkgName
|
||||
}
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
||||
func batchInfoString(info *gjson.Json, field string) string {
|
||||
if info == nil {
|
||||
return ""
|
||||
}
|
||||
if value := strings.TrimSpace(info.Get("inboundOptions." + field).String()); value != "" {
|
||||
return value
|
||||
}
|
||||
return strings.TrimSpace(info.Get(field).String())
|
||||
}
|
||||
|
||||
func appendUniqueNonBlank(values []string, value string) []string {
|
||||
value = strings.TrimSpace(value)
|
||||
if value == "" {
|
||||
return values
|
||||
}
|
||||
for _, item := range values {
|
||||
if item == value {
|
||||
return values
|
||||
}
|
||||
}
|
||||
return append(values, value)
|
||||
}
|
||||
|
||||
func uniqueNonBlank(values []string) []string {
|
||||
result := make([]string, 0, len(values))
|
||||
for _, value := range values {
|
||||
result = appendUniqueNonBlank(result, value)
|
||||
}
|
||||
return result
|
||||
}
|
||||
Reference in New Issue
Block a user