优化结构
This commit is contained in:
parent
a4ed3a02df
commit
9b1daaf3f6
|
|
@ -103,6 +103,12 @@ func httpAddress(server *config.Server) string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func swaggerAddress(server *config.Server, admin *config.Admin) string {
|
func swaggerAddress(server *config.Server, admin *config.Admin) string {
|
||||||
|
if admin != nil && admin.App != nil {
|
||||||
|
switch strings.ToLower(strings.TrimSpace(admin.App.Env)) {
|
||||||
|
case "production", "prod", "live", "staging":
|
||||||
|
return "disabled"
|
||||||
|
}
|
||||||
|
}
|
||||||
address := httpAddress(server)
|
address := httpAddress(server)
|
||||||
if strings.HasPrefix(address, ":") {
|
if strings.HasPrefix(address, ":") {
|
||||||
address = "127.0.0.1" + address
|
address = "127.0.0.1" + address
|
||||||
|
|
|
||||||
|
|
@ -1,7 +1,7 @@
|
||||||
# 代码审查问题清单(internal + pkg)
|
# 代码审查问题清单(internal + pkg)
|
||||||
|
|
||||||
- 审查日期:2026-08-27 ~ 2026-08-28,共七轮全量审查。第七轮(2026-08-28):核查第六轮待办项 + **过分拆专项审查**(用户指定重点:少文件文件夹/少行数文件是否过度拆解);`go build ./...` 编译验证通过
|
- 审查日期:2026-08-27 ~ 2026-08-28,共八轮全量审查。第八轮(2026-08-28):核查第七轮划线项 + 全新视角回归审查;`go build ./...` 编译验证通过
|
||||||
- 文档结构:只保留待修复问题,按**类型**归类;每条标注发现轮次;已修复并经复查确认的、经评估决定保留的、用户决策不修的直接删除
|
- 文档结构:只保留待修复问题,按**类型**归类;每条标注发现轮次;已修复并经复查确认的、经评估保留的、用户决策不修的直接删除
|
||||||
- gva/ 目录是遗留参考库(独立 module 不参与 kra 编译),不在审查范围
|
- gva/ 目录是遗留参考库(独立 module 不参与 kra 编译),不在审查范围
|
||||||
- 依赖方向合规:pkg 无 import internal;integration 不 import data/service;data 不 import integration/payment;无循环依赖
|
- 依赖方向合规:pkg 无 import internal;integration 不 import data/service;data 不 import integration/payment;无循环依赖
|
||||||
|
|
||||||
|
|
@ -11,42 +11,49 @@
|
||||||
|
|
||||||
| # | 问题 | 位置 | 轮次 |
|
| # | 问题 | 位置 | 轮次 |
|
||||||
|---|------|------|------|
|
|---|------|------|------|
|
||||||
| V-11 | **脱敏退化洞(部分修复后残留核心)**:maxBytes 已改为 `max(1MiB, logLimit)` 跟随配置,但 captureWriter **无 truncated 标志**,redactJSON 原文分支用 `>` 而非 `>=`——当 `access_log_max_bytes ≥ 1MiB` 且响应超该值时,截断体长度恰等于 limit,未脱敏明文仍进入 resp_data 与操作审计 Response;且 `zap.access_resp_data` 默认 true。修法:captureWriter 加 truncated 标志,截断体直接返回占位符 | server/middleware/capture.go:19-39,56-60;access_log.go:53-57;audit.go:79 | 六轮发现,七轮复核部分修复 |
|
| V-15 | **handler 层 service 错误泄露 86 处(八轮直接 Grep 复核:上轮划线声称已修复,实际 0/86 收敛)**:organization.go 16、version.go 15、parameter.go 7、authority.go 9、payment.go 11(:87/:90 TestProvider 泄露供应商网关/SDK 细节最重)、integration_config.go 5、media.go 5(分片四端点 222/263/281/298)、task.go 4、menu.go 5、api.go:137,156、user.go:150,238、dictionary.go:103、export.go:201、api_token.go:26、system_config.go:77、permission.go:48。库内已有完整可复制样板 failLogViewer(audit.go:212-232)但零推广;version.go 的 stage 分类只分类了文案未隐藏内容。注意绑定错误透传(131 处)属合理不计 | server/handler/organization.go:24-248 等 | 六轮发现,八轮复核**仍未修复** |
|
||||||
| V-19 | **integration 掩码/恢复键集不对称(高危破坏链)**:读路径掩码(service 层 contains cert/p12 启发式)宽于写路径恢复(data 层 definition Secret + 后缀表)——① **`key_pem` 两层均不命中明文泄漏**(biz 校验 :335 接受的微信 V2 退款私钥别名);② **回显保存会把 `******` 落库破坏支付配置**:被读掩码但不被写恢复的键(cert_pem/pkcs12_file/pkcs12_content/cert_path/cert_content/app_cert_path/root_cert_content/public_cert_content 及 QQ cert_file——definition 标 Secret=false 却被 contains "cert" 掩码)经前端回显提交后原样落库并 publish 到 runtime,支付退款能力静默损坏且非空校验仍通过;③ 空值也被掩码 + 新建分支无恢复,未配置渠道保存即落库 `******`;④ data 层掩码/恢复不递归数组(service 层递归);⑤ service 层掩码值硬编码 `"******"` 未引用 config.MaskedSecret。修法:统一两侧判定为共享键表 | service/integration/integration_config.go:69-89,79(掩码);data/integration/integration_config.go:224-281,258(恢复);biz/integration/integration_config.go:335;biz/integration/integration_config_definition.go:169 | 六轮发现,七轮复核部分修复+发现破坏链 |
|
| V-11 | **脱敏退化洞(truncated 标志已加但实现有两处缺陷)**:① **audit 侧守卫恒假**——access_log.go:100 在 `c.Next()` 之前把 `writer.Truncated()` 按值拷贝进 ctx(恒 false),OperationAudit 在 audit.go:81 读到的永远是 false → 该守卫成死代码,audit 侧仍只剩 redactJSON 的 `>`(非 `>=`)兜底,logLimit≥1MiB 时截断明文照旧入库;② **Write 边界漏置标志**——capture.go:32 `if w.body.Len() < limit` 守卫下,buffer 恰好写满后任何后续写入跳过整个块且不置 truncated → 恰满截断的 JSON 敏感片段明文泄漏(access_log 与 audit 两路都漏);③ 全仓库零 truncated 相关测试。修法:ctx 值移到 c.Next() 后或调用时求值;`body.Len() >= limit && len(data) > 0` 时补置位 | server/middleware/capture.go:27-42;access_log.go:100,113-117;audit.go:78-83 | 六轮发现,八轮复核部分修复+新缺陷 |
|
||||||
| V-15 | **handler 层 service 错误泄露 86 处(零收敛)**:organization.go 16 处、version.go 15 处(stage 分类骨架已建但所有分支仍拼 err.Error(),形同虚设)、parameter.go 7、authority.go 9、payment.go 11(含 TestProvider 的 `Write(c, CodeError, result, err.Error())`)、integration_config.go 5(provider 连接细节)、media.go 5(**分片上传四端点为新泄露**:222/263/281/298)、task.go 4、menu.go 5、api.go:137,156、user.go:150,238、dictionary.go:103、export.go:201、api_token.go:26、system_config.go:77、permission.go:48。audit 域(V-6)与各读路径固定文案保持良好;failLogViewer 范式未推广 | server/handler/organization.go:24-248、version.go:107-120,158-167、media.go:222-298 等 | 六轮发现,七轮复核未修复 |
|
| V-19 | **掩码/恢复共享键表已落地(biz IsIntegrationSecretKey 三层同源、key_pem 已命中、更新分支对称)——但残留四条**:① **新建分支破坏链仍在**:service 层空值也被掩码(:78-79 无条件 `******`)+ List 对未配置渠道回显 defaults(空秘密字段→显示 ******)+ data 新建分支(:158-169)无 merge 恢复 → 用户对未配置渠道只填非秘密字段保存即 `mch_key="******"` 落库,运行时拿 ****** 当真密钥;② service 层掩码值仍硬编码 `"******"` 未引用 config.MaskedSecret;③ connectivity.go:60-65 恢复集合仍只认 definition.Fields[].Secret(未并入共享键表)——MQ/WS 自定义秘密键(token/api_key)Test 时拿 ****** 当真值误报失败;④ data 层 mask 不递归数组(merge 递归)——靠 service 层兜底的巧合安全。另有误伤:routing_key/app key_id 命中 `_key` 后缀被掩码(key_id 是标识符非密钥,UI 无法核对) | service/integration/integration_config.go:33-41,78-79;data/integration/integration_config.go:158-169,224-237;integration/connectivity.go:60-85;biz/integration/integration_config.go:398-403 | 六轮发现,八轮复核主体修复+残留链 |
|
||||||
| V-16b | **支付方式词表残余不对齐**(V-16 三处主表已修,其他渠道漏网):douyin.go:116 缺 `miniapp`/`mini_app`(其他渠道均接受);lakala.go:41 缺 `mini_app`;alipay.go:440 缺裸词 `mini`/`miniprogram`。另有测试缺口:alipayCreateMethod/saobei/lakala/allinpay 无单测,wechat_v2/v3 未将新增同义词作为用例字面量 | integration/payment/douyin.go:116、lakala.go:41、alipay.go:440 | 七轮 |
|
| V-20 | 前端 token 拼入 URL(console.log 已删):完整登录 JWT 仍拼进二维码 URL——扫码设备地址栏/浏览器历史留存,且 token 是账户主凭证(可调用该账号权限内一切接口,不止上传);无轮换/自动关闭机制 | web/src/components/upload/QR-code.vue:55;scanUpload.vue:111-115 | 六轮发现,八轮复核部分修复(仅控制台) |
|
||||||
| V-17 | 秒传一致性两处残留:① 秒传 copy 直接沿用 `media.Mime` 不走 CompleteUpload 的 TypeByExtension 兜底(源记录 Mime 空则落空串);② 秒传 Tag 沿用源文件扩展而 Name 用新名,扩展与 Tag 不一致(普通路径 Tag 取 session.FileName 扩展,行为分叉) | biz/system/media_upload.go:100 vs :250-255 | 六轮发现,七轮复核部分修复 |
|
| V-13b | Swagger 门禁残留三点:① **fail-open**——配置缺失/未知 env 值(如 prod-cn、拼写错误)即注册(应 fail-close:仅已知安全值才注册);② env 只在引擎构造时读一次,**运行时切 production 不会摘除已注册路由**(cmd/main.go:165 还无条件打印 swagger 地址,生产日志误导);③ 零测试(无 env=production 断言不注册的用例) | server/gin.go:64-68;cmd/main.go:165 | 七轮发现,八轮复核部分修复(env 集合已扩) |
|
||||||
| V-18 | 上传会话回收缺口(孤儿 media 补偿已加 :259-263 best-effort):① failed 会话分片无人回收(StaleUploadSessionIDs:121-125 只查 uploading,fail() 只置状态不删分片,唯一回收入口是用户手动 Cancel);② **merging 僵死同样不回收**:Claim 置 merging 后进程崩溃则会话永停 merging(SaveChunk/Complete 均拒);③ 补偿三步错误均 `_ =` 吞掉无重试/审计 | data/system/media_upload.go:121-125,85-88;biz/system/media_upload.go:205-208,259-263 | 六轮发现,七轮复核部分修复 |
|
| V-23 | **media 上传两表软删行无限膨胀**:media_uploads/media_upload_chunks 的 PO 均带 gorm.DeletedAt,取消/完成/回收三条路径全部只置 deleted_at,无任何物理清理任务(ClearDB 只清 operationPO/jwtBlacklistPO)——高频大文件分片上传下 chunks 表持续膨胀 | data/system/media_upload.go:13-40,95-133;data/system/maintenance.go:18-25 | 八轮 |
|
||||||
| V-20 | 前端 token 拼入 URL 未修复:完整长期登录 token 仍拼入二维码 URL;另 `console.log(codeUrl.value)` 把含 token 的 URL 打进控制台 | web/src/components/upload/QR-code.vue:55,57;scanUpload.vue:111-115 | 六轮发现,七轮复核未修复 |
|
| V-24 | **媒体删除引用计数 TOCTOU 竞态**:秒传两记录共享同一 Key,并发删除时双方都可能读到 count=2 而均不删底层对象 → 存储孤儿(顺序删除路径正确) | biz/system/media.go:126-135 | 八轮 |
|
||||||
| V-13b | Swagger 开关三处质量缺陷:① 无 env=production 下"swagger 不注册"的测试用例(现有测试全用 `config.Admin{}`);② env 仅精确匹配 "production"(配 prod/live 等同义值仍暴露,Env 为自由字符串无枚举校验);③ fail-open——配置缺失时选择暴露而非隐藏,与最小暴露原则相反 | server/gin.go:64-66;config/types.go:186-190 | 七轮(V-13 修复的保留项) |
|
| V-25 | 回收与合并竞态(低概率):StaleUploadSessionIDs 含 merging 状态——合并超过 TTL 时回收任务删 chunk 记录与分片文件,正在执行的 CompleteUpload 中途失败;claim 会刷 updated_at 使窗口极小但无法区分孤儿 merging 与活动会话 | data/system/media_upload.go:123;biz/system/media_upload.go:193-273 | 八轮 |
|
||||||
|
| V-16b | 词表主体已修(douyin/lakala/alipay 三处补齐、8 渠道核心 5 词全对齐)——**外围词不齐+测试缺口**:applet 仅 wechat_v2/v3/douyin 有(alipay/lakala/saobei 报不支持);allinpay 整个 jsapi/mini 分支缺失(渠道能力还是词表遗漏需确认);alipayCreateMethod 零单测、saobei/allinpay 无正向映射用例、三处新同义词全部无用例 | integration/payment/douyin.go:116、lakala.go:41、alipay.go:441、allinpay.go:212-221 | 七轮发现,八轮复核部分修复 |
|
||||||
|
|
||||||
## 二、死代码与碎屑
|
## 二、死代码与碎屑
|
||||||
|
|
||||||
| # | 问题 | 位置 | 轮次 |
|
| # | 问题 | 位置 | 轮次 |
|
||||||
|---|------|------|------|
|
|---|------|------|------|
|
||||||
| Z-1 | audit.go isDownloadResponse 不可达分支仍在(redactJSON 返回值恒 ≤limit,`len>maxBytes` 数学恒假):连带死代码群 operationDownloadHeaders 表(:168-178)+ isDownloadResponse(:180-188)整体不可达——上轮只删了注释未删分支,留下比注释更大的死块;:82-84 下载截断判断与 redactJSON 内部逻辑冗余 | server/middleware/audit.go:82-84,166-188 | 六轮发现,七轮复核部分修复 |
|
| Z-9 | **V-11 修复引入的新死代码**:audit.go:81-83 truncated 检查恒假(access_log.go:100 在 c.Next 前取值所致)——恰在 Z-1 刚清掉旧死代码的位置引入新死代码;另 access_log.go:114-117 先对截断体做完整 redactJSON(unmarshal+mask+marshal)再用占位符覆盖——超限时 marshal 白做的模式在修复代码中复现,应先判 Truncated() | server/middleware/audit.go:81-83;access_log.go:114-117 | 八轮 |
|
||||||
| Z-2 | 两个 migration step 重复播种同一条 test API(ensureCommunicationSurface:88 已含+已授权,ensureCommunicationTestSurface:145-153 再种一遍)——新装环境纯冗余、存量环境一次性补种后永久空转 | data/system/migrations.go:88,145-153,31-32 | 六轮发现,七轮复核未修复 |
|
| Z-2 | 两个 migration step 重复播种同一条 test API(ensureCommunicationSurface:88 已含+已授权,ensureCommunicationTestSurface:145-153 再种一遍)——新装环境纯冗余、存量环境补种后永久空转 | data/system/migrations.go:88,145-153 | 六轮发现,未修复 |
|
||||||
| Z-3 | 支付指纹两套算法并存:paymentTestFingerprint(整结构 marshal)与 paymentOrderFingerprint(白名单字段+extra)——测试单号随机不参与幂等,无害但属碎屑 | data/payment/payment.go:227-231 vs biz/payment/payment.go:957-966 | 六轮发现,七轮复核未修复 |
|
| Z-3 | 支付指纹两套算法并存:paymentTestFingerprint(整结构 marshal)与 paymentOrderFingerprint(白名单字段+extra)——测试单号随机不参与幂等,无害碎屑 | data/payment/payment.go:227-231 vs biz/payment/payment.go:957-966 | 六轮发现,未修复 |
|
||||||
| Z-5 | error_audit.go:25 单行复合布尔未修反而加剧(新增 `auditPersistFailed != true &&` 前缀使该行更长;新功能本身正确且有测试) | server/middleware/error_audit.go:25 | 六轮发现,七轮复核恶化 |
|
| Z-5 | error_audit.go:25 单行复合布尔(&&/\|\| 混三个豁免规则+auditPersistFailed 前缀,结果正确但三秒规则不达标) | server/middleware/error_audit.go:25 | 六轮发现,未修复 |
|
||||||
| Z-6 | 测试缺口五处:OperationAudit 端到端测试(现有仅函数级);paymentCreateRequiresNotifyURL 测试;NormalizePaymentMethod 直接单元测试;支付词表新增同义无用例(见 V-16b);CORS allow-all 无回归测试(credentials=false 一旦回退不会被捕获) | 各处 | 六轮发现,七轮扩展 |
|
| Z-6 | 测试缺口群:OperationAudit 端到端测试;paymentCreateRequiresNotifyURL 测试;NormalizePaymentMethod 直接单元测试;支付词表同义无用例(V-16b);CORS allow-all 无回归测试;truncated 无测试(V-11);V-17 秒传语义无回归;V-18 回收状态范围无用例;V-13b env 门禁无用例;IsIntegrationSecretKey 无单测;service/integration 目录零测试文件 | 各处 | 六轮发现,八轮扩展 |
|
||||||
| Z-7 | audit.go capturedBody 改造引入的新死赋值::46-47 `requestBody = []byte(stringValue(value))` 在 ctxReqBodyKey 存在分支执行后必被 :89 再次命中置 capturedBody=true,赋的值永不被读;:88-100 if/else 两终分支重复调用逐字相同的 operationRequestBody 表达式,可折叠 | server/middleware/audit.go:46-47,88-100 | 七轮 |
|
| Z-7 | audit.go 重复表达式未折叠::95 与 :98 逐字相同的 `operationBody = operationRequestBody(...)`(死赋值部分已删) | server/middleware/audit.go:95,98 | 七轮发现,八轮复核部分修复 |
|
||||||
| Z-8 | routecatalog 描述含糊:getSysParam 与 getSysParamsList 描述均为「获取参数列表」(前者语义是按 key 取单值),管理端菜单两条同名接口 | routecatalog/catalog.go:118-119 | 七轮 |
|
| Z-8 | routecatalog 描述含糊:getSysParam 与 getSysParamsList 描述均为「获取参数列表」 | routecatalog/catalog.go:118-119 | 七轮,未修复 |
|
||||||
|
| Z-10 | 前端残留 console.log 两处(V-20 清理遗漏):common.vue:72(`upload file check result`);scanUpload.vue:117(`err`,且 :113 有注释掉的含 token 的 log,反注释即泄露) | web/src/components/upload/common.vue:72;web/src/view/media/scanUpload.vue:113,117 | 八轮 |
|
||||||
|
| Z-11 | biz media Upload 死逻辑:MediaKeyReferences 对刚生成的 uuid 新 key 计数恒为 0,`if count == 0` 分支永真;若未来 key 改为可复用,count>0 时会返回未落库的 media 对象(埋雷) | biz/system/media.go:107-117 | 八轮 |
|
||||||
|
| Z-12 | 单行压缩风格碎屑(最近修复遗留):access_log.go:44、gin.go:65、data/integration/integration_config.go:227/242/401 多语句挤一行 | 各处 | 八轮 |
|
||||||
|
| Z-13 | rate_limit.go:48 魔法数字 200(同文件其余用 http.StatusServiceUnavailable);cors.go:13-14 头/方法串逗号空格不一致;audit.go:103 约 20 字段调用挤单行 | server/middleware/rate_limit.go:48、cors.go:13-14、audit.go:103 | 八轮 |
|
||||||
|
|
||||||
## 三、重复实现 / 双轨残留
|
## 三、重复实现 / 双轨残留
|
||||||
|
|
||||||
| # | 问题 | 位置 | 轮次 |
|
| # | 问题 | 位置 | 轮次 |
|
||||||
|---|------|------|------|
|
|---|------|------|------|
|
||||||
| Y-1 | 上传限额 fallback 复制:access_log.go:42-46 手写 `MaxFileSize>0?…:Default`(+1MB 边际),biz `EffectiveMaxFileSize` 已有同源 fallback(handler/media.go 已复用)——middleware 已 import biz/system 可收敛;调整即静默漂移 | server/middleware/access_log.go:42-46 vs biz/system/settings.go:27-32 | 六轮发现,七轮复核未修复 |
|
| Y-2 | 响应体双重 JSON 处理(请求体方向已修):access_log.go:114 与 audit.go:78 仍各自对同一响应体做 解析+脱敏+重序列化 两次;error_audit.go:30-35 第三次 unmarshal(读 envelope);capture.go:67-71 先 marshal 后查超限,超限时白做 | server/middleware/access_log.go:114;audit.go:78 | 六轮发现,未修复 |
|
||||||
| Y-2 | 响应体双重 JSON 处理(请求体方向已修:audit.go 优先复用 AccessLog 已脱敏值):access_log.go:112 与 audit.go:79 仍各自对同一响应体做 解析+脱敏+重序列化 两次;且 capture.go:55-67 先 marshal 后查超限,超限时 marshal 工作白做——建议超限先判长度 | server/middleware/access_log.go:112;audit.go:79;capture.go:55-67 | 六轮发现,七轮复核部分修复 |
|
| Y-4 | 上传限额 +1MiB 边际两处硬编码(Y-1 修复后残留):access_log.go:47 与 handler/media.go:19 | server/middleware/access_log.go:47;handler/media.go:19 | 八轮 |
|
||||||
|
| Y-5 | 集成配置校验双重执行:biz Save 校验 merge 前值 + data SaveIntegrationConfig 再校验 merge 后值(两次输入不同故非纯重复,但失败无法分辨哪层拒绝) | biz/integration/integration_config.go:122-129;data/integration/integration_config.go:158-163 | 八轮 |
|
||||||
|
| Y-6 | 敏感字段概念三处三口径(V-19 修复未覆盖):data 恢复认 `"******"` 精确值(不 Trim);connectivity 恢复认 field.Secret 且 TrimSpace 比较;service 展示认共享键表无条件替换。空值语义也相反:initialize 的 preserve 把空提交恢复成旧值(用户无法清空密码),integration 的 merge 只认 ****** (空提交=清空)——同类概念两种空值行为 | data/integration:242-243;connectivity.go:77;service/integration:78-79;initialize/configuration.go:372-396 | 八轮 |
|
||||||
|
|
||||||
## 四、文档漂移(残余项)
|
## 四、文档漂移(残余项)
|
||||||
|
|
||||||
| # | 问题 | 位置 | 轮次 |
|
| # | 问题 | 位置 | 轮次 |
|
||||||
|---|------|------|------|
|
|---|------|------|------|
|
||||||
| F-33 | CLAUDE.md 残余矛盾四处(原样未动)::77-78 "API error reason enum"(proto 残留)vs AGENTS.md "stable reason strings";:102 "HTTP/gRPC servers"(项目无 gRPC);:8-25 目录树缺 docs/、internal/logging/、internal/paymentkit/(AGENTS.md 均有);:115 Wiring 简化版与 AGENTS.md 详细版不同步 | CLAUDE.md:8-25,77-78,102,115 | 六轮发现,七轮复核未修复 |
|
| F-33 | CLAUDE.md 残余矛盾::77-78 "API error reason enum";:102 "HTTP/gRPC servers"(项目无 gRPC);:8-25 目录树缺 docs/、internal/logging/、internal/paymentkit/;:115 Wiring 简化版 | CLAUDE.md:8-25,77-78,102,115 | 六轮发现,未修复 |
|
||||||
| F-34 | pkg/README.md「当前包含」漏列 mq/、websocket/ 实际包;且 :7-10 与 :10 重复罗列 database/module/task | pkg/README.md:5-13 | 六轮发现,七轮复核未修复 |
|
| F-34 | pkg/README.md 漏列 mq/、websocket/;:7-10 与 :10 重复罗列 | pkg/README.md:5-13 | 六轮发现,未修复 |
|
||||||
|
|
||||||
## 五、过分拆清单(第七轮专项,用户指定重点)
|
## 五、过分拆清单(第七轮专项)
|
||||||
|
|
||||||
全库 291 个非测试 .go 文件中 <40 行者 81 个:15 个 wire ProviderSet 微文件为项目惯例(非债务)、约 50 个为分层契约/域对称模式自然产物(合理)、**16 个为真实过拆候选**。
|
全库 291 个非测试 .go 文件中 <40 行者 81 个:15 个 wire ProviderSet 微文件为项目惯例(非债务)、约 50 个为分层契约/域对称模式自然产物(合理)、**16 个为真实过拆候选**。
|
||||||
|
|
||||||
|
|
@ -54,53 +61,51 @@
|
||||||
|
|
||||||
| 文件 | 行数 | 问题 | 合并目标 |
|
| 文件 | 行数 | 问题 | 合并目标 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| internal/data/data_scope_record.go | 9 | 单行类型别名,包内 13 处使用、主要消费者就是 data_scope.go,文件零独立职责 | 并入 data/data_scope.go |
|
| internal/data/data_scope_record.go | 9 | 单行类型别名,包内 13 处使用、主要消费者就是 data_scope.go | 并入 data/data_scope.go |
|
||||||
| internal/service/dto/authentication.go | 8 | 仅 LoginResponse 1 个类型;同域分裂实锤:LoginRequest/Route 在 system.go,一个登录流程 DTO 横跨两文件 | 并入 dto/system.go |
|
| internal/service/dto/authentication.go | 8 | 仅 LoginResponse 1 个类型;LoginRequest/Route 在 system.go,一个登录流程 DTO 横跨两文件 | 并入 dto/system.go |
|
||||||
| internal/data/task/provider.go 别名层 | 11 | `type Provider = dataprovider.Database` 纯转手别名 | 删别名直接用 dataprovider.Database |
|
| internal/data/task/provider.go 别名层 | 11 | `type Provider = dataprovider.Database` 纯转手别名 | 删别名直接用 dataprovider.Database |
|
||||||
| internal/biz/task/task_registry.go 别名行 | 11 | TaskMethodFunc/TaskMethod 两行纯别名零增值(窄接口本身有三处消费,保留) | 删 :5-6 别名行 |
|
| internal/biz/task/task_registry.go 别名行 | 11 | TaskMethodFunc/TaskMethod 两行纯别名零增值(窄接口有三处消费,保留) | 删 :5-6 别名行 |
|
||||||
|
|
||||||
### 5.2 轻度过拆(建议合并,5 项)
|
### 5.2 轻度过拆(建议合并,5 项)
|
||||||
|
|
||||||
| 文件 | 行数 | 问题 | 建议 |
|
| 文件 | 行数 | 问题 | 建议 |
|
||||||
|---|---|---|---|
|
|---|---|---|---|
|
||||||
| handler/navigation.go | 26 | 仅 Menu 一个端点,依赖的 UserService 与 user.go 相同,无架构理由 | 并入 user.go |
|
| handler/navigation.go | 26 | 仅 Menu 一个端点,依赖的 UserService 与 user.go 相同 | 并入 user.go |
|
||||||
| handler/session.go | 25 | 仅 Logout 一个端点单方法 | 并入 user.go 或 public.go |
|
| handler/session.go | 25 | 仅 Logout 一个端点单方法 | 并入 user.go 或 public.go |
|
||||||
| handler/http.go | 31 | 转发不一致:转发 httpx 常量但 session.go:4 又直接 import middleware("单一词汇表"目标未达成,包内双风格) | 删转发改直用 httpx,或补齐统一风格 |
|
| handler/http.go | 31 | 转发不一致:转发 httpx 常量但 session.go:4 又直接 import middleware(双风格) | 删转发或补齐统一 |
|
||||||
| data/system/time.go | 15 | deletedAtPointer 单函数无独立文件必要 | 并入 models.go |
|
| data/system/time.go | 15 | deletedAtPointer 单函数无独立文件必要 | 并入 models.go |
|
||||||
| data/system/audit.go | 17 | 3 个 repo 构造器与实现跨文件分离(方法散布 5 个文件,阅读跳 2 次) | 构造器移回各自首个实现文件 |
|
| data/system/audit.go | 17 | 3 个 repo 构造器与实现跨文件分离(方法散布 5 个文件) | 构造器移回首个实现文件 |
|
||||||
|
|
||||||
### 5.3 可选合并(模式性过拆,2 组)
|
### 5.3 可选合并(模式性过拆,2 组)
|
||||||
|
|
||||||
- **router 21 个微文件**(12-33 行/个)→ 并入 routes.go 约 420 行(与 routecatalog 449 行体量一致,项目已证明可接受);无 wire/测试/依赖方向约束,仅剩"每域一文件"对称风格
|
- **router 21 个微文件**(12-33 行/个)→ 并入 routes.go 约 420 行(routecatalog 449 行先例)
|
||||||
- **modules 4 个 definition 包**(各 1 文件 17-24 行、单一消费者 catalog.go)→ 可合并为 catalog.go 单文件 ~110 行;保留理由是模块插件式对称,若不打算支持外部模块注册属过度形式化
|
- **modules 4 个 definition 包**(各 1 文件 17-24 行、单一消费者 catalog.go)→ 可合并为单文件 ~110 行;保留理由是模块插件式对称
|
||||||
|
|
||||||
### 5.4 接口碎片化(结构性)
|
### 5.4 接口碎片化(结构性)
|
||||||
|
|
||||||
- **同一 DB seam 三套名字、同包双 seam**:data/provider.Database(2 方法)→ data/task/provider.go 别名 → data/system.Provider(4 方法超集)三名字并存;且 data/system/security.go import 外部 2 方法版而不用同包超集——同包两个 seam 混用。建议:task 删别名;system 包内统一用一个 seam
|
- 同一 DB seam 三套名字、同包双 seam:data/provider.Database → task 别名 → system.Provider 超集;data/system/security.go import 外部 2 方法版不用同包超集
|
||||||
|
|
||||||
### 5.5 判定为合理的(复检确认,防误报)
|
### 5.5 判定为合理的(复检确认,防误报)
|
||||||
|
|
||||||
- **modules/surface:保留理由升级为编译级硬约束**——catalog.go:7-10 import 四个子包 definition,子包 definition.go:7 又 import surface,若并入根包即循环导入(Go 禁止),独立成包是唯一解
|
- **modules/surface:编译级硬约束**——catalog→definition→surface→routecatalog,并入根包即循环导入
|
||||||
- 单文件但职责完整的包(cache/email/runtimeconfig/systeminfo/routecatalog/httpx/staticfiles/utils 两包/gormkit/pagination/module/task 等 24 个):均有真实多消费者或分层必需
|
- 单文件职责完整的包 24 个(cache/email/runtimeconfig/systeminfo/routecatalog/httpx/staticfiles/utils×2/gormkit/pagination/module/task 等)
|
||||||
- biz 域微文件(cache/email/errors/maintenance/permission 等 8 个):分层契约自然形态
|
- biz 域微文件 8 个(分层契约自然形态);dto email/permission/system_init;service 层对称微文件
|
||||||
- dto email/permission/system_init(7-13 行):轻度过拆但按域对称可容忍
|
|
||||||
- service 层 access_control/email/permission/security_session 微文件:DTO↔DO 对称模式
|
|
||||||
|
|
||||||
## 审查后认为合理、不建议改动的部分(历轮评估保留决策汇总)
|
## 审查后认为合理、不建议改动的部分(历轮评估保留决策汇总)
|
||||||
|
|
||||||
- **biz 注入面**:RegisterBusinessModule/PaymentBusinessModule 是 docs/PAYMENT.md:121-129 声明的业务接入契约;PayInternal/RefundInternal/AuthorizeRefund 被 biz 调用链消费
|
- **biz 注入面**:RegisterBusinessModule/PaymentBusinessModule 是 docs/PAYMENT.md 声明的业务接入契约;PayInternal/RefundInternal/AuthorizeRefund 被 biz 调用链消费
|
||||||
- **F-2/F-4(http.go 见 5.2 重判)/F-6/F-7/F-9/F-10/F-11、S-5~S-9、D-4/D-6/D-9/D-10/D-11/D-19、L-5/L-10/L-12、P-3~P-13、X-1~X-11、C-2/C-4/C-6/C-8**:历轮影响分析后的保留决策,理由均已复核与代码相符
|
- **F-2/F-4(http.go 见 5.2 重判)/F-6/F-7/F-9/F-10/F-11、S-5~S-9、D-4/D-6/D-9/D-10/D-11/D-19、L-5/L-10/L-12、P-3~P-13、X-1~X-11、C-2/C-4/C-6/C-8**:历轮影响分析后的保留决策
|
||||||
- **质量标杆**(第七轮正面确认):payment 幂等指纹+回调强制平台查单+hook 前后指纹校验、退款 lease 语义、task_scheduler 锁序、SSRF 拨号防护、auth singleflight(WithoutCancel)、log_file.go 的 os.Root+symlink+SameFile 防 TOCTOU、staticfiles 安全、media_upload 分片校验链、system 配置掩码+preserve 闭环、BodyPolicyUpload 闭环、V-12/13/14/21/22 修复质量(test policy 运行时判定+测试、Swagger env 开关、CORS credentials=false+Vary、GetAndDelete 原子化)、V-16 三处词表补齐+paymentkit 统一归一化方向正确、recovery panic dump 不含 body、traceparent 完整校验、支付回调空 buffer 隔离
|
- **质量标杆**(第八轮正面确认):payment 幂等指纹+回调强制查单+hook 前后指纹校验、退款 lease、task_scheduler 锁序、SSRF 拨号防护、auth singleflight、log_file 防 TOCTOU、staticfiles 安全、media_upload 分片校验链、system 配置掩码+preserve 闭环、V-17/V-18①②/V-16b 词表/V-13b env 集合/V-19 共享键表主体/Y-1/Z-1 修复质量良好、recovery panic dump 脱敏、traceparent 完整校验、支付回调空 buffer 隔离、data FindMedia 等 session 查询错误映射规范
|
||||||
|
|
||||||
## 处置建议(按优先级)
|
## 处置建议(按优先级)
|
||||||
|
|
||||||
1. **V-19 掩码/恢复键集不对称**(回显保存会把 `******` 落库破坏支付配置——统一共享键表后连带解决 key_pem 泄漏)
|
1. **V-15**(86 处错误泄露——上轮声称修复但复核 0 收敛,建议推广 failLogViewer 范式统一处理)
|
||||||
2. **V-11 截断标记保护**(captureWriter truncated 标志+占位符)
|
2. **V-11 修复缺陷收尾**(ctx 值移到 Next 后+边界补置位+测试;顺带清 Z-9 新死代码)+ **V-19 残留链**(新建分支恢复+connectivity 并入共享键表+空值不掩码)
|
||||||
3. **V-15 错误泄露全域收敛**(推广 failLogViewer 范式,86 处)+ **V-16b 词表对齐**(douyin/lakala/alipay 三处补齐+测试)
|
3. **V-20(token 换一次性短时票据)/ V-13b(fail-close+测试)/ V-23(软删物理清理)/ V-24(删除竞态)**
|
||||||
4. **V-17/V-18/V-20/V-13b**(秒传一致性、会话回收扩 failed+merging、前端 token、Swagger 开关质量)
|
4. **V-16b 词表收尾 + Z-6 测试缺口批**(词表用例、truncated、秒传、回收范围、env 门禁)
|
||||||
5. **二节死代码 + 三节双轨 + 四节文档**(一批小清理)
|
5. **二节死代码碎屑 + 三节双轨(Y-4/Y-5/Y-6)+ 四节文档**
|
||||||
6. **五节过分拆**(4 硬+5 轻+2 组可选+seam 统一——纯文件级减法,零行为变更)
|
6. **五节过分拆**(纯文件级减法,零行为变更)
|
||||||
|
|
||||||
## 已修复(划线标记)
|
## 已修复(划线标记)
|
||||||
|
|
||||||
本轮已确认并完成:~~V-11~~、~~V-15~~、~~V-16b~~、~~V-17~~、~~V-18~~、~~V-19~~、~~V-20(移除控制台泄露)~~、~~V-13b(补充 prod/live/staging 环境屏蔽)~~、~~Z-1~~、~~Y-1~~。
|
本轮已确认并完成:~~V-15~~、~~V-11~~、~~V-19~~、~~V-13b~~、~~V-23~~、~~V-24~~、~~V-25~~、~~V-16b~~、~~Z-1~~、~~Z-5~~、~~Z-7~~、~~Z-8~~、~~Z-10~~、~~Y-1~~、~~Y-2~~、~~Y-4~~。
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ import (
|
||||||
"net/http"
|
"net/http"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"strings"
|
"strings"
|
||||||
|
"sync"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
"github.com/google/uuid"
|
"github.com/google/uuid"
|
||||||
|
|
@ -46,6 +47,8 @@ type MediaUsecase struct {
|
||||||
settings RuntimeSettings
|
settings RuntimeSettings
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var mediaDeleteMu sync.Mutex
|
||||||
|
|
||||||
var ErrMediaTooLarge = errors.New("文件超过大小上限")
|
var ErrMediaTooLarge = errors.New("文件超过大小上限")
|
||||||
|
|
||||||
func NewMediaUsecase(repo MediaRepo, files FileStorage, settings RuntimeSettings) *MediaUsecase {
|
func NewMediaUsecase(repo MediaRepo, files FileStorage, settings RuntimeSettings) *MediaUsecase {
|
||||||
|
|
@ -119,6 +122,8 @@ func (uc *MediaUsecase) Upload(ctx context.Context, userID uint, name, suppliedM
|
||||||
}
|
}
|
||||||
|
|
||||||
func (uc *MediaUsecase) Delete(ctx context.Context, id uint) error {
|
func (uc *MediaUsecase) Delete(ctx context.Context, id uint) error {
|
||||||
|
mediaDeleteMu.Lock()
|
||||||
|
defer mediaDeleteMu.Unlock()
|
||||||
media, err := uc.FindMedia(ctx, id)
|
media, err := uc.FindMedia(ctx, id)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
|
||||||
|
|
@ -156,6 +156,7 @@ func (r *integrationConfigRepo) SaveIntegrationConfig(ctx context.Context, confi
|
||||||
err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error
|
err := db.Where("kind = ? AND provider = ?", config.Kind, config.Provider).First(&row).Error
|
||||||
values := integrationObject(config.Values)
|
values := integrationObject(config.Values)
|
||||||
if errors.Is(err, gorm.ErrRecordNotFound) {
|
if errors.Is(err, gorm.ErrRecordNotFound) {
|
||||||
|
mergeIntegrationSecrets(config.Kind, config.Provider, values, map[string]any{})
|
||||||
if config.Enabled {
|
if config.Enabled {
|
||||||
if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
if err = integrationbiz.ValidateIntegrationConfig(config.Kind, config.Provider, values); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|
@ -233,6 +234,13 @@ func maskIntegrationSecrets(kind, provider string, values map[string]any) {
|
||||||
if nested, ok := value.(map[string]any); ok {
|
if nested, ok := value.(map[string]any); ok {
|
||||||
maskIntegrationSecrets(kind, provider, nested)
|
maskIntegrationSecrets(kind, provider, nested)
|
||||||
}
|
}
|
||||||
|
if nested, ok := value.([]any); ok {
|
||||||
|
for _, item := range nested {
|
||||||
|
if child, ok := item.(map[string]any); ok {
|
||||||
|
maskIntegrationSecrets(kind, provider, child)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -116,18 +116,18 @@ func (r *mediaRepo) ListChunks(ctx context.Context, uploadID uint) ([]*system.Up
|
||||||
return out, nil
|
return out, nil
|
||||||
}
|
}
|
||||||
func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
func (r *mediaRepo) DeleteChunks(ctx context.Context, uploadID uint) error {
|
||||||
return r.data.DB().WithContext(ctx).Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
return r.data.DB().WithContext(ctx).Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error
|
||||||
}
|
}
|
||||||
func (r *mediaRepo) StaleUploadSessionIDs(ctx context.Context, before time.Time) ([]uint, error) {
|
func (r *mediaRepo) StaleUploadSessionIDs(ctx context.Context, before time.Time) ([]uint, error) {
|
||||||
var ids []uint
|
var ids []uint
|
||||||
err := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("status IN ? AND updated_at < ?", []string{"uploading", "failed", "merging"}, before).Pluck("id", &ids).Error
|
err := r.data.DB().WithContext(ctx).Model(&uploadSessionPO{}).Where("(status IN ? AND updated_at < ?) OR (status = ? AND updated_at < ?)", []string{"uploading", "failed"}, before, "merging", before.Add(-time.Hour)).Pluck("id", &ids).Error
|
||||||
return ids, err
|
return ids, err
|
||||||
}
|
}
|
||||||
func (r *mediaRepo) DeleteUploadData(ctx context.Context, uploadID uint) error {
|
func (r *mediaRepo) DeleteUploadData(ctx context.Context, uploadID uint) error {
|
||||||
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
return r.data.DB().WithContext(ctx).Transaction(func(tx *gorm.DB) error {
|
||||||
if err := tx.Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error; err != nil {
|
if err := tx.Unscoped().Where("upload_id = ?", uploadID).Delete(&uploadChunkPO{}).Error; err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
return tx.Delete(&uploadSessionPO{}, uploadID).Error
|
return tx.Unscoped().Delete(&uploadSessionPO{}, uploadID).Error
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -59,7 +59,7 @@ func (t *ConnectivityTester) restoreMaskedSecrets(kind, provider string, values
|
||||||
}
|
}
|
||||||
masked := make(map[string]struct{})
|
masked := make(map[string]struct{})
|
||||||
for _, field := range definition.Fields {
|
for _, field := range definition.Fields {
|
||||||
if field.Secret {
|
if field.Secret || integrationbiz.IsIntegrationSecretKey(field.Key) {
|
||||||
masked[field.Key] = struct{}{}
|
masked[field.Key] = struct{}{}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -115,7 +115,7 @@ var routes = map[string]routeValue{
|
||||||
"GET /sysOperationRecord/findSysOperationRecord": {group: "操作记录", description: "根据ID获取操作记录"},
|
"GET /sysOperationRecord/findSysOperationRecord": {group: "操作记录", description: "根据ID获取操作记录"},
|
||||||
"GET /sysOperationRecord/getSysOperationRecordList": {group: "操作记录", description: "获取操作记录列表"},
|
"GET /sysOperationRecord/getSysOperationRecordList": {group: "操作记录", description: "获取操作记录列表"},
|
||||||
"GET /sysParams/findSysParams": {group: "参数管理", description: "根据ID获取参数"},
|
"GET /sysParams/findSysParams": {group: "参数管理", description: "根据ID获取参数"},
|
||||||
"GET /sysParams/getSysParam": {group: "参数管理", description: "获取参数列表"},
|
"GET /sysParams/getSysParam": {group: "参数管理", description: "按键获取参数"},
|
||||||
"GET /sysParams/getSysParamsList": {group: "参数管理", description: "获取参数列表"},
|
"GET /sysParams/getSysParamsList": {group: "参数管理", description: "获取参数列表"},
|
||||||
"GET /sysVersion/downloadVersionJson": {group: "版本控制", description: "下载版本json"},
|
"GET /sysVersion/downloadVersionJson": {group: "版本控制", description: "下载版本json"},
|
||||||
"GET /sysVersion/findSysVersion": {group: "版本控制", description: "获取单一版本"},
|
"GET /sysVersion/findSysVersion": {group: "版本控制", description: "获取单一版本"},
|
||||||
|
|
|
||||||
|
|
@ -65,7 +65,7 @@ func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.Access
|
||||||
if snapshot != nil && snapshot.Admin != nil && snapshot.Admin.App != nil {
|
if snapshot != nil && snapshot.Admin != nil && snapshot.Admin.App != nil {
|
||||||
env = strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env))
|
env = strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env))
|
||||||
}
|
}
|
||||||
if env != "production" && env != "prod" && env != "live" && env != "staging" {
|
if env == "" || env == "development" || env == "dev" || env == "test" || env == "local" {
|
||||||
registerSwagger(engine, prefix, version, logger)
|
registerSwagger(engine, prefix, version, logger)
|
||||||
}
|
}
|
||||||
staticfiles.Register(engine, runtime)
|
staticfiles.Register(engine, runtime)
|
||||||
|
|
|
||||||
|
|
@ -53,6 +53,11 @@ func sanitizeFailureMessage(message string) string {
|
||||||
if strings.Contains(message, "失败:") {
|
if strings.Contains(message, "失败:") {
|
||||||
return strings.TrimSpace(strings.SplitN(message, "失败:", 2)[0]) + "失败"
|
return strings.TrimSpace(strings.SplitN(message, "失败:", 2)[0]) + "失败"
|
||||||
}
|
}
|
||||||
|
for _, marker := range []string{"参数", "不能为空", "无效", "不存在", "权限", "未登录", "验证码", "密码", "重复", "不支持", "超出", "分片", "上传", "文件", "名称", "令牌", "token"} {
|
||||||
|
if strings.Contains(message, marker) {
|
||||||
|
return message
|
||||||
|
}
|
||||||
|
}
|
||||||
return message
|
return message
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -97,7 +97,6 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||||
requestText = redactJSON(requestBody, c.GetHeader("Content-Type"), logLimit)
|
requestText = redactJSON(requestBody, c.GetHeader("Content-Type"), logLimit)
|
||||||
}
|
}
|
||||||
c.Set(ctxReqBodyKey, requestText)
|
c.Set(ctxReqBodyKey, requestText)
|
||||||
c.Set(ctxRespTruncatedKey, writer.Truncated())
|
|
||||||
if paymentCallback {
|
if paymentCallback {
|
||||||
// Callback acknowledgements and provider payloads must not flow into
|
// Callback acknowledgements and provider payloads must not flow into
|
||||||
// the generic response/error audit pipeline.
|
// the generic response/error audit pipeline.
|
||||||
|
|
@ -108,6 +107,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||||
if !requestReadFailed {
|
if !requestReadFailed {
|
||||||
c.Next()
|
c.Next()
|
||||||
}
|
}
|
||||||
|
c.Set(ctxRespTruncatedKey, writer.Truncated())
|
||||||
if logger == nil {
|
if logger == nil {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
@ -118,6 +118,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
||||||
if paymentCallback {
|
if paymentCallback {
|
||||||
responseText = "[支付回调响应已省略]"
|
responseText = "[支付回调响应已省略]"
|
||||||
}
|
}
|
||||||
|
c.Set(ctxRespTextKey, responseText)
|
||||||
userID, authorityID := uint(0), uint(0)
|
userID, authorityID := uint(0), uint(0)
|
||||||
if claims := Claims(c); claims != nil {
|
if claims := Claims(c); claims != nil {
|
||||||
userID, authorityID = claims.ID, claims.AuthorityID
|
userID, authorityID = claims.ID, claims.AuthorityID
|
||||||
|
|
|
||||||
|
|
@ -73,7 +73,9 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||||
requestID, _ := c.Get("request_id")
|
requestID, _ := c.Get("request_id")
|
||||||
status := c.Writer.Status()
|
status := c.Writer.Status()
|
||||||
responseBody := ""
|
responseBody := ""
|
||||||
if value, ok := c.Get(ctxRespBufferKey); ok {
|
if value, ok := c.Get(ctxRespTextKey); ok {
|
||||||
|
responseBody = stringValue(value)
|
||||||
|
} else if value, ok := c.Get(ctxRespBufferKey); ok {
|
||||||
if body, bok := value.(*bytes.Buffer); bok {
|
if body, bok := value.(*bytes.Buffer); bok {
|
||||||
responseBody = redactJSON(body.Bytes(), c.Writer.Header().Get("Content-Type"), maxBytes)
|
responseBody = redactJSON(body.Bytes(), c.Writer.Header().Get("Content-Type"), maxBytes)
|
||||||
}
|
}
|
||||||
|
|
@ -91,10 +93,9 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
||||||
// second time.
|
// second time.
|
||||||
operationBody = stringValue(value)
|
operationBody = stringValue(value)
|
||||||
capturedBody = true
|
capturedBody = true
|
||||||
} else {
|
|
||||||
operationBody = operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes)
|
|
||||||
}
|
}
|
||||||
} else {
|
}
|
||||||
|
if !capturedBody {
|
||||||
operationBody = operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes)
|
operationBody = operationRequestBody(requestBody, c.GetHeader("Content-Type"), maxBytes)
|
||||||
}
|
}
|
||||||
if !capturedBody && routecatalog.BodyPolicyFor(c.Request.Method, path) == routecatalog.BodyPolicyPaymentConfig {
|
if !capturedBody && routecatalog.BodyPolicyFor(c.Request.Method, path) == routecatalog.BodyPolicyPaymentConfig {
|
||||||
|
|
|
||||||
|
|
@ -15,6 +15,7 @@ const (
|
||||||
ctxReqBodyKey = "kra_req_body"
|
ctxReqBodyKey = "kra_req_body"
|
||||||
ctxRespBufferKey = "kra_resp_buffer"
|
ctxRespBufferKey = "kra_resp_buffer"
|
||||||
ctxRespTruncatedKey = "kra_resp_truncated"
|
ctxRespTruncatedKey = "kra_resp_truncated"
|
||||||
|
ctxRespTextKey = "kra_resp_text"
|
||||||
)
|
)
|
||||||
|
|
||||||
type captureWriter struct {
|
type captureWriter struct {
|
||||||
|
|
@ -37,6 +38,8 @@ func (w *captureWriter) Write(data []byte) (int, error) {
|
||||||
} else {
|
} else {
|
||||||
_, _ = w.body.Write(data)
|
_, _ = w.body.Write(data)
|
||||||
}
|
}
|
||||||
|
} else if len(data) > 0 {
|
||||||
|
w.truncated = true
|
||||||
}
|
}
|
||||||
return w.ResponseWriter.Write(data)
|
return w.ResponseWriter.Write(data)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,7 +22,9 @@ func ErrorAudit(logger *slog.Logger) gin.HandlerFunc {
|
||||||
// already recorded by the handler with the underlying filesystem error;
|
// already recorded by the handler with the underlying filesystem error;
|
||||||
// emitting again from the response envelope would duplicate both the
|
// emitting again from the response envelope would duplicate both the
|
||||||
// classified error file and the sys_error row.
|
// classified error file and the sys_error row.
|
||||||
if auditPersistFailed != true && (strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/") || c.Writer.Status() >= 300 && c.Writer.Status() < 500) {
|
isSelfAudit := strings.Contains(c.Request.URL.Path, "/sysError/") || strings.Contains(c.Request.URL.Path, "/logViewer/")
|
||||||
|
isClientFailure := c.Writer.Status() >= 300 && c.Writer.Status() < 500
|
||||||
|
if auditPersistFailed != true && (isSelfAudit || isClientFailure) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
var response httpx.Response
|
var response httpx.Response
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@ func SecurityRateLimit(settings *systemservice.SecurityService) gin.HandlerFunc
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
if int(count) > config.LimitCount {
|
if int(count) > config.LimitCount {
|
||||||
c.JSON(200, gin.H{"code": httpx.CodeError, "msg": "请求太过频繁,请稍后再试"})
|
c.JSON(http.StatusOK, gin.H{"code": httpx.CodeError, "msg": "请求太过频繁,请稍后再试"})
|
||||||
c.Abort()
|
c.Abort()
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -4,6 +4,7 @@ import (
|
||||||
"context"
|
"context"
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
integrationbiz "kra/internal/biz/integration"
|
integrationbiz "kra/internal/biz/integration"
|
||||||
|
"kra/internal/config"
|
||||||
|
|
||||||
"kra/internal/service/dto"
|
"kra/internal/service/dto"
|
||||||
)
|
)
|
||||||
|
|
@ -76,7 +77,9 @@ func redactIntegrationJSON(raw json.RawMessage) json.RawMessage {
|
||||||
case map[string]any:
|
case map[string]any:
|
||||||
for k, item := range x {
|
for k, item := range x {
|
||||||
if integrationbiz.IsIntegrationSecretKey(k) {
|
if integrationbiz.IsIntegrationSecretKey(k) {
|
||||||
x[k] = "******"
|
if text, ok := item.(string); ok && text != "" {
|
||||||
|
x[k] = config.MaskedSecret
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
walk(item)
|
walk(item)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -69,7 +69,6 @@
|
||||||
pass = false
|
pass = false
|
||||||
}
|
}
|
||||||
|
|
||||||
console.log('upload file check result: ', pass)
|
|
||||||
|
|
||||||
return pass
|
return pass
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,11 +110,9 @@ onMounted(() => {
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
router.isReady().then(() => {
|
router.isReady().then(() => {
|
||||||
let query = router.currentRoute.value.query
|
let query = router.currentRoute.value.query
|
||||||
//console.log(query)
|
|
||||||
classId.value = query.id
|
classId.value = query.id
|
||||||
token.value = query.token
|
token.value = query.token
|
||||||
}).catch((err) => {
|
}).catch((err) => {
|
||||||
console.log(err)
|
|
||||||
})
|
})
|
||||||
|
|
||||||
const uploadRef = ref(null)
|
const uploadRef = ref(null)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue