sync: 同步 v1.0.4 开源代码并脱敏

This commit is contained in:
MiFaZhan
2026-10-29 14:52:43 +08:00
parent 32e4316ee4
commit 6893e21f07
136 changed files with 9851 additions and 436 deletions
+1 -1
View File
@@ -46,7 +46,7 @@ func (r *Repository) loadParentTaskTx(ctx context.Context, tx gdb.TX, parent *en
continue
}
detail := &Detail{OriginalSnapshot: batchDetail.Snapshot, ID: batchDetail.Id, BatchNo: batchDetail.BatchNo, RowNo: batchDetail.RowNo, DetailStatus: batchDetail.DetailStatus, ICCID: strings.TrimSpace(batchDetail.ItemId), Reason: strings.TrimSpace(batchDetail.ErrorReason)}
if detail.DetailStatus != consts.BatchDetailStatusSuccess && detail.Reason == "" {
if detail.DetailStatus != consts.BatchDetailStatusSuccess && detail.DetailStatus != consts.BatchDetailStatusRunning && detail.Reason == "" {
detail.Reason = "子批次明细尚未全部验证成功"
}
if snapshot := batchDetail.Snapshot; snapshot != nil {
@@ -2,19 +2,19 @@ package allocate
import (
"context"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
_ "github.com/gogf/gf/contrib/drivers/sqlite/v2"
"github.com/gogf/gf/v2/database/gdb"
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/os/gtime"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/do"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/model/entity"
"testing"
)
func TestPersistedAllocateDetailsRequireExactBusinessResult(t *testing.T) {
task := &Task{Batch: Batch{BatchNo: "ALC-child", TenantID: "000000", CreatedBy: 133, CreatedDept: 175}}
detail := &Detail{ID: 1, ICCID: "89860401790000277308", TargetDeptID: 185}
detail := &Detail{ID: 1, ICCID: "89860401791508277308", TargetDeptID: 185}
good := entity.BatchDetail{Id: 1, BatchNo: task.Batch.BatchNo, BatchType: consts.BatchTypeAllocate, TenantId: task.Batch.TenantID, DetailStatus: consts.BatchDetailStatusSuccess, UpdatedBy: 133, Snapshot: gjson.New(detail.Snapshot(task.Batch).String())}
if err := validatePersistedDetailResults(task, []*Detail{detail}, []*entity.BatchDetail{&good}); err != nil {
t.Fatal(err)
+59 -28
View File
@@ -166,68 +166,99 @@ func markAtomicFailure(task *Task, reason string) {
task.FailureReason = reason
for _, detail := range task.Details {
if detail != nil && strings.TrimSpace(detail.Reason) == "" {
detail.Reason = "本批次存在失败明细,整批未执行"
detail.Reason = atomicNotExecutedReason
}
}
}
const atomicNotExecutedReason = "校验通过,但本批次存在失败明细,整批未执行划拨"
func allocationDetailStatus(detail *Detail) int {
if detail.Reason == atomicNotExecutedReason {
return consts.BatchDetailStatusNotExecuted
}
if detail.Success() {
return consts.BatchDetailStatusSuccess
}
return consts.BatchDetailStatusFailed
}
func countValidationFailures(task *Task) int {
count := 0
for _, detail := range task.Details {
if detail != nil && !detail.Success() && detail.Reason != atomicNotExecutedReason {
count++
}
}
return count
}
func persistedAllocationDetailStatus(task *Task, detail *Detail) int {
status := allocationDetailStatus(detail)
if strings.TrimSpace(task.Batch.ParentBatchNo) != "" && status == consts.BatchDetailStatusSuccess {
return consts.BatchDetailStatusRunning
}
return status
}
func validateDetail(task *Task, detail *Detail) {
if detail == nil || strings.TrimSpace(detail.Reason) != "" {
if task == nil || detail == nil {
return
}
var reasons []string
if reason := strings.TrimSpace(detail.Reason); reason != "" {
reasons = append(reasons, reason)
}
defer func() { detail.Reason = strings.Join(reasons, ";") }()
if detail.ICCID == "" {
detail.Reason = "ICCID不能为空"
reasons = append(reasons, "ICCID不能为空")
return
}
detail.Reason = ""
target := resolveTarget(task, detail)
if target == nil {
return
}
detail.Target = target
detail.TargetDeptID = target.DeptID
if detail.TargetDeptKey == "" {
detail.TargetDeptKey = strings.TrimSpace(target.DeptKey)
}
if detail.TargetDeptName == "" {
detail.TargetDeptName = strings.TrimSpace(target.DeptName)
reasons = append(reasons, detail.Reason)
} else {
detail.Target = target
detail.TargetDeptID = target.DeptID
if detail.TargetDeptKey == "" {
detail.TargetDeptKey = strings.TrimSpace(target.DeptKey)
}
if detail.TargetDeptName == "" {
detail.TargetDeptName = strings.TrimSpace(target.DeptName)
}
}
if detail.Card == nil {
detail.Reason = "ICCID不存在或不属于当前租户"
reasons = append(reasons, "ICCID不存在或不属于当前租户")
return
}
if strings.TrimSpace(detail.ICCID) != strings.TrimSpace(detail.Card.ICCID) {
detail.Reason = "ICCID与卡档案不一致"
reasons = append(reasons, "ICCID与卡档案不一致")
return
}
detail.MSISDN = coalesce(detail.MSISDN, detail.Card.MSISDN)
detail.IMSI = coalesce(detail.IMSI, detail.Card.IMSI)
if detail.Card.DeptID != task.Batch.CreatedDept {
detail.Reason = "ICCID不在当前操作员授权范围内"
return
reasons = append(reasons, "ICCID不在当前操作员授权范围内")
}
if strings.TrimSpace(detail.MSISDN) != "" && strings.TrimSpace(detail.MSISDN) != detail.Card.MSISDN {
detail.Reason = "MSISDN与卡档案不一致"
return
reasons = append(reasons, "MSISDN与卡档案不一致")
}
if strings.TrimSpace(detail.IMSI) != "" && strings.TrimSpace(detail.IMSI) != detail.Card.IMSI {
detail.Reason = "IMSI与卡档案不一致"
return
reasons = append(reasons, "IMSI与卡档案不一致")
}
if detail.Card.BossCardStatus == consts.SimBossCardStatusStopped {
detail.Reason = "卡已停机,不能划拨"
return
reasons = append(reasons, "卡已停机,不能划拨")
}
if detail.Card.BossCardStatus == consts.SimBossCardStatusCanceled {
detail.Reason = "卡已销户,不能划拨"
return
reasons = append(reasons, "卡已销户,不能划拨")
}
if detail.Card.PackageStatus == consts.PackageStatusExpired ||
(detail.Card.FlowPkgEndAt != nil && !detail.Card.FlowPkgEndAt.IsZero() && detail.Card.FlowPkgEndAt.Before(gtime.Now())) {
detail.Reason = "套餐已过期,不能划拨"
return
reasons = append(reasons, "套餐已过期,不能划拨")
}
if detail.Card.DeptID == target.DeptID {
detail.Reason = "卡已属于目标客户,无需重复划拨"
if target != nil && detail.Card.DeptID == target.DeptID {
reasons = append(reasons, "卡已属于目标客户,无需重复划拨")
}
}
+56 -5
View File
@@ -74,7 +74,7 @@ func TestAllocateBatchSizesRemainBounded(t *testing.T) {
func TestBuildDetailUpdateStatementUsesBoundParameters(t *testing.T) {
task := &Task{Batch: Batch{BatchNo: "ALC-1", CreatedBy: 7}}
details := []*Detail{
{ID: 11, DetailStatus: consts.BatchDetailStatusPending, Reason: "unsafe ' reason"},
{ID: 11, DetailStatus: consts.BatchDetailStatusPending, Reason: atomicNotExecutedReason},
{ID: 12, DetailStatus: consts.BatchDetailStatusRunning, Reason: "second"},
}
statement, args := buildDetailUpdateStatement(task, details, gtime.Now())
@@ -84,8 +84,11 @@ func TestBuildDetailUpdateStatementUsesBoundParameters(t *testing.T) {
if !strings.Contains(statement, "CASE") || !strings.Contains(statement, "IN (?,?)") {
t.Fatalf("unexpected statement: %s", statement)
}
if len(args) != len(details)*7+9 {
t.Fatalf("argument count = %d, want %d", len(args), len(details)*7+9)
if len(args) != len(details)*7+10 {
t.Fatalf("argument count = %d, want %d", len(args), len(details)*7+10)
}
if args[1] != consts.BatchDetailStatusNotExecuted || args[3] != consts.BatchDetailStatusFailed {
t.Fatalf("status parameters = %v, %v; want not executed and failed", args[1], args[3])
}
}
@@ -96,20 +99,68 @@ func TestNormalizeAtomicParentResultRejectsPartialSuccess(t *testing.T) {
}}
successCount, failCount := normalizeAtomicParentResult(task)
if successCount != 0 || failCount != len(task.Details) {
t.Fatalf("normalizeAtomicParentResult() = %d, %d; want 0, %d", successCount, failCount, len(task.Details))
if successCount != 0 || failCount != 1 {
t.Fatalf("normalizeAtomicParentResult() = %d, %d; want 0, 1", successCount, failCount)
}
for _, detail := range task.Details {
if detail.Success() {
t.Fatalf("detail %s remained successful", detail.ICCID)
}
}
if allocationDetailStatus(task.Details[0]) != consts.BatchDetailStatusNotExecuted || allocationDetailStatus(task.Details[1]) != consts.BatchDetailStatusFailed {
t.Fatalf("wrong detail statuses: %+v", task.Details)
}
rows := task.ResultRows()
if rows[0].Status != "未执行" || rows[1].Status != "失败" {
t.Fatalf("wrong result file statuses: %+v", rows)
}
status, result, _ := parentBatchResult(successCount, failCount)
if status != consts.BatchAsyncStatusFailed || result != consts.BatchRunResultFailed {
t.Fatalf("parentBatchResult() = %d, %d; want failed, failed", status, result)
}
}
func TestAllocateChildValidationDoesNotReportExecutedSuccess(t *testing.T) {
task := &Task{Batch: Batch{BatchNo: "child", ParentBatchNo: "parent"}}
detail := &Detail{ID: 1, ICCID: "valid"}
_, args := buildDetailUpdateStatement(task, []*Detail{detail}, gtime.Now())
if args[1] != consts.BatchDetailStatusRunning {
t.Fatalf("validated child status = %v, want running until parent commits", args[1])
}
task.Batch.ParentBatchNo = ""
_, args = buildDetailUpdateStatement(task, []*Detail{detail}, gtime.Now())
if args[1] != consts.BatchDetailStatusSuccess {
t.Fatalf("committed parent status = %v, want success", args[1])
}
}
func TestValidateTaskCollectsAllCardFailuresBeforeBlockingBatch(t *testing.T) {
task := &Task{
Batch: Batch{CreatedDept: 100, TargetDeptID: 200},
TargetsByID: map[int64]*Target{200: {DeptID: 200, ParentID: 100}},
Details: []*Detail{
{ICCID: "missing"},
{ICCID: "valid", Card: &Card{ICCID: "valid", DeptID: 100}},
{ICCID: "stopped", Card: &Card{ICCID: "stopped", DeptID: 100, BossCardStatus: consts.SimBossCardStatusStopped, PackageStatus: consts.PackageStatusExpired}},
},
}
validateTask(task)
if task.Details[0].Reason != "ICCID不存在或不属于当前租户" {
t.Fatalf("first failure was lost: %q", task.Details[0].Reason)
}
if task.Details[1].Reason != atomicNotExecutedReason || allocationDetailStatus(task.Details[1]) != consts.BatchDetailStatusNotExecuted {
t.Fatalf("validated card was not blocked: %+v", task.Details[1])
}
for _, reason := range []string{"卡已停机,不能划拨", "套餐已过期,不能划拨"} {
if !strings.Contains(task.Details[2].Reason, reason) {
t.Fatalf("later card was not fully validated: %q", task.Details[2].Reason)
}
}
if successes, failures := normalizeAtomicParentResult(task); successes != 0 || failures != 2 {
t.Fatalf("got successes=%d failures=%d, want 0 and 2", successes, failures)
}
}
func TestValidateDetailAllowsCurrentHolderReallocation(t *testing.T) {
task := &Task{
Batch: Batch{
+12 -17
View File
@@ -283,6 +283,9 @@ func (r *Repository) Commit(ctx context.Context, task *Task) error {
markAtomicFailure(task, "划拨批次存在失败明细,整批未执行")
}
successCount, failCount := countResult(task)
if failCount > 0 {
failCount = countValidationFailures(task)
}
autoSplitChild := strings.TrimSpace(task.Batch.ParentBatchNo) != ""
var artifact batchresult.Artifact
var artifactErr error
@@ -443,7 +446,7 @@ func (r *Repository) aggregateParentOnce(ctx context.Context, parentBatchNo stri
finalInfo["successCount"] = successCount
finalInfo["failCount"] = failCount
startTime := resolveAllocateParentStartTime(parent.StartTime, aggregate.EarliestStartTime, now)
data := do.BatchMain{StartTime: startTime, BatchAsyncStatus: status, RunResult: runResult, TotalCount: len(task.Details), ValidCount: successCount, SuccessCount: successCount, FailCount: failCount, FailReason: failReason, BatchInfo: mergeBatchInfo(gjson.New(finalInfo), ossID, downloadID, artifactErr != nil), EndTime: now, UpdatedBy: parent.CreatedBy, UpdatedAt: now}
data := do.BatchMain{StartTime: startTime, BatchAsyncStatus: status, RunResult: runResult, TotalCount: len(task.Details), ValidCount: len(task.Details) - failCount, SuccessCount: successCount, FailCount: failCount, FailReason: failReason, BatchInfo: mergeBatchInfo(gjson.New(finalInfo), ossID, downloadID, artifactErr != nil), EndTime: now, UpdatedBy: parent.CreatedBy, UpdatedAt: now}
result, err := tx.Ctx(ctx).Model(dao.BatchMain.Table()).Where(mainColumns.Id, parent.Id).WhereIn(mainColumns.BatchAsyncStatus, []int{consts.BatchAsyncStatusPending, consts.BatchAsyncStatusProcessing}).Data(data).Update()
if err != nil {
if artifactErr == nil {
@@ -536,7 +539,7 @@ func normalizeAtomicParentResult(task *Task) (int, int) {
}
task.FailureReason = coalesce(task.FailureReason, "划拨批次存在失败明细,整批未执行")
markAtomicFailure(task, task.FailureReason)
return 0, len(task.Details)
return 0, countValidationFailures(task)
}
func (r *Repository) updateDetails(ctx context.Context, tx gdb.TX, task *Task, now *gtime.Time) error {
@@ -644,10 +647,7 @@ func validatePersistedDetailResults(task *Task, details []*Detail, persisted []*
}
for _, detail := range details {
row := byID[detail.ID]
status := consts.BatchDetailStatusFailed
if detail.Success() {
status = consts.BatchDetailStatusSuccess
}
status := persistedAllocationDetailStatus(task, detail)
if row == nil || row.DetailStatus != status || row.ErrorReason != detail.Reason || row.UpdatedBy != task.Batch.CreatedBy || row.Snapshot == nil || !sameDetailSnapshot(row.Snapshot, detail.Snapshot(task.Batch)) {
return gerror.Newf("划拨明细最终结果不一致,明细ID:%d", detail.ID)
}
@@ -669,12 +669,9 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
columns := dao.BatchDetail.Columns()
var builder strings.Builder
fmt.Fprintf(&builder, "UPDATE %s SET %s = CASE %s ", dao.BatchDetail.Table(), columns.DetailStatus, columns.Id)
args := make([]any, 0, len(details)*7+8)
args := make([]any, 0, len(details)*7+9)
for _, detail := range details {
status := consts.BatchDetailStatusFailed
if detail.Success() {
status = consts.BatchDetailStatusSuccess
}
status := persistedAllocationDetailStatus(task, detail)
builder.WriteString("WHEN ? THEN ? ")
args = append(args, detail.ID, status)
}
@@ -690,7 +687,7 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
}
fmt.Fprintf(&builder, "ELSE %s END, %s = ?, %s = ? ", columns.Snapshot, columns.UpdatedBy, columns.UpdatedAt)
args = append(args, task.Batch.CreatedBy, now)
fmt.Fprintf(&builder, "WHERE %s = ? AND %s = ? AND %s = ? AND %s IN (?, ?, ?, ?) AND %s IN (%s)",
fmt.Fprintf(&builder, "WHERE %s = ? AND %s = ? AND %s = ? AND %s IN (?, ?, ?, ?, ?) AND %s IN (%s)",
columns.BatchNo,
columns.BatchType,
columns.TenantId,
@@ -706,6 +703,7 @@ func buildDetailUpdateStatement(task *Task, details []*Detail, now *gtime.Time)
consts.BatchDetailStatusRunning,
consts.BatchDetailStatusSuccess,
consts.BatchDetailStatusFailed,
consts.BatchDetailStatusNotExecuted,
)
for _, detail := range details {
args = append(args, detail.ID)
@@ -845,10 +843,7 @@ func (r *Repository) updateDetail(ctx context.Context, tx gdb.TX, task *Task, de
detail.DetailStatus != consts.BatchDetailStatusRunning) {
return nil
}
status := consts.BatchDetailStatusFailed
if detail.Success() {
status = consts.BatchDetailStatusSuccess
}
status := persistedAllocationDetailStatus(task, detail)
result, err := tx.Ctx(ctx).Model(dao.BatchDetail.Table()).
Where(dao.BatchDetail.Columns().Id, detail.ID).
Where(dao.BatchDetail.Columns().BatchNo, task.Batch.BatchNo).
@@ -949,7 +944,7 @@ func (r *Repository) finalizeBatch(ctx context.Context, tx gdb.TX, task *Task, s
BatchAsyncStatus: batchAsyncStatus,
RunResult: runResult,
TotalCount: len(task.Details),
ValidCount: successCount,
ValidCount: len(task.Details) - failCount,
SuccessCount: successCount,
FailCount: failCount,
FailReason: task.FailureReason,
+12
View File
@@ -6,6 +6,7 @@ import (
"github.com/gogf/gf/v2/encoding/gjson"
"github.com/gogf/gf/v2/os/gtime"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/consts"
"git.5iot.com/iotlcm-public/iotlcm-worker-svr/internal/service/batchresult"
)
@@ -155,8 +156,19 @@ func (t *Task) ResultRows() []batchresult.Row {
TargetDeptName: targetName,
Operation: "卡划拨",
Success: detail.Success(),
Status: allocationResultStatus(detail),
Reason: strings.TrimSpace(detail.Reason),
})
}
return rows
}
func allocationResultStatus(detail *Detail) string {
if allocationDetailStatus(detail) == consts.BatchDetailStatusNotExecuted {
return "未执行"
}
if detail.Success() {
return "成功"
}
return "失败"
}