88dfe5c640
iotlcm-openapi:对外 OpenAPI 网关(V3 接口 + 旧版兼容接口)。
- Go module:iotlcm-openapi → git.5iot.com/iotlcm-public/iotlcm-openapi(178 文件)
- 凭据与地址脱敏:JWT signingKey、MySQL DSN、Redis 口令、
RabbitMQ DSN 全部改为 ${*} 占位;私网 IP → 文档地址段
- 7 处真实手机号 → 合法 11 位编造号
- 启动 banner 统一 IOTLCM 方块字 + 社区惯例署名
- 移除内部资产:CI 触发器
- Dockerfile 改用公共基础镜像,移除 GOPRIVATE 内网设置
- README 重写(原为 GoFrame 模板占位文案),补 Apache-2.0 LICENSE,
LF 行尾 + .gitattributes
334 lines
10 KiB
Go
334 lines
10 KiB
Go
package common
|
|
|
|
import (
|
|
"context"
|
|
"strconv"
|
|
"strings"
|
|
|
|
"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/gtime"
|
|
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/consts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/dao"
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/batchmetadata"
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/library/contexts"
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model"
|
|
"git.5iot.com/iotlcm-public/iotlcm-openapi/internal/model/entity"
|
|
)
|
|
|
|
// BuildRenewBatchNo 生成续费类批次号。
|
|
func (s *sCommon) BuildRenewBatchNo(ctx context.Context) (string, error) {
|
|
return s.BuildBatchNo(ctx, consts.BatchNoPrefixRenew)
|
|
}
|
|
|
|
// SubmitRenew 创建卡续费批次并投递到 worker 异步执行。
|
|
func (s *sCommon) SubmitRenew(ctx context.Context, in *model.CommonRenewParam) (*model.CommonRenewRes, error) {
|
|
return s.submitRenew(ctx, in, false, batchmetadata.Bulk)
|
|
}
|
|
|
|
func (s *sCommon) submitRenew(ctx context.Context, in *model.CommonRenewParam, waitForCompletion bool, executionMode int) (*model.CommonRenewRes, error) {
|
|
if in == nil {
|
|
return nil, gerror.New("续费参数不能为空")
|
|
}
|
|
duration := in.DurationYears
|
|
if duration <= 0 {
|
|
duration = in.Validity
|
|
}
|
|
if duration <= 0 {
|
|
return nil, gerror.New("续费有效期必须大于0")
|
|
}
|
|
packageID := strings.TrimSpace(in.PackageId)
|
|
cards, err := normalizeRenewCards(in.Cards)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
tenantID := strings.TrimSpace(contexts.GetTenantId(ctx))
|
|
deptID := contexts.GetAgentDeptId(ctx)
|
|
userID := contexts.GetUserId(ctx)
|
|
if tenantID == "" || deptID <= 0 {
|
|
return nil, gerror.New("当前接口调用方信息不完整")
|
|
}
|
|
if userID <= 0 {
|
|
userID = deptID
|
|
}
|
|
|
|
deptIDs, err := s.loadRenewAccessibleDeptIds(ctx, tenantID, deptID)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
identifiers := make([]cardIdentifier, 0, len(cards))
|
|
for _, card := range cards {
|
|
identifiers = append(identifiers, cardIdentifier{Msisdn: card.Msisdn, Iccid: card.Iccid, Imsi: card.Imsi})
|
|
}
|
|
cardMap, err := s.loadCardsByIdentifiers(ctx, tenantID, deptIDs, identifiers)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
for index := range cards {
|
|
dbCard := cardMap[cardIdentifierKey(identifiers[index])]
|
|
if !cardMatchesIdentifier(dbCard, identifiers[index]) {
|
|
return nil, gerror.New("卡片不存在或无权限")
|
|
}
|
|
cards[index].Iccid = strings.TrimSpace(dbCard.Iccid)
|
|
cards[index].Msisdn = strings.TrimSpace(dbCard.Msisdn)
|
|
cards[index].Imsi = strings.TrimSpace(dbCard.Imsi)
|
|
}
|
|
iccids := make([]string, 0, len(cards))
|
|
for _, card := range cards {
|
|
iccids = append(iccids, card.Iccid)
|
|
}
|
|
|
|
batchNo, err := s.BuildRenewBatchNo(ctx)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
now := gtime.Now()
|
|
remark := strings.TrimSpace(in.Remark)
|
|
batchInfo := gjson.New(map[string]any{
|
|
"durationValue": duration,
|
|
"durationYears": duration,
|
|
"durationMonths": duration,
|
|
"packageId": packageID,
|
|
"paymentMode": "balance",
|
|
|
|
"remark": remark,
|
|
"quantity": len(cards),
|
|
"submittedFrom": "openapi",
|
|
"submittedAt": now.Format("Y-m-d H:i:s"),
|
|
"runtimeResolveRenewDetail": true,
|
|
})
|
|
|
|
err = g.DB().Transaction(ctx, func(ctx context.Context, tx gdb.TX) error {
|
|
main := &entity.BatchMain{
|
|
BatchNo: batchNo,
|
|
BatchType: consts.BatchTypeRenew,
|
|
BatchStatus: consts.BatchStatusNormal,
|
|
BatchAsyncStatus: consts.BatchAsyncStatusPending,
|
|
SourceType: consts.BatchSourceTypeBatchInput,
|
|
DeptId: deptID,
|
|
TotalCount: len(cards),
|
|
ValidCount: len(cards),
|
|
RunResult: consts.BatchRunResultPending,
|
|
BatchInfo: batchInfo,
|
|
Remark: gjson.New(map[string]any{"remark": remark}),
|
|
TenantId: tenantID,
|
|
CreatedDept: deptID,
|
|
CreatedBy: userID,
|
|
CreatedAt: now,
|
|
UpdatedBy: userID,
|
|
UpdatedAt: now,
|
|
}
|
|
if err := s.SaveBatchMain(ctx, tx, main, executionMode); err != nil {
|
|
return err
|
|
}
|
|
details := make([]*entity.BatchDetail, 0, len(cards))
|
|
for i, inputCard := range cards {
|
|
dbCard := cardMap[cardIdentifierKey(cardIdentifier{Msisdn: inputCard.Msisdn, Iccid: inputCard.Iccid, Imsi: inputCard.Imsi})]
|
|
snapshot := renewDetailSnapshot(inputCard, dbCard)
|
|
details = append(details, &entity.BatchDetail{
|
|
BatchNo: batchNo,
|
|
BatchType: consts.BatchTypeRenew,
|
|
DetailStatus: consts.BatchDetailStatusPending,
|
|
BlankOutStatus: consts.BlankOutStatusNo,
|
|
RowNo: i + 1,
|
|
ItemId: inputCard.Iccid,
|
|
Snapshot: snapshot,
|
|
TenantId: tenantID,
|
|
CreatedDept: deptID,
|
|
CreatedBy: userID,
|
|
CreatedAt: now,
|
|
UpdatedBy: userID,
|
|
UpdatedAt: now,
|
|
})
|
|
}
|
|
return s.SaveBatchDetails(ctx, tx, details)
|
|
})
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
message := model.CommonBatchExecuteMessage{BatchNo: batchNo, BatchType: consts.BatchTypeRenew}
|
|
if err := s.Publish(ctx, consts.QueueBatchExecute, message); err != nil {
|
|
return nil, gerror.Wrap(err, "投递续费异步消息失败")
|
|
}
|
|
if waitForCompletion {
|
|
if _, err := s.waitBatchCompletion(ctx, tenantID, deptID, batchNo); err != nil {
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
return &model.CommonRenewRes{
|
|
BatchNo: batchNo,
|
|
BatchType: consts.BatchTypeRenew,
|
|
Total: len(cards),
|
|
}, nil
|
|
}
|
|
|
|
func buildRenewSubmitRemark(ctx context.Context) string {
|
|
agent := contexts.GetAgentUser(ctx)
|
|
if agent == nil {
|
|
return "OpenAPI续费调用"
|
|
}
|
|
deptName := strings.TrimSpace(agent.DeptName)
|
|
deptKey := strings.TrimSpace(agent.DeptKey)
|
|
switch {
|
|
case deptName != "" && deptKey != "":
|
|
return "OpenAPI续费调用方:" + deptName + "(" + deptKey + ")"
|
|
case deptName != "":
|
|
return "OpenAPI续费调用方:" + deptName
|
|
case deptKey != "":
|
|
return "OpenAPI续费调用方:" + deptKey
|
|
default:
|
|
return "OpenAPI续费调用"
|
|
}
|
|
}
|
|
|
|
func normalizeRenewCards(input []model.CommonRenewCard) ([]model.CommonRenewCard, error) {
|
|
if len(input) == 0 {
|
|
return nil, gerror.New("续费卡不能为空")
|
|
}
|
|
seen := make(map[string]struct{}, len(input))
|
|
cards := make([]model.CommonRenewCard, 0, len(input))
|
|
for _, item := range input {
|
|
item.Iccid = strings.TrimSpace(item.Iccid)
|
|
item.Msisdn = strings.TrimSpace(item.Msisdn)
|
|
item.Imsi = strings.TrimSpace(item.Imsi)
|
|
item.PackageId = strings.TrimSpace(item.PackageId)
|
|
identifier := cardIdentifier{Msisdn: item.Msisdn, Iccid: item.Iccid, Imsi: item.Imsi}
|
|
if err := validateCardIdentifier(identifier); err != nil {
|
|
return nil, err
|
|
}
|
|
key := cardIdentifierKey(identifier)
|
|
if _, ok := seen[key]; ok {
|
|
return nil, gerror.Newf("卡片标识重复:%s", key)
|
|
}
|
|
seen[key] = struct{}{}
|
|
cards = append(cards, item)
|
|
}
|
|
return cards, nil
|
|
}
|
|
func (s *sCommon) loadRenewAccessibleDeptIds(ctx context.Context, tenantID string, deptID int64) ([]int64, error) {
|
|
deptIDs := []int64{deptID}
|
|
columns := dao.SysDept.Columns()
|
|
array, err := dao.SysDept.Ctx(ctx).
|
|
Fields(columns.DeptId).
|
|
Where(columns.TenantId, tenantID).
|
|
WhereNull(columns.DeletedAt).
|
|
Where("dept_id = ? OR ancestors LIKE ?", deptID, "%,"+strconv.FormatInt(deptID, 10)+",%").
|
|
Array()
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "查询续费数据权限失败")
|
|
}
|
|
if len(array) == 0 {
|
|
return deptIDs, nil
|
|
}
|
|
seen := map[int64]struct{}{}
|
|
deptIDs = deptIDs[:0]
|
|
for _, item := range array {
|
|
id := item.Int64()
|
|
if id <= 0 {
|
|
continue
|
|
}
|
|
if _, ok := seen[id]; ok {
|
|
continue
|
|
}
|
|
seen[id] = struct{}{}
|
|
deptIDs = append(deptIDs, id)
|
|
}
|
|
if len(deptIDs) == 0 {
|
|
deptIDs = append(deptIDs, deptID)
|
|
}
|
|
return deptIDs, nil
|
|
}
|
|
|
|
func (s *sCommon) loadRenewCards(ctx context.Context, tenantID string, deptIDs []int64, iccids []string) (map[string]*entity.TSimCardInfo, error) {
|
|
cardMap := make(map[string]*entity.TSimCardInfo, len(iccids))
|
|
if len(iccids) == 0 {
|
|
return cardMap, nil
|
|
}
|
|
columns := dao.TSimCardInfo.Columns()
|
|
var rows []*entity.TSimCardInfo
|
|
err := dao.TSimCardInfo.Ctx(ctx).
|
|
Where(columns.TenantId, tenantID).
|
|
WhereIn(columns.DeptId, deptIDs).
|
|
WhereIn(columns.Iccid, iccids).
|
|
Scan(&rows)
|
|
if err != nil {
|
|
return nil, gerror.Wrap(err, "查询续费卡失败")
|
|
}
|
|
for _, row := range rows {
|
|
if row == nil {
|
|
continue
|
|
}
|
|
row.Iccid = strings.TrimSpace(row.Iccid)
|
|
if row.Iccid != "" {
|
|
cardMap[row.Iccid] = row
|
|
}
|
|
}
|
|
return cardMap, nil
|
|
}
|
|
|
|
func renewDetailSnapshot(input model.CommonRenewCard, card *entity.TSimCardInfo) *gjson.Json {
|
|
data := map[string]any{
|
|
"iccid": input.Iccid,
|
|
"targetCode": input.Iccid,
|
|
}
|
|
if input.Msisdn != "" {
|
|
data["msisdn"] = input.Msisdn
|
|
}
|
|
if input.Imsi != "" {
|
|
data["imsi"] = input.Imsi
|
|
}
|
|
if input.PackageId != "" {
|
|
data["packageId"] = input.PackageId
|
|
data["packageCode"] = input.PackageId
|
|
}
|
|
if card != nil {
|
|
if input.Msisdn == "" {
|
|
data["msisdn"] = strings.TrimSpace(card.Msisdn)
|
|
}
|
|
if input.Imsi == "" {
|
|
data["imsi"] = strings.TrimSpace(card.Imsi)
|
|
}
|
|
if input.PackageId == "" {
|
|
data["packageCode"] = strings.TrimSpace(card.CurrentPackageCode)
|
|
}
|
|
data["currentPackageCode"] = strings.TrimSpace(card.CurrentPackageCode)
|
|
data["outboundBatchNo"] = strings.TrimSpace(card.OutboundBatchNo)
|
|
}
|
|
return gjson.New(data)
|
|
}
|
|
|
|
func (s *sCommon) SubmitSingleRenew(ctx context.Context, card model.CommonRenewCard, appID, packageID, remark string, validity int) (*model.CommonSingleRenewRes, error) {
|
|
card.PackageId = packageID
|
|
batch, err := s.submitRenew(ctx, &model.CommonRenewParam{
|
|
AppId: appID, PackageId: packageID, Validity: validity, DurationYears: validity, Remark: remark,
|
|
Cards: []model.CommonRenewCard{card},
|
|
}, true, batchmetadata.Single)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
return s.loadSingleRenewResult(ctx, batch.BatchNo, packageID, validity)
|
|
}
|
|
|
|
func (s *sCommon) SubmitBatchRenew(ctx context.Context, appID string, cards []model.CommonRenewCard, iccids []string, packageID, remark string, validity int) (*model.CommonRenewRes, error) {
|
|
if len(cards) == 0 {
|
|
cards = make([]model.CommonRenewCard, 0, len(iccids))
|
|
for _, iccid := range iccids {
|
|
cards = append(cards, model.CommonRenewCard{Iccid: iccid, PackageId: packageID})
|
|
}
|
|
}
|
|
for index := range cards {
|
|
if cards[index].PackageId == "" {
|
|
cards[index].PackageId = packageID
|
|
}
|
|
}
|
|
return s.SubmitRenew(ctx, &model.CommonRenewParam{
|
|
AppId: appID, PackageId: packageID, Validity: validity, DurationYears: validity, Remark: remark, Cards: cards,
|
|
})
|
|
}
|