加上一个日志输出,看看回调内容
All checks were successful
构建并部署到测试环境(无 SSH) / build-and-deploy (push) Successful in 8m10s

This commit is contained in:
2026-07-28 09:21:54 +08:00
parent a8ca00e5f2
commit 7c5b6ee036
9 changed files with 294 additions and 3 deletions

View File

@@ -9362,6 +9362,10 @@ components:
type: object
DtoShopSeriesGrantPackageItem:
properties:
allocation_id:
description: 套餐授权ID
minimum: 0
type: integer
cost_price:
description: 成本价(分)
type: integer

View File

@@ -137,7 +137,7 @@ func initHandlers(svc *services, deps *Dependencies) *Handlers {
wecomInfra.NewSceneRepository(deps.DB), deps.SystemConfigAudit,
)
wecomApprovalCallback := callback.NewWeComApprovalHandler(wecomInfra.NewCallbackService(
wecomRepository, integrationlog.NewRepository(deps.DB), deps.QueueClient,
wecomRepository, integrationlog.NewRepository(deps.DB), deps.QueueClient, deps.Logger,
))
svc.Account.SetWeComMemberFinder(wecomMembers)

View File

@@ -11,6 +11,7 @@ import (
"github.com/break/junhong_cmp_fiber/pkg/constants"
"github.com/break/junhong_cmp_fiber/pkg/errors"
"github.com/break/junhong_cmp_fiber/pkg/queue"
"go.uber.org/zap"
)
type callbackIntegrationLog interface {
@@ -42,11 +43,12 @@ type CallbackService struct {
applications *ApplicationRepository
integration callbackIntegrationLog
queue *queue.Client
logger *zap.Logger
}
// NewCallbackService 创建企业微信审批回调服务。
func NewCallbackService(applications *ApplicationRepository, integration callbackIntegrationLog, queueClient *queue.Client) *CallbackService {
return &CallbackService{applications: applications, integration: integration, queue: queueClient}
func NewCallbackService(applications *ApplicationRepository, integration callbackIntegrationLog, queueClient *queue.Client, logger *zap.Logger) *CallbackService {
return &CallbackService{applications: applications, integration: integration, queue: queueClient, logger: logger}
}
// VerifyURL 校验企微回调 URL 并返回 echostr 明文。
@@ -75,6 +77,9 @@ func (s *CallbackService) Receive(ctx context.Context, applicationID uint, signa
if err != nil {
return err
}
if s.logger != nil {
s.logger.Info("企业微信审批回调解密成功", zap.Uint("application_id", applicationID), zap.ByteString("content", plaintext))
}
var event approvalChangeEvent
if err := xml.Unmarshal(plaintext, &event); err != nil || event.Event != "sys_approval_change" || strings.TrimSpace(event.SPNo) == "" {
return errors.New(errors.CodeInvalidParam, "企业微信审批回调事件无效")

View File

@@ -0,0 +1,2 @@
schema: spec-driven
created: 2026-07-27

View File

@@ -0,0 +1,89 @@
## Context
当前 `pkg/errors` 会根据业务错误码计算 HTTP 状态,并由全局 ErrorHandler 对所有 5xx 消息脱敏。该安全边界本身正确;问题在于生产代码将含义不同的失败统一产生为 `CodeServiceUnavailable`,所以业务提示先被错误归类为 503随后又被正确的 5xx 脱敏规则替换为“服务暂时不可用”。
初步审计发现 `internal/` 下 79 个 `CodeServiceUnavailable` 产生点59 个属于缺少 Adapter、客户端或其他运行能力保留 5034 个明确属于业务配置或状态错误16 个属于外部调用或外部响应异常,需要在原始响应边界区分 4xx、502 和 504。企业微信当前两个 `completeFailed` 方法会在记录 Integration Log 后无条件返回 503丢失了 provider 错误码已经提供的分类信息。
本变更是横切错误契约治理。主通道为 Infrastructure/统一错误边界,辅助通道为 Application、Handler 和外部 Adapter不迁移任何完整业务模块也不改变资金、审批状态机或业务事实。
## Goals / Non-Goals
**Goals:**
- 让 4xx、502、503、504 分别表达可纠正业务失败、上游响应异常、运行能力不可用和上游超时。
- 在企业微信响应边界保留已知凭据、权限、可信 IP、可见范围和模板授权错误的安全提示。
- 保留全局 5xx 脱敏和既有结构化日志、Integration Log。
- 用最小的错误码映射和逐项静态审计约束所有现有 503 产生点。
**Non-Goals:**
- 不改变成功响应、API 路径、DTO、数据库结构或数据权限规则。
- 不重构企业微信客户端、审批领域或全局 ErrorHandler。
- 不新增错误分类框架、第三方依赖、缓存、异步任务或数据库事务。
- 不同步修复与错误语义无关的企业微信模板、字段映射或通讯录功能。
## Decisions
### 1. 保留 ErrorHandler 脱敏,在错误产生处纠正分类
全局 ErrorHandler 继续按 `GetHTTPStatus` 对 5xx 返回通用安全消息。Application、Service 和 Handler 若收到已结构化的 `AppError`,在没有新增语义时原样传递;分类发生在业务前置条件判断处或外部 Adapter 能读取 provider 响应的位置。
备选方案是让 ErrorHandler 对带自定义消息的 503 不脱敏。该方案会扩大敏感信息泄露面,而且无法纠正调用方看到的 HTTP 语义,因此拒绝。
### 2. 使用现有错误体系补一个明确的 502 错误码
`pkg/errors` 现有服务端错误码区间增加一个“上游响应异常”错误码,完整登记到 `allErrorCodes``errorMessages``GetHTTPStatus`HTTP 状态映射为 502。超时复用现有 `CodeTimeout`504运行能力缺失继续使用 `CodeServiceUnavailable`503
现有 `CodeGatewayError``CodeGatewayTimeout``CodeGatewayInvalidResp` 位于客户端错误码区间,且已被运营商 Gateway 业务广泛使用;直接修改它们的 HTTP 映射会扩大兼容性影响,因此本变更不借机迁移这些旧错误码。
备选方案是所有上游错误继续使用 503。该方案无法区分“本服务没有能力处理”与“本服务调用上游失败”正是当前问题故拒绝。
### 3. 企业微信按 provider 失败类型做小型显式映射
目录、Token、模板、审批详情、审批提交、附件上传和审批单号客户端在同一响应处理边界按以下顺序分类
| 失败类型 | 错误语义 | HTTP |
| --- | --- | --- |
| 本地应用不存在、已禁用、凭据缺失或 API 地址无效 | 现有企业微信专用 4xx | 4xx |
| provider 明确返回凭据、可信 IP、可见范围、权限或模板授权错误 | 企业微信专用 4xx必要时在现有 12101219 区间补最少错误码 | 4xx |
| provider 未知业务错误、HTTP 5xx、连接/读响应/解析失败、响应过大 | 新增的上游响应异常错误码 | 502 |
| context deadline 或外部请求超时 | `CodeTimeout` | 504 |
| 客户端或 Adapter 未装配 | `CodeServiceUnavailable` | 503 |
映射使用现有函数或最小包内函数复用;只有两个以上客户端确实共享同一组 provider 错误码时才提取公共 helper不创建接口、工厂或新包。未知 provider 错误一律失败关闭为 502原始 code/message 仅保留在 Integration Log 和服务端日志。
备选方案是在每个上层业务 Service 根据中文消息二次判断。该方案脆弱且重复,并会丢失 provider code故拒绝。
### 4. 业务前置条件直接使用已有 4xx
- 审批场景未配置或已禁用:使用现有状态类 4xx并保留“企业微信审批场景未配置或已禁用”。
- 审批准备结果过期:使用冲突或状态类 4xx提示重新提交。
- 审批 Adapter、消费者、客户端确实未装配仍为 503。
这些判断不涉及多表写入、事务、缓存或异步重试;依赖注入继续使用现有结构体字段注入,不引入新容器或 Port。
### 5. 以静态审计清单收口
实施时重新枚举生产代码全部 `CodeServiceUnavailable` 产生点,并逐项标记“保留 503”或迁移目标避免只修用户当前遇到的两个接口。验证限定为代码审计、格式化、LSP diagnostics、`go vet``go build`,不增加或运行测试。
Audit Event、Domain Ledger 与 Outbox 均为 N/A因为本变更不产生业务事实Integration Log 沿用现有实现。实施记录需增量更新 `.scratch/tech-global-audit/审计覆盖基线.md`,说明本变更未增加审计事件的理由。
## Risks / Trade-offs
- [客户端依赖旧的固定 503] → 在发布说明列出 BREAKING 状态变化,客户端应优先按业务 `code` 分支,并允许按 change 整体回滚。
- [企业微信 provider 错误码分类不完整] → 只映射已有代码或现成企微实现能够证实的错误;未知错误统一返回 502禁止根据模糊中文包含关系猜测。
- [批量替换造成真实 503 被误改] → 以产生点逐项审计和错误分类清单为门禁,不做全局机械替换。
- [5xx 日志或响应泄露敏感信息] → 响应继续使用全局映射消息provider 原文仅进入已脱敏的 Integration Log/结构化日志路径。
- [横切修改范围扩大] → 只触碰错误常量和错误产生边界;不顺带重构调用链。
## Migration Plan
1. 先建立全部 `CodeServiceUnavailable` 产生点分类清单,锁定应改变与应保留的分支。
2. 增加 502 错误码映射,并按纵向调用切片纠正业务前置条件和企业微信 Adapter 分类。
3. 重新审计全部 `CodeServiceUnavailable` 产生点执行格式化、LSP diagnostics、`go vet``go build`
4. 发布时标注部分失败响应 HTTP 状态变化;无需数据库迁移、缓存清理或停机。
5. 回滚时整体回退本 change 的错误码与调用点修改即可;无数据回滚。
## Open Questions
无。具体企业微信 provider code 映射以仓库现有实现和 `/Users/break/csxjProject/wecom` 的已验证实现为证据,在实施阶段只录入能被代码证实的映射。

View File

@@ -0,0 +1,32 @@
## Why
功能 ID`feature-503-error-semantics`
当前多个可预期的业务配置、凭据和上游调用错误被统一包装为 `CodeServiceUnavailable`,导致接口固定返回 HTTP 503并由全局 5xx 脱敏规则将原本可操作的中文提示替换为“服务暂时不可用”。这使调用方无法区分业务配置缺失、上游失败与真正的服务不可用,也直接造成企业微信审批场景等错误难以定位。
## What Changes
- 在错误产生边界按语义分类:可预期业务配置或状态错误使用 4xx上游连接、HTTP 5xx 或非法响应使用 502上游超时使用 504仅运行时能力或依赖确实不可用时使用 503。
- 企业微信场景配置、通讯录权限/可见范围、凭据与可信 IP 等已知失败原因返回专用 4xx 错误和安全、可操作的中文提示,不再统一压成 503。
- 保留全局 5xx 脱敏机制;不通过放宽脱敏规则暴露内部错误,而是在原始响应或依赖调用边界完成分类与安全消息转换。
- 对现有 `CodeServiceUnavailable` 产生点建立分类静态审计,防止业务提醒和上游故障再次误用 503。
- **BREAKING**:部分当前返回 HTTP 503 的 API 失败响应将改为对应的 4xx、502 或 504客户端若依赖固定 503 状态码,需要按统一错误码语义调整处理逻辑。
## Capabilities
### New Capabilities
无。
### Modified Capabilities
- `error-handling`:明确业务错误、上游错误和服务不可用错误的分类边界,以及 5xx 安全脱敏与 4xx 可操作提示的协作规则。
- `error-code-validation`:增加对 503 误用、上游 502/504 分类及凭据/配置类 4xx 映射的静态校验要求。
## Impact
- 架构主通道为 Infrastructure 统一错误边界,辅助通道为 Application、Handler 和外部 Adapter沿用现有 `pkg/errors` 错误码、Fiber 全局 ErrorHandler 与各业务调用链,不新增错误抽象或依赖。
- 重点影响企业微信审批场景、通讯录同步和 Token 获取,以及其他将上游失败或业务配置问题包装成 `CodeServiceUnavailable` 的调用点;不迁移业务模块,不修改资金、审批状态机或业务事实。
- API 响应结构仍为 `{code, msg, data, timestamp}`仅纠正部分失败响应的错误码、HTTP 状态和安全提示;成功路径、数据库结构和正常请求性能不变,错误分类只在既有分支内完成,不引入额外 I/OP95/P99 目标不受影响。
- 验证仅使用仓库级 503 产生点审计、错误码注册表核对、`gofmt`、LSP diagnostics、`go vet``go build`;不新增或运行单元测试、集成测试和接口测试。
- Audit Event、Domain Ledger、Outbox 均为 N/A本变更只纠正错误分类不创建或变更业务事实Integration Log 沿用现有外部调用记录,不新增记录体系,并增量维护审计覆盖基线中的决定说明。

View File

@@ -0,0 +1,47 @@
## ADDED Requirements
### Requirement: HTTP Error Semantics Static Validation
系统 SHALL 通过错误码注册表和 HTTP 状态映射的静态核对,确保业务 4xx、上游响应异常 502、超时 504 与服务能力不可用 503 各自具有唯一且完整的映射。
#### Scenario: 错误码映射符合语义
- **WHEN** 实施者核对 `allErrorCodes``errorMessages``GetHTTPStatus`
- **THEN** 业务配置或状态错误映射为对应 4xx
- **AND** 上游响应异常映射为 502
- **AND** 超时映射为 504
- **AND** `CodeServiceUnavailable` 映射为 503
#### Scenario: 新增服务端错误码
- **WHEN** 实施者新增上游响应异常错误码
- **THEN** 必须同步登记错误码常量、`allErrorCodes``errorMessages``GetHTTPStatus`
- **AND** LSP diagnostics、`go vet``go build` 不得出现相关错误
### Requirement: Service Unavailable Usage Audit
系统 MUST 对生产代码中的 `CodeServiceUnavailable` 产生点维护可复核的语义分类,禁止将可预期业务失败或普通上游失败仅为触发脱敏而标记为 503。
#### Scenario: 业务前置条件失败
- **WHEN** 静态审计发现错误产生于场景未配置、场景已禁用或审批准备结果过期
- **THEN** 该产生点使用语义匹配的 4xx
- **AND** 安全中文提示不得被替换为“服务暂时不可用”
#### Scenario: 企业微信已知配置错误
- **WHEN** 响应处理代码能够识别企业微信凭据、可信 IP、可见范围、权限或模板授权错误
- **THEN** 该分支返回专用企业微信 4xx 错误
- **AND** 安全提示保留配置检查方向
#### Scenario: 企业微信未知上游错误
- **WHEN** 响应处理代码面对连接失败、HTTP 5xx、非法响应或无法可靠分类的 provider 错误
- **THEN** 该分支返回上游响应异常错误
- **AND** HTTP 状态码为 502
#### Scenario: 运行能力缺失
- **WHEN** 代码分支表示请求所必需的 Adapter 或客户端未装配
- **THEN** 该产生点继续使用 `CodeServiceUnavailable`
- **AND** HTTP 状态码为 503

View File

@@ -0,0 +1,78 @@
## MODIFIED Requirements
### Requirement: Standardized Error Codes
系统 SHALL 使用标准化错误码表达失败语义,并由 `GetHTTPStatus(code)` 唯一确定 HTTP 状态码;调用方不得仅因错误需要对外脱敏而选择 `CodeServiceUnavailable`
#### Scenario: 参数验证错误码
- **WHEN** 参数验证失败
- **THEN** 使用 `CodeInvalidParam`
- **AND** 不使用已删除的兼容别名
#### Scenario: 可预期业务配置或状态错误
- **WHEN** 请求因场景未配置、场景已禁用、准备结果过期或其他可由调用方或管理员纠正的业务前置条件而失败
- **THEN** 系统返回语义匹配的 4xx 错误码
- **AND** 响应保持统一格式 `{code, msg, data, timestamp}`
- **AND** `msg` 提供安全、可操作的中文提示
#### Scenario: 上游服务响应失败
- **WHEN** 系统已正确配置并调用外部服务,但发生连接失败、上游 HTTP 5xx、响应体无法读取、响应过大或响应格式非法
- **THEN** 系统返回表示上游响应异常的错误码
- **AND** HTTP 状态码为 502
- **AND** 不向客户端泄露上游原始响应、地址或底层错误
#### Scenario: 上游服务超时
- **WHEN** 外部服务调用或等待外部调用结果超过约定时限
- **THEN** 系统返回超时错误码
- **AND** HTTP 状态码为 504
#### Scenario: 服务能力确实不可用
- **WHEN** 当前运行环境缺少完成请求所需的 Adapter、客户端、队列消费者或其他必要能力
- **THEN** 使用 `CodeServiceUnavailable`
- **AND** HTTP 状态码为 503
#### Scenario: 企业微信凭据或授权配置错误
- **WHEN** 企业微信返回可识别的凭据无效、权限不足、可信 IP、应用可见范围或模板授权配置错误
- **THEN** 系统返回专用的企业微信 4xx 错误码
- **AND** `msg` 指明可安全披露的配置检查方向
- **AND** 不将该错误统一转换为 503
## ADDED Requirements
### Requirement: Error Classification at Origin Boundary
系统 MUST 在最早能够识别失败语义的业务校验或外部响应边界完成错误分类;下游 Application、Service 与 Handler 在没有新增语义时 SHALL 原样传递结构化 `AppError`
#### Scenario: 企业微信返回已知错误码
- **WHEN** 企业微信响应包含可识别的 provider 错误码与错误消息
- **THEN** 企业微信 Adapter 在记录 Integration Log 后将其映射为对应的结构化错误
- **AND** 上层不得再无条件包装为 `CodeServiceUnavailable`
#### Scenario: 未识别的企业微信失败
- **WHEN** 企业微信失败响应无法可靠判定为凭据、权限或业务配置错误
- **THEN** Adapter 将其分类为上游响应异常
- **AND** HTTP 状态码为 502
### Requirement: Server Error Message Sanitization
系统 SHALL 保留全局 5xx 响应脱敏,并仅向日志与既有 Integration Log 写入排障所需的底层上下文。
#### Scenario: 5xx AppError 带有内部消息
- **WHEN** 全局 ErrorHandler 处理 HTTP 502、503、504 或其他 5xx 的 `AppError`
- **THEN** 客户端收到错误码映射表中的安全通用消息
- **AND** 服务端结构化日志保留原始错误链与请求标识
#### Scenario: 4xx AppError 带有安全业务提示
- **WHEN** 全局 ErrorHandler 处理已分类的 4xx `AppError`
- **THEN** 客户端收到该错误的安全中文业务提示
- **AND** 提示不得包含数据库错误、堆栈、密钥、内部地址或未经筛选的上游原文

View File

@@ -0,0 +1,34 @@
## 1. 统一上游错误语义
- [ ] 1.1 【Infrastructure边界`pkg/errors` 错误契约,不迁移业务模块】在现有服务端错误码区间增加最少的上游响应异常错误码,登记 `allErrorCodes``errorMessages` 并映射 HTTP 502保留 `CodeTimeout`=504、`CodeServiceUnavailable`=503 与全局 5xx 脱敏,执行 `gofmt``go vet ./pkg/errors/...``go build ./pkg/errors/...` 并检查 LSP diagnostics。
## 2. 审批业务前置条件切片
- [ ] 2.1 【Application + Infrastructure边界审批发起前置条件不迁移审批状态机、资金或事件写入】将“审批场景未配置或已禁用”和“审批准备结果已失效”改为语义匹配的现有 4xx保留 Adapter、消费者和客户端未装配的真实 503检索对应调用链确认结构化错误未被二次包装执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
## 3. 企业微信 Token 连接切片
- [ ] 3.1 【Infrastructure Adapter边界Token 获取和刷新,不修改应用配置模型或缓存策略】对照仓库现有响应结构及 `/Users/break/csxjProject/wecom` 已实现代码,列出可证实的凭据、可信 IP 与权限类 provider code通过代码引用检索确认每个分类都有明确来源不依据模糊中文包含关系猜测。
- [ ] 3.2 【Infrastructure Adapter】在 Token 原始响应边界映射已知配置错误为企业微信专用 4xx、超时为 504、连接/HTTP 5xx/读取/响应过大/解析/未知 provider 错误为 502客户端未装配仍为 503复用现有 Integration Log不新增抽象执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
## 4. 企业微信通讯录同步切片
- [ ] 4.1 【Infrastructure Adapter边界成员/部门同步,不修改成员映射、数据库写入或应用可见范围配置】对照 `/Users/break/csxjProject/wecom` 的现有通讯录实现整理可见范围、凭据、权限、超时、HTTP/响应异常的可证实分类;通过代码引用检索确认 provider code 与分支来源。
- [ ] 4.2 【Infrastructure Adapter】移除 DirectoryClient `completeFailed` 的无条件 503将已知可见范围/凭据/权限失败映射为专用 4xx、超时映射为 504、未知上游失败映射为 502未装配客户端保留 503保持 Integration Log 完成记录先于错误返回,执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
## 5. 企业微信审批外部调用切片
- [ ] 5.1 【Infrastructure Adapter边界模板 inspect不修改模板存储、字段映射或场景配置】纠正模板详情客户端的 HTTP、读取、响应大小、解析及 provider 失败分类,已知模板授权/凭据错误为专用 4xx、超时为 504、未知上游异常为 502、未装配为 503执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
- [ ] 5.2 【Infrastructure Adapter边界审批提交和附件上传不修改审批状态机、附件存储或 Asynq 重试策略】纠正审批提交客户端和附件上传客户端的凭据/权限、超时与未知上游失败分类,保留未装配能力的 503执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
- [ ] 5.3 【Infrastructure Adapter边界审批详情与批量审批单号读取不修改恢复任务游标、审批回调或业务决策】纠正审批详情、审批单号客户端的凭据/权限、超时、HTTP/响应异常分类,保留客户端/任务未装配和恢复流程自身能力缺失的 503执行 `gofmt`、相关包 `go vet`/`go build` 并检查 LSP diagnostics。
## 6. 全量 503 产生点收口
- [ ] 6.1 【Infrastructure + Application边界仅本 change 触及的错误产生与传递路径】重新枚举 `internal/` 全部 `CodeServiceUnavailable` 产生点,逐项记录“真实能力不可用保留 503”或迁移到 4xx/502/504禁止机械替换发现未被 25 组覆盖的误用时在原始语义边界修正,并执行所属包 `gofmt``go vet`/`go build` 与 LSP diagnostics。
- [ ] 6.2 【Handler/API】静态检查受影响 Handler 对结构化 `AppError` 原样返回给全局 ErrorHandler未自行拼接底层错误核对企业微信连接、通讯录同步、模板 inspect 和代理充值调用链最终保留统一响应结构及安全中文消息,并检查 Handler LSP diagnostics。
## 7. 文档、审计决定与最终验证
- [ ] 7.1 【文档】在 `docs/feature-503-error-semantics/` 编写中文变更总结,列出 BREAKING HTTP 状态变化、客户端迁移方式、发布与整体回滚步骤,并增量更新 README确认未新增 Handler因此 `cmd/api/docs.go``cmd/gendocs/main.go` 无注册变更,检查文档链接与格式。
- [ ] 7.2 【审计】增量更新 `.scratch/tech-global-audit/审计覆盖基线.md`Audit Event、Domain Ledger、Outbox 均登记 N/A 理由Integration Log 继续复用且不得写入密钥或未经筛选的敏感响应;通过代码检索核对所有企微外部调用失败仍完成既有 Integration Log并检查修改文件 LSP diagnostics。
- [ ] 7.3 【最终门禁】执行全量 `gofmt`、受影响包 `go vet``go build` 与 LSP diagnostics重新审计 `CodeServiceUnavailable` 清单,确认无已知误用、无编译或静态诊断错误。明确不新增、不运行单元测试、集成测试、接口测试及外部真实企微联调。