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
+164 -12
View File
@@ -19,6 +19,22 @@ import (
const inboundBasicInfoClientNameEnv = "UPSTREAM_INBOUND_BASIC_INFO_CLIENT_NAME"
const upstreamProxyMaxTimeoutConfigKey = "upstream.proxy.maxTimeout"
// apnAsyncProtocolVersionConfigKey 是异步APN提交的兼容开关。
// 默认值改为开启:常规发布不再需要人工逐环境开开关,兼容性由提交前的协议能力探测兜底。
// 只有需要临时回退到旧协议时,才显式配为 0。
const apnAsyncProtocolVersionConfigKey = "upstream.proxy.apnAsyncProtocolVersion"
// defaultApnAsyncProtocolVersion 默认开启异步APN提交。
const defaultApnAsyncProtocolVersion = 1
// defaultUpstreamProxyMaxTimeout 是单次批量转发的兜底上限。
// 必须大于 upstream-proxy 的 gateway.batchTimeout(默认 20 分钟),
// 让超时由更靠近通道的一层先判定,避免 worker 提前放弃而通道仍在办理,造成结果未知。
const defaultUpstreamProxyMaxTimeout = 25 * time.Minute
// defaultUpstreamProxyTimeout 是未配置单卡预算时的默认值。
const defaultUpstreamProxyTimeout = 10 * time.Second
type Client struct {
currentUsageClientName string
currentUsageTimeout time.Duration
@@ -134,6 +150,106 @@ func (c *Client) ChangeSimApnFunction(ctx context.Context, input *model.SimCardA
return requestAll[model.SimCardApnChangeOutputItem](ctx, "/sim/change/sim-apn", input, len(input.SimKeys))
}
// apnRequestNotSentError 仅描述任何APN写请求之前的能力拒绝。
// 提交开始后的通信错误绝不能使用此类型。
type apnRequestNotSentError struct{ cause error }
func (e *apnRequestNotSentError) Error() string { return e.cause.Error() }
func (e *apnRequestNotSentError) Unwrap() error { return e.cause }
func (e *apnRequestNotSentError) APNRequestNotSent() bool { return true }
func (c *Client) CheckSimApnAsyncCapability(ctx context.Context, input *model.SimCardApnChangeInput) (err error) {
defer func() {
if err != nil {
err = &apnRequestNotSentError{cause: err}
}
}()
if !apnAsyncSubmitEnabled(ctx) {
return gerror.New("异步APN提交已被显式关闭(upstream.proxy.apnAsyncProtocolVersion=0);清除该配置或置为1后重试")
}
if err := apnDependencyReady(ctx); err != nil {
return gerror.Wrap(err, "异步APN依赖未就绪,未发送办理请求")
}
if input == nil || strings.TrimSpace(input.ReqData.OperationID) == "" {
return gerror.New("异步APN提交能力探测缺少operationId")
}
return c.checkSimApnProtocol(ctx, input)
}
// apnDependencyReady 检查本地真实依赖(迁移表结构);测试可替换以便固定结论。
var apnDependencyReady = func(ctx context.Context) error {
return defaultAPNReadiness.check(ctx)
}
// apnAsyncSubmitEnabled 判断应急总闸是否被显式关闭。
// 默认放行:是否可提交由程序自行检查真实依赖决定——
// - 本地依赖:apn_readiness 核对迁移表结构是否就绪;
// - 远端兼容:每次提交前的协议能力探测,旧版 chan/proxy 会明确拒绝而不是按同步语义执行。
// 因此常规发布不再需要人工逐环境开开关;仅回退到旧协议时把配置显式设为 0。
func apnAsyncSubmitEnabled(ctx context.Context) bool {
return g.Cfg().MustGet(ctx, apnAsyncProtocolVersionConfigKey, defaultApnAsyncProtocolVersion).Int() == 1
}
// 仅验证远端协议:已有受理映射的只读补查不受新增提交门禁控制。
// 旧版代理/通道缺少能力接口时必须拒绝,不能把查询当作普通办理转发。
func (c *Client) checkSimApnProtocol(ctx context.Context, input *model.SimCardApnChangeInput) error {
if len(input.SimKeys) == 0 || len(input.SimKeys) > 100 || (strings.TrimSpace(input.ReqData.OperationID) == "" && strings.TrimSpace(input.ReqData.QueryJobID) == "") {
return gerror.New("异步APN能力探测需要operationId和1至100张卡快照")
}
type capability struct {
ProtocolVersion int `json:"protocolVersion"`
}
probeCtx, cancel := context.WithTimeout(ctx, 10*time.Second)
defer cancel()
items, err := requestAll[capability](probeCtx, "/sim/query/sim-apn-capability", input, len(input.SimKeys))
if err != nil {
return gerror.Wrap(err, "异步APN协议能力探测失败,未发送办理或恢复请求")
}
if len(items) != len(input.SimKeys) {
return gerror.New("异步APN协议能力响应卡数不匹配,禁止发送办理请求")
}
seen := make(map[model.SimKey]bool)
for _, item := range items {
if item.SimKey == nil || item.Code != 0 || item.ResData.ProtocolVersion != 1 || seen[*item.SimKey] {
return gerror.New("通道未就绪或不支持异步APN协议,禁止发送办理请求")
}
seen[*item.SimKey] = true
}
for _, key := range input.SimKeys {
if key == nil || !seen[*key] {
return gerror.New("异步APN协议能力响应卡关联不匹配")
}
}
return nil
}
// SubmitSimApnFunction 仅提交异步批量任务;受理状态不能按办理成功回写。
func (c *Client) SubmitSimApnFunction(ctx context.Context, input *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
if err := c.CheckSimApnAsyncCapability(ctx, input); err != nil {
return nil, err
}
request := *input
request.ReqData.SubmitOnly = true
request.ReqData.QueryJobID = ""
request.ReqData.RecoverOnly = false
return c.ChangeSimApnFunction(ctx, &request)
}
// QuerySimApnFunctionResult 根据已持久化的jobId只查一次,不重新提交。
func (c *Client) QuerySimApnFunctionResult(ctx context.Context, input *model.SimCardApnChangeInput) ([]model.SimBaseRes[model.SimCardApnChangeOutputItem], error) {
if strings.TrimSpace(input.ReqData.QueryJobID) == "" && strings.TrimSpace(input.ReqData.OperationID) == "" {
return nil, gerror.New("异步APN结果查询缺少jobId和operationId")
}
request := *input
request.ReqData.QueryJobID = strings.TrimSpace(request.ReqData.QueryJobID)
request.ReqData.SubmitOnly = false
request.ReqData.RecoverOnly = request.ReqData.QueryJobID == ""
if err := c.checkSimApnProtocol(ctx, &request); err != nil {
return nil, gerror.Wrap(err, "异步APN只读查询协议未就绪,未发送查询请求")
}
return c.ChangeSimApnFunction(ctx, &request)
}
func request[T any](ctx context.Context, path string, input any, itemCount int) ([]model.SimBaseRes[T], error) {
items, err := requestAll[T](ctx, path, input, itemCount)
if err != nil {
@@ -173,19 +289,13 @@ func requestAllWithAddressTimeout[T any](ctx context.Context, path string, input
}
client := g.Client()
timeout := timeoutOverride
if timeout <= 0 {
timeout = config.Timeout
}
if timeout <= 0 {
timeout = 10 * time.Second
}
if itemCount > 1 {
timeout *= time.Duration(itemCount)
}
if maxTimeout := g.Cfg().MustGet(ctx, upstreamProxyMaxTimeoutConfigKey, 2*time.Minute).Duration(); maxTimeout > 0 && timeout > maxTimeout {
timeout = maxTimeout
if path == "/sim/change/sim-apn" || path == "/sim/query/sim-apn-capability" {
client.SetContentType("application/json")
}
timeout := resolveUpstreamTimeout(path, itemCount, config.Timeout,
timeoutOverride,
configDuration(ctx, upstreamProxyMaxTimeoutConfigKey, defaultUpstreamProxyMaxTimeout),
configDuration(ctx, "upstream.proxy.dataServiceTimeout", 240*time.Second))
setClientTimeout(client, timeout)
clientResponse, err := client.Post(ctx, fmt.Sprintf("http://%s%s", address, path), input)
if err != nil {
@@ -239,3 +349,45 @@ func setClientTimeout(client *gclient.Client, timeout time.Duration) {
transport.ResponseHeaderTimeout = timeout
}
}
// resolveUpstreamTimeout 计算单次转发预算:
// 普通批量按「单卡预算 × 卡数」放大,并以 maxTimeout 兜底,避免批量操作被默认 2 分钟上限提前判失败;
// 数据开停沿用独立预算,优先于按卡计时。
func resolveUpstreamTimeout(path string, itemCount int, perCard, timeoutOverride, maxTimeout, dataServiceTimeout time.Duration) time.Duration {
if itemCount < 1 {
itemCount = 1
}
timeout := timeoutOverride
if timeout <= 0 {
timeout = perCard
}
if timeout <= 0 {
timeout = defaultUpstreamProxyTimeout
}
timeout *= time.Duration(itemCount)
if path == "/sim/change/sim-apn" {
if dataServiceTimeout > 0 {
return dataServiceTimeout
}
return 240 * time.Second
}
if maxTimeout > 0 && timeout > maxTimeout {
timeout = maxTimeout
}
return timeout
}
// configDuration 读取时长配置,兼容写成 "20m" 字符串或秒数整数两种形式。
func configDuration(ctx context.Context, key string, fallback time.Duration) time.Duration {
value := g.Cfg().MustGet(ctx, key)
if value == nil || value.IsNil() {
return fallback
}
if duration := value.Duration(); duration >= time.Second {
return duration
}
if seconds := value.Int(); seconds > 0 {
return time.Duration(seconds) * time.Second
}
return fallback
}