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
+154
View File
@@ -0,0 +1,154 @@
package upstream
import (
"context"
"errors"
"fmt"
"sort"
"strings"
"sync"
"time"
"github.com/gogf/gf/v2/errors/gerror"
"github.com/gogf/gf/v2/frame/g"
)
// apnRequiredSchema 是异步APN提交依赖的表与关键列,对应 20261015 的四份迁移脚本。
// 缺表或缺列意味着迁移没有执行完,此时提交必然写不进去,应在发请求前就拒绝。
var apnRequiredSchema = map[string][]string{
"t_apn_operation": {"operation_id", "chan_key", "request_hash", "response_json", "revision"},
"data_operation_parent": {"batch_no", "ready", "finalize_token", "finalize_until"},
"data_operation_sub_batch": {"operation_id", "batch_no", "chan_key", "request_data", "status", "poll_count", "lease_token"},
"data_operation_sub_detail": {"detail_id", "operation_id", "batch_no", "iccid"},
"data_operation_card_claim": {"tenant_id", "iccid", "batch_id", "active_batch_id"},
}
// apnReadinessTTL 是依赖检查结果的缓存时间:迁移完成后最迟一分钟内自动放行,无需重启。
const apnReadinessTTL = time.Minute
// apnReadinessVerifyTimeout 是单次结构校验的独立超时,与调用方上下文解耦。
const apnReadinessVerifyTimeout = 5 * time.Second
type apnReadinessCache struct {
ttl time.Duration
mu sync.Mutex
checkedAt time.Time
err error
// verify 默认指向 verifyAPNSchema;独立出来是为了让缓存行为可测。
verify func(context.Context) error
}
var defaultAPNReadiness = &apnReadinessCache{ttl: apnReadinessTTL, verify: verifyAPNSchema}
// check 返回异步APN提交的依赖状态,结果按 TTL 缓存,避免每次提交都查库。
func (c *apnReadinessCache) check(ctx context.Context) error {
c.mu.Lock()
defer c.mu.Unlock()
if !c.checkedAt.IsZero() && time.Since(c.checkedAt) < c.ttl {
return c.err
}
verify := c.verify
if verify == nil {
verify = verifyAPNSchema
}
// 校验使用独立超时,不受调用方取消影响;否则一次取消就会把失败结果缓存给全进程。
verifyCtx, cancel := context.WithTimeout(context.WithoutCancel(ctx), apnReadinessVerifyTimeout)
defer cancel()
c.err = verify(verifyCtx)
if c.err != nil && (errors.Is(c.err, context.Canceled) || errors.Is(c.err, context.DeadlineExceeded)) {
// 只是本次校验没跑完,不代表依赖缺失,不写入缓存。
c.checkedAt = time.Time{}
return c.err
}
c.checkedAt = time.Now()
return c.err
}
// verifyAPNSchema 直接查询库里的实际表结构,而不是依赖人工开开关确认「迁移已执行」。
func verifyAPNSchema(ctx context.Context) (err error) {
if !databaseConfigured(ctx) {
// 未配置数据库连接(测试桩、纯传输层用例)时跳过;生产 worker 不存在该情况。
return nil
}
// g.DB() 在缺配置时会 panic,这里统一转成可读错误,避免拖垮提交链路。
defer func() {
if recovered := recover(); recovered != nil {
err = gerror.Newf("无法获取数据库连接,不能确认异步APN依赖是否就绪:%v", recovered)
}
}()
db := g.DB()
if db == nil {
return gerror.New("无法获取数据库连接,不能确认异步APN依赖是否就绪")
}
config := db.GetConfig()
if strings.EqualFold(config.Type, "sqlite") {
// 本地/单元测试使用 SQLite,没有 information_schema,跳过结构检查。
return nil
}
names := make([]string, 0, len(apnRequiredSchema))
for name := range apnRequiredSchema {
names = append(names, name)
}
sort.Strings(names)
placeholders := make([]string, 0, len(names))
args := make([]interface{}, 0, len(names))
for _, name := range names {
placeholders = append(placeholders, "?")
args = append(args, name)
}
records, err := db.GetAll(ctx, fmt.Sprintf(
"SELECT TABLE_NAME AS table_name, COLUMN_NAME AS column_name FROM information_schema.columns WHERE TABLE_SCHEMA = DATABASE() AND TABLE_NAME IN (%s)",
strings.Join(placeholders, ",")), args...)
if err != nil {
return gerror.Wrap(err, "查询异步APN依赖表结构失败")
}
existing := make(map[string]map[string]bool, len(records))
for _, record := range records {
table := record["table_name"].String()
if existing[table] == nil {
existing[table] = make(map[string]bool)
}
existing[table][strings.ToLower(record["column_name"].String())] = true
}
return validateAPNSchema(existing)
}
// databaseConfigured 判断当前配置里是否声明了数据库,用于区分测试桩与真实运行环境。
func databaseConfigured(ctx context.Context) bool {
value := g.Cfg().MustGet(ctx, "database")
return value != nil && !value.IsNil() && !value.IsEmpty()
}
// validateAPNSchema 比对必需的表与列,返回可直接据此执行迁移的错误信息。
func validateAPNSchema(existing map[string]map[string]bool) error {
tables := make([]string, 0, len(apnRequiredSchema))
for table := range apnRequiredSchema {
tables = append(tables, table)
}
sort.Strings(tables)
var missingTables []string
var missingColumns []string
for _, table := range tables {
columns, ok := existing[table]
if !ok {
missingTables = append(missingTables, table)
continue
}
required := append([]string(nil), apnRequiredSchema[table]...)
sort.Strings(required)
for _, column := range required {
if !columns[column] {
missingColumns = append(missingColumns, table+"."+column)
}
}
}
switch {
case len(missingTables) > 0:
return gerror.Newf("异步APN依赖未就绪:缺少数据表 %s,请先执行 20261015 迁移脚本",
strings.Join(missingTables, "、"))
case len(missingColumns) > 0:
return gerror.Newf("异步APN依赖未就绪:缺少字段 %s,请先执行 20261015 迁移脚本",
strings.Join(missingColumns, "、"))
}
return nil
}