优化结构
This commit is contained in:
parent
56f9440a6b
commit
ae5345de14
|
|
@ -9,14 +9,16 @@ agents must follow when changing the template.
|
|||
cmd/ Entrypoint, Wire injector, main.go.
|
||||
configs/ Runtime config (config.yaml). No secrets.
|
||||
internal/config/ Viper config models, loading, snapshots, and reloads.
|
||||
internal/logging/ Structured logging sinks and source metadata.
|
||||
internal/server/ Gin handlers, middleware, routers, static files, HTTP wiring.
|
||||
internal/service/dto/ Hand-written request and response DTOs.
|
||||
internal/service/ DTO/DO transport adapters; organized by domain.
|
||||
internal/biz/ Domain models, usecases, repo interfaces, errors.
|
||||
internal/data/ Repo implementations, database clients, migrations.
|
||||
internal/initialize/ First-install and configuration orchestration.
|
||||
internal/integration/ External I/O adapters: cache, email, payment, storage.
|
||||
internal/integration/ External I/O adapters: cache, email, payment, storage (mq, websocket, runtimeconfig, systeminfo).
|
||||
internal/modules/ Built-in module schema, seed, menu, API, and task contributions.
|
||||
internal/paymentkit/ Shared payment normalization, signing, and JSON helpers.
|
||||
internal/routecatalog/ HTTP route metadata and runtime policies.
|
||||
internal/worker/ Timed-task runtime and scheduler.
|
||||
internal/utils/ Stateless, internal-only helper packages.
|
||||
|
|
|
|||
|
|
@ -1,90 +1,102 @@
|
|||
# 代码审查问题清单(internal + pkg)
|
||||
|
||||
- 审查日期:2026-08-27 ~ 2026-08-28,共五轮全量审查。第五轮(2026-08-28):核查第四至七轮全部处置声称(W/S/P/F/C/L/D 系列已修复与评估保留项)+ 全新视角回归审查;`go build ./...` 编译验证通过
|
||||
- 文档结构:只保留待修复问题,按**类型**归类(不按轮次);每条标注发现轮次;已修复并经复查确认的、以及经评估决定保留的直接删除
|
||||
- 审查日期:2026-08-27 ~ 2026-08-28,共七轮全量审查。第七轮(2026-08-28):核查第六轮待办项 + **过分拆专项审查**(用户指定重点:少文件文件夹/少行数文件是否过度拆解);`go build ./...` 编译验证通过
|
||||
- 文档结构:只保留待修复问题,按**类型**归类;每条标注发现轮次;已修复并经复查确认的、经评估决定保留的、用户决策不修的直接删除
|
||||
- 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;无循环依赖
|
||||
|
||||
---
|
||||
|
||||
## 一、安全与正确性缺陷(第五轮新发现,最优先)
|
||||
|
||||
| # | 问题 | 位置 | 说明 |
|
||||
|---|------|------|------|
|
||||
| ~~V-1~~ | ~~已清空 `configs/config.yaml` 中的 MySQL DSN、数据库密码和 JWT signing key;模板仅保留 `KRA_*` 环境变量覆盖说明。泄漏值仍存在于历史提交,需在部署侧轮换数据库密码与 JWT 密钥。~~ | ~~configs/config.yaml:9,13,91~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-2~~ | ~~已让操作审计 Response 复用 `redactJSON`,与访问日志保持同一脱敏和长度上限策略,避免 token/密钥字段明文入库。~~ | ~~server/middleware/audit.go;capture.go~~ | ~~已完成(2026-08-28;针对性测试通过)~~ |
|
||||
| ~~V-3~~ | ~~已补齐 `key`、`public_cert`、`signing_key`、`secret_key` 等敏感键,并将 integration payment test 路由标记为 `BodyPolicyPaymentConfig`,请求体统一使用摘要策略。~~ | ~~server/middleware/redact.go;routecatalog/catalog.go~~ | ~~已完成(2026-08-28;针对性测试通过)~~ |
|
||||
| ~~V-4~~ | ~~已在 config watcher 与 data reload 入口补充运行时契约说明:watch 只发布不可变快照,`reloadSystem` 才重建数据库/缓存/存储/集成客户端;保留双路径以维护回滚与连接退休安全。~~ | ~~internal/config/runtime.go;internal/data/config_store.go~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-5~~ | ~~已修正 ApplySync 失败文案为「同步失败」。~~ | ~~server/handler/api.go:195~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-6~~ | ~~已移除 audit handler 八处 `err.Error()` 拼接;客户端只收到稳定分类提示,内部错误仍由审计/日志链路保留。~~ | ~~server/handler/audit.go~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-7~~ | ~~已补齐秒传复制的 `Size`、`MD5`、`Mime`、`UserID` 字段,保持与完整上传记录一致。~~ | ~~biz/system/media_upload.go~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-8~~ | ~~`CompleteUploadSession` 失败时现在回退会话状态并返回错误,避免 media 已创建而 session 长期停留 `merging`;非关键分片清理仍保持 best-effort。~~ | ~~biz/system/media_upload.go~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~V-9~~ | ~~已逐项确认:用户重名检查排除软删记录是有意的重新注册语义;export 关系替换使用软删以保留审计历史;对软删记录更新匹配 0 行符合不可恢复约束;关联查询显式 `deleted_at IS NULL` 与 Model scope 叠加属于迁移期防御,并新增参数软删除更新回归测试。~~ | ~~data/system/*~~ | ~~评估完成,保留(2026-08-28)~~ |
|
||||
| ~~V-10~~ | ~~已删除外部退款分支中 `!accepted` 的不可达兜底,统一返回已确认的 `providerErr`。~~ | ~~biz/payment/payment.go~~ | ~~已完成(2026-08-28)~~ |
|
||||
|
||||
## 二、死代码残留(第五轮新扫描)
|
||||
|
||||
| 死代码 | 位置 | 证据 |
|
||||
|--------|------|------|
|
||||
| ~~PaymentOrderSourceRegistry.Len~~ | ~~已删除:全仓(含测试)零调用。~~ | ~~biz/payment/payment_order.go~~ |
|
||||
| ~~PaymentFulfillmentRegistry.Len~~ | ~~已删除:全仓(含测试)零调用。~~ | ~~biz/payment/payment.go~~ |
|
||||
| ~~NewConfiguredPaymentUsecase 恒 nil 错误透传壳~~ | ~~已删除并通过 Wire 重新生成,ProviderSet 直接绑定 `NewPaymentUsecase`。~~ | ~~biz/payment/provider.go;cmd/wire_gen.go~~ |
|
||||
| ~~biz/integration mergeIntegrationDefaults 一行转发壳~~ | ~~已删除,调用点统一使用 `MergeIntegrationDefaults`。~~ | ~~biz/integration/integration_config.go~~ |
|
||||
| ~~P-15 残留~~ | ~~已修正 handler/http.go 注释与 source.go 旧路径标记,并同步移除过时测试样本。~~ | ~~server/handler/http.go;internal/logging/source.go~~ |
|
||||
| ~~`convertAuthority` 的 DeletedAt 值传递被 json:"-" 丢弃~~ | ~~已删除无效赋值,避免向已隐藏字段传递无效数据。~~ | ~~service/system/user_conversion.go~~ |
|
||||
|
||||
## 三、重复实现残留(历轮收敛后的漏网项)
|
||||
## 一、安全与正确性缺陷(最高优先级)
|
||||
|
||||
| # | 问题 | 位置 | 轮次 |
|
||||
|---|------|------|------|
|
||||
| ~~D-2 遗漏 1-3~~ | ~~已统一 qq、wechat v3 与 payment data 的支付方式归一化到 `paymentkit.NormalizePaymentMethod`,消除点号/短横线/空格处理漂移。~~ | ~~integration/payment/qq.go;wechat_v3.go;data/payment/payment.go~~ | ~~已完成(2026-08-28;针对性测试通过)~~ |
|
||||
| ~~D-31~~ | ~~已复用 `paymentOrderFingerprint` 完成 BeforeCreate 前后不可变校验,保留同等字段覆盖范围并删除 10 个 canonical 局部变量。~~ | ~~biz/payment/payment.go~~ | ~~已完成(2026-08-28;针对性测试通过)~~ |
|
||||
| ~~D-32~~ | ~~已新增 `BodyPolicyUpload` 并让 AccessLog 按 routecatalog 决定上传请求体限额,删除硬编码 URL 后缀判断。~~ | ~~routecatalog/catalog.go;server/middleware/access_log.go~~ | ~~已完成(2026-08-28;针对性测试通过)~~ |
|
||||
| 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-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-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-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-17 | 秒传一致性两处残留:① 秒传 copy 直接沿用 `media.Mime` 不走 CompleteUpload 的 TypeByExtension 兜底(源记录 Mime 空则落空串);② 秒传 Tag 沿用源文件扩展而 Name 用新名,扩展与 Tag 不一致(普通路径 Tag 取 session.FileName 扩展,行为分叉) | biz/system/media_upload.go:100 vs :250-255 | 六轮发现,七轮复核部分修复 |
|
||||
| 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-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-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 修复的保留项) |
|
||||
|
||||
## 四、文档漂移(第五轮新发现,低成本高收益)
|
||||
## 二、死代码与碎屑
|
||||
|
||||
| # | 问题 | 位置 |
|
||||
|---|------|------|
|
||||
| ~~F-21~~ | ~~已将 CLAUDE.md 正文同步为 Gin+手写 DTO+make generate 的现行仓库约定,移除 proto/AIP/fieldmask 时代描述。~~ | ~~CLAUDE.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~F-22~~ | ~~已修正 pkg/README.md,移除不存在的 logging/httpx/paymentkit 公共包描述。~~ | ~~pkg/README.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~F-23~~ | ~~已修正 internal/README.md 与 internal/server/README.md 的 httpx 路径。~~ | ~~internal/README.md;internal/server/README.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~F-24~~ | ~~已将 integration README 的 paymentkit 路径改为 `internal/paymentkit`。~~ | ~~internal/integration/README.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~F-25~~ | ~~已修正 service README,根包仅聚合 Wire ProviderSet,不再声称 re-export 门面。~~ | ~~internal/service/README.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| ~~F-26~~ | ~~已在 AGENTS.md 项目结构中补充 `docs/`。~~ | ~~AGENTS.md~~ | ~~已完成(2026-08-28)~~ |
|
||||
| # | 问题 | 位置 | 轮次 |
|
||||
|---|------|------|------|
|
||||
| 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-2 | 两个 migration step 重复播种同一条 test API(ensureCommunicationSurface:88 已含+已授权,ensureCommunicationTestSurface:145-153 再种一遍)——新装环境纯冗余、存量环境一次性补种后永久空转 | data/system/migrations.go:88,145-153,31-32 | 六轮发现,七轮复核未修复 |
|
||||
| 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-6 | 测试缺口五处:OperationAudit 端到端测试(现有仅函数级);paymentCreateRequiresNotifyURL 测试;NormalizePaymentMethod 直接单元测试;支付词表新增同义无用例(见 V-16b);CORS allow-all 无回归测试(credentials=false 一旦回退不会被捕获) | 各处 | 六轮发现,七轮扩展 |
|
||||
| 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-8 | routecatalog 描述含糊:getSysParam 与 getSysParamsList 描述均为「获取参数列表」(前者语义是按 key 取单值),管理端菜单两条同名接口 | routecatalog/catalog.go:118-119 | 七轮 |
|
||||
|
||||
## 五、过分拆分残留(微文件清单,行数实测)
|
||||
## 三、重复实现 / 双轨残留
|
||||
|
||||
| 文件 | 行数 | 说明 |
|
||||
|---|---|---|
|
||||
| ~~internal/modules/surface/surface.go~~ | ~~19~~ | ~~评估保留:隔离 routecatalog 到 pkg/module 的适配依赖。~~ |
|
||||
| ~~internal/data/provider/provider.go~~ | ~~8~~ | ~~评估保留:`Database` seam 被多个 data 子包独立依赖。~~ |
|
||||
| ~~internal/data/data_scope_record.go~~ | ~~9~~ | ~~评估保留:隔离 data 回调写模型与 system 查询模型。~~ |
|
||||
| ~~internal/biz/task/task_registry.go~~ | ~~11~~ | ~~评估保留:biz 只暴露窄注册接口,避免 worker/pkg/task 类型穿透。~~ |
|
||||
| ~~internal/service/dto/{email,authentication,permission,system_init}.go~~ | ~~7/8/12/13~~ | ~~评估保留:按 transport 契约分文件,合并只减少文件数。~~ |
|
||||
| ~~internal/server/handler/{session,navigation,set,http}.go~~ | ~~25/26/26/31~~ | ~~评估保留:单方法 handler 与响应词汇表由 Wire/路由注入约束形成。~~ |
|
||||
| ~~internal/server/router 8 个单域注册微文件~~ | ~~12-19~~ | ~~评估保留:领域路由注册边界清晰,统一合并会扩大单文件变更面。~~ |
|
||||
| # | 问题 | 位置 | 轮次 |
|
||||
|---|------|------|------|
|
||||
| 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 处理(请求体方向已修: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 | 六轮发现,七轮复核部分修复 |
|
||||
|
||||
注:Wire ProviderSet 微文件(约 10 个 ≤9 行)属 Wire 惯例不计债务;biz/system 微文件群、SystemConfigService、provider/providers 双文件等已在历轮合并完成。
|
||||
## 四、文档漂移(残余项)
|
||||
|
||||
| # | 问题 | 位置 | 轮次 |
|
||||
|---|------|------|------|
|
||||
| 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-34 | pkg/README.md「当前包含」漏列 mq/、websocket/ 实际包;且 :7-10 与 :10 重复罗列 database/module/task | pkg/README.md:5-13 | 六轮发现,七轮复核未修复 |
|
||||
|
||||
## 五、过分拆清单(第七轮专项,用户指定重点)
|
||||
|
||||
全库 291 个非测试 .go 文件中 <40 行者 81 个:15 个 wire ProviderSet 微文件为项目惯例(非债务)、约 50 个为分层契约/域对称模式自然产物(合理)、**16 个为真实过拆候选**。
|
||||
|
||||
### 5.1 硬过拆(建议合并,4 项)
|
||||
|
||||
| 文件 | 行数 | 问题 | 合并目标 |
|
||||
|---|---|---|---|
|
||||
| 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/data/task/provider.go 别名层 | 11 | `type Provider = dataprovider.Database` 纯转手别名 | 删别名直接用 dataprovider.Database |
|
||||
| internal/biz/task/task_registry.go 别名行 | 11 | TaskMethodFunc/TaskMethod 两行纯别名零增值(窄接口本身有三处消费,保留) | 删 :5-6 别名行 |
|
||||
|
||||
### 5.2 轻度过拆(建议合并,5 项)
|
||||
|
||||
| 文件 | 行数 | 问题 | 建议 |
|
||||
|---|---|---|---|
|
||||
| handler/navigation.go | 26 | 仅 Menu 一个端点,依赖的 UserService 与 user.go 相同,无架构理由 | 并入 user.go |
|
||||
| handler/session.go | 25 | 仅 Logout 一个端点单方法 | 并入 user.go 或 public.go |
|
||||
| handler/http.go | 31 | 转发不一致:转发 httpx 常量但 session.go:4 又直接 import middleware("单一词汇表"目标未达成,包内双风格) | 删转发改直用 httpx,或补齐统一风格 |
|
||||
| data/system/time.go | 15 | deletedAtPointer 单函数无独立文件必要 | 并入 models.go |
|
||||
| data/system/audit.go | 17 | 3 个 repo 构造器与实现跨文件分离(方法散布 5 个文件,阅读跳 2 次) | 构造器移回各自首个实现文件 |
|
||||
|
||||
### 5.3 可选合并(模式性过拆,2 组)
|
||||
|
||||
- **router 21 个微文件**(12-33 行/个)→ 并入 routes.go 约 420 行(与 routecatalog 449 行体量一致,项目已证明可接受);无 wire/测试/依赖方向约束,仅剩"每域一文件"对称风格
|
||||
- **modules 4 个 definition 包**(各 1 文件 17-24 行、单一消费者 catalog.go)→ 可合并为 catalog.go 单文件 ~110 行;保留理由是模块插件式对称,若不打算支持外部模块注册属过度形式化
|
||||
|
||||
### 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
|
||||
|
||||
### 5.5 判定为合理的(复检确认,防误报)
|
||||
|
||||
- **modules/surface:保留理由升级为编译级硬约束**——catalog.go:7-10 import 四个子包 definition,子包 definition.go:7 又 import surface,若并入根包即循环导入(Go 禁止),独立成包是唯一解
|
||||
- 单文件但职责完整的包(cache/email/runtimeconfig/systeminfo/routecatalog/httpx/staticfiles/utils 两包/gormkit/pagination/module/task 等 24 个):均有真实多消费者或分层必需
|
||||
- biz 域微文件(cache/email/errors/maintenance/permission 等 8 个):分层契约自然形态
|
||||
- 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 调用链消费
|
||||
- **F-2/F-4/F-6/F-7/F-9/F-10/F-11**:result.go 自有协议语义非纯转发;handler/http.go 别名层;task 双 usecase 生命周期不同;initialize 三层是启动编排倒置链;system usecase 壳是 Wire 契约边界;security_session 剩余 6 方法跨三层消费;biz 接口嵌入是组合窄能力
|
||||
- **S-5/S-6/S-7/S-8/S-9**:payment 大文件承载跨供应商编排;dto 跨域文件移动放大契约变化;data 转换命名差异需全域迁移;media 四文件职责边界清晰;单方法 handler 由 Wire 注入约束
|
||||
- **D-4/D-6/D-9/D-10/D-11/D-19**:handler 样板各域差异明显;mq helper 属排除范围;config/runtimeconfig 分离已补决策注释;三处清理入口不同各自独立触发;树算法输入模型不同;金额守恒四处输入形态互异(完整结构体/配置化 JSON/XML 值映射/SDK 结构体)
|
||||
- **L-5/L-10/L-12**:错误审计白名单被测试锁定;Kratos errors 仅跨层/stdlib 管内部的双体系分层;binding 覆盖结构必填、手工覆盖上下文(覆盖不均但新代码倾向 binding,未恶化)
|
||||
- **P-3/P-5/P-6/P-7/P-8/P-9/P-10/P-11/P-13**:pkg 各包定位经复核成立;mq/websocket 属既定排除
|
||||
- **X-1~X-4/X-6~X-8/X-10/X-11**:路由双声明/集成配置双通道/热重载分工(watchLoop 发布快照 vs reloadConfig 重建基础设施,合并需重做锁与退休策略)等均完成影响分析保留
|
||||
- **C-2/C-4/C-6/C-8**:loadUser/loadUsers 查询策略不同;*Data nil 防御覆盖测试替身边界;媒体三重限制各守一层;PaymentLogger 是审计替换 seam
|
||||
- **L-7 复核**:data 层 Table() 已全 PO 化(仅 2 处导出动态表名合理保留);saveRelations 只做 DO→PO 转换
|
||||
- **质量标杆**(第五轮正面确认):payment 幂等指纹+回调强制平台查单、退款 lease 语义、task_scheduler 锁序、task_executor SSRF 拨号防护+orphan 跟踪、auth singleflight(context.WithoutCancel 隔离取消传染)、data_scope 回调注入、email CRLF 清洗、ListTasks(0,0) 全量语义、新增缝(PaymentAdapterFactory/MergeRuntimeConfig/deletePrefixViaList/AST 缓存/systeminfo)均内聚无越界
|
||||
- **biz 注入面**:RegisterBusinessModule/PaymentBusinessModule 是 docs/PAYMENT.md:121-129 声明的业务接入契约;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**:历轮影响分析后的保留决策,理由均已复核与代码相符
|
||||
- **质量标杆**(第七轮正面确认):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 隔离
|
||||
|
||||
## 处置建议(按优先级)
|
||||
|
||||
1. **立即处理 V-1**(泄漏的数据库密码)→ **V-2/V-3**(审计敏感数据落地防护对称化)
|
||||
2. **V-4/V-9**(热重载漂移文档化、软删语义逐项确认+补测试)
|
||||
3. **V-5/V-6/V-7/V-8/V-10 + 二节死代码**(小而具体的清理批次)
|
||||
4. **三节 D-2 三处遗漏 + D-31/D-32**
|
||||
5. **四节文档漂移群**(一次 README/CLAUDE.md 同步批)
|
||||
6. **五节微文件合并**(最后)
|
||||
1. **V-19 掩码/恢复键集不对称**(回显保存会把 `******` 落库破坏支付配置——统一共享键表后连带解决 key_pem 泄漏)
|
||||
2. **V-11 截断标记保护**(captureWriter truncated 标志+占位符)
|
||||
3. **V-15 错误泄露全域收敛**(推广 failLogViewer 范式,86 处)+ **V-16b 词表对齐**(douyin/lakala/alipay 三处补齐+测试)
|
||||
4. **V-17/V-18/V-20/V-13b**(秒传一致性、会话回收扩 failed+merging、前端 token、Swagger 开关质量)
|
||||
5. **二节死代码 + 三节双轨 + 四节文档**(一批小清理)
|
||||
6. **五节过分拆**(4 硬+5 轻+2 组可选+seam 统一——纯文件级减法,零行为变更)
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import (
|
|||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
stdmime "mime"
|
||||
"os"
|
||||
"path"
|
||||
"path/filepath"
|
||||
|
|
@ -246,10 +247,18 @@ func (uc *MediaUsecase) CompleteUpload(ctx context.Context, userID, uploadID uin
|
|||
return fail(errors.New("整文件校验失败"))
|
||||
}
|
||||
media := &MediaFile{Name: session.FileName, URL: stored.URL, Tag: strings.TrimPrefix(filepath.Ext(session.FileName), "."), Key: key, Size: stored.Size, Mime: mime, MD5: hash, UserID: userID}
|
||||
if strings.TrimSpace(media.Mime) == "" {
|
||||
media.Mime = stdmime.TypeByExtension(ext)
|
||||
if media.Mime == "" {
|
||||
media.Mime = "application/octet-stream"
|
||||
}
|
||||
}
|
||||
if err = uc.CreateMedia(ctx, media); err != nil {
|
||||
return fail(err)
|
||||
}
|
||||
if err = uc.CompleteUploadSession(ctx, uploadID, key, media.ID); err != nil {
|
||||
_ = uc.DeleteMedia(ctx, media.ID)
|
||||
_ = uc.files.Delete(ctx, key)
|
||||
_ = uc.FailUploadSession(ctx, uploadID)
|
||||
return nil, fmt.Errorf("完成上传会话失败: %w", err)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -87,6 +87,38 @@ func (s *Store) Delete(ctx context.Context, key string) error {
|
|||
return nil
|
||||
}
|
||||
|
||||
func (s *Store) GetAndDelete(ctx context.Context, key string) (string, bool, error) {
|
||||
if client := s.client(); client != nil {
|
||||
value, err := client.Eval(ctx, "local v=redis.call('GET',KEYS[1]); if v then redis.call('DEL',KEYS[1]); end; return v", []string{key}).Result()
|
||||
if err == redis.Nil {
|
||||
return "", false, nil
|
||||
}
|
||||
if err != nil {
|
||||
return "", false, err
|
||||
}
|
||||
if value == nil {
|
||||
return "", false, nil
|
||||
}
|
||||
switch typed := value.(type) {
|
||||
case string:
|
||||
return typed, true, nil
|
||||
case []byte:
|
||||
return string(typed), true, nil
|
||||
default:
|
||||
return "", false, nil
|
||||
}
|
||||
}
|
||||
s.mu.Lock()
|
||||
defer s.mu.Unlock()
|
||||
entry, ok := s.memory[key]
|
||||
if !ok || (!entry.expiresAt.IsZero() && time.Now().After(entry.expiresAt)) {
|
||||
delete(s.memory, key)
|
||||
return "", false, nil
|
||||
}
|
||||
delete(s.memory, key)
|
||||
return entry.value, true, nil
|
||||
}
|
||||
|
||||
func (s *Store) Increment(ctx context.Context, key string, expiration time.Duration) (int64, error) {
|
||||
if client := s.client(); client != nil {
|
||||
if expiration > 0 {
|
||||
|
|
|
|||
|
|
@ -438,7 +438,7 @@ func alipayCreateMethod(extra, config map[string]any) (string, error) {
|
|||
}
|
||||
normalized := paymentkit.NormalizePaymentMethod(value)
|
||||
switch normalized {
|
||||
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "miniapp", "mini_program":
|
||||
case "", "create", "trade_create", "alipay_trade_create", "jsapi", "js_api", "miniapp", "mini_app", "mini_program":
|
||||
return "alipay.trade.create", nil
|
||||
case "pay", "trade_pay", "alipay_trade_pay", "barcode", "barcode_pay", "micropay", "face_to_face":
|
||||
return "alipay.trade.pay", nil
|
||||
|
|
|
|||
|
|
@ -183,7 +183,7 @@ func wechatV2CreateMethod(extra, config map[string]any) (string, error) {
|
|||
}
|
||||
normalized := paymentkit.NormalizePaymentMethod(value)
|
||||
switch normalized {
|
||||
case "", "jsapi", "js_api", "mini", "miniapp", "mini_program", "miniprogram", "applet":
|
||||
case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "mini_program", "miniprogram", "applet":
|
||||
return gopayWechat.TradeType_JsApi, nil
|
||||
case "app", "app_pay":
|
||||
return gopayWechat.TradeType_App, nil
|
||||
|
|
|
|||
|
|
@ -49,7 +49,7 @@ func (a *wechatV3Adapter) Create(ctx context.Context, req *bizpayment.PaymentReq
|
|||
tradeType = normalizeWechatV3TradeType(firstAny(c, "trade_type", "pay_type", "method"))
|
||||
}
|
||||
switch tradeType {
|
||||
case "", "jsapi", "mini", "miniprogram", "mini_program", "applet":
|
||||
case "", "jsapi", "js_api", "mini", "miniapp", "mini_app", "miniprogram", "mini_program", "applet":
|
||||
if openID := firstAny(req.Extra, "openid", "open_id"); openID != "" {
|
||||
bm.SetBodyMap("payer", func(payer gopay.BodyMap) { payer.Set("openid", openID) })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -165,7 +165,7 @@ var routes = map[string]routeValue{
|
|||
"POST /info/createInfo": {group: "公告", description: "新建公告", audit: true},
|
||||
"POST /init/checkdb": {group: "初始化", description: "检查数据库", public: true},
|
||||
"POST /init/initdb": {group: "初始化", description: "初始化数据库", public: true},
|
||||
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true, bodyPolicy: BodyPolicyPaymentConfig},
|
||||
"POST /integration/configs/:kind/:provider/test": {group: "集成配置", description: "测试通信集成连接", audit: true, bodyPolicy: BodyPolicyIntegrationConfig},
|
||||
"POST /jwt/jsonInBlacklist": {group: "jwt", description: "jwt加入黑名单(退出,必选)"},
|
||||
"POST /mediaUpload/chunk": {group: "媒体上传", description: "上传分片", bodyPolicy: BodyPolicyUpload},
|
||||
"POST /mediaUpload/complete": {group: "媒体上传", description: "完成大文件上传"},
|
||||
|
|
|
|||
|
|
@ -61,7 +61,9 @@ func NewGinEngineWithRuntime(runtime *config.Store, access *systemservice.Access
|
|||
if ws != nil && ws.Enabled() {
|
||||
engine.GET(ws.Path(), handleWebSocket)
|
||||
}
|
||||
registerSwagger(engine, prefix, version, logger)
|
||||
if snapshot == nil || snapshot.Admin == nil || snapshot.Admin.App == nil || strings.ToLower(strings.TrimSpace(snapshot.Admin.App.Env)) != "production" {
|
||||
registerSwagger(engine, prefix, version, logger)
|
||||
}
|
||||
staticfiles.Register(engine, runtime)
|
||||
if logger != nil {
|
||||
for _, route := range engine.Routes() {
|
||||
|
|
|
|||
|
|
@ -31,7 +31,7 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
started := time.Now()
|
||||
var requestBody []byte
|
||||
multipart := strings.Contains(c.GetHeader("Content-Type"), "multipart/form-data")
|
||||
mediaUpload := multipart && routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path) == routecatalog.BodyPolicyUpload
|
||||
mediaUpload := routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path) == routecatalog.BodyPolicyUpload
|
||||
var admin *config.Admin
|
||||
if runtime != nil {
|
||||
if snapshot := runtime.Snapshot(); snapshot != nil {
|
||||
|
|
@ -46,7 +46,14 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
}
|
||||
}
|
||||
bytesIn := c.Request.ContentLength
|
||||
logLimit := 1024
|
||||
if admin != nil && admin.Zap != nil && admin.Zap.AccessLogMaxBytes > 0 {
|
||||
logLimit = admin.Zap.AccessLogMaxBytes
|
||||
}
|
||||
maxBytes := 1 << 20
|
||||
if logLimit > maxBytes {
|
||||
maxBytes = logLimit
|
||||
}
|
||||
writer := &captureWriter{ResponseWriter: c.Writer, maxBytes: maxBytes}
|
||||
c.Writer = writer
|
||||
c.Header("X-Kra-Version", version)
|
||||
|
|
@ -75,10 +82,6 @@ func AccessLog(runtime *config.Store, logger *slog.Logger, version string) gin.H
|
|||
if bytesIn < 0 {
|
||||
bytesIn = 0
|
||||
}
|
||||
logLimit := 1024
|
||||
if admin != nil && admin.Zap != nil && admin.Zap.AccessLogMaxBytes > 0 {
|
||||
logLimit = admin.Zap.AccessLogMaxBytes
|
||||
}
|
||||
bodyPolicy := routecatalog.BodyPolicyFor(c.Request.Method, c.Request.URL.Path)
|
||||
paymentCallback := bodyPolicy == routecatalog.BodyPolicyPaymentCallback
|
||||
paymentConfigWrite := bodyPolicy == routecatalog.BodyPolicyPaymentConfig
|
||||
|
|
|
|||
|
|
@ -65,10 +65,6 @@ func OperationAudit(runtime *config.Store, recorder *systemservice.AuditRecorder
|
|||
c.Request.Body = io.NopCloser(bytes.NewReader(requestBody))
|
||||
}
|
||||
}
|
||||
// The reference middleware captures up to the global 1 MiB response
|
||||
// safety limit and only applies the configured operation-log limit when a
|
||||
// download response is recorded. Using maxBytes here would silently
|
||||
// truncate ordinary JSON responses before that decision is possible.
|
||||
started := time.Now()
|
||||
c.Next()
|
||||
userID := uint(0)
|
||||
|
|
|
|||
|
|
@ -34,7 +34,7 @@ func CORS(runtime *config.Store) gin.HandlerFunc {
|
|||
corsHandled := false
|
||||
if mode == "allow-all" {
|
||||
if origin != "" {
|
||||
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, true)
|
||||
setCORSHeaders(c, origin, defaultCORSHeaders, defaultCORSMethods, defaultCORSExpose, false)
|
||||
corsHandled = true
|
||||
}
|
||||
} else if rule := matchingCORSRule(cors.Whitelist, origin); rule != nil {
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import (
|
|||
"context"
|
||||
"encoding/json"
|
||||
integrationbiz "kra/internal/biz/integration"
|
||||
"strings"
|
||||
|
||||
"kra/internal/service/dto"
|
||||
)
|
||||
|
|
@ -38,13 +39,13 @@ func (s *IntegrationConfigService) List(ctx context.Context, kind string) ([]*dt
|
|||
enabled = config.Enabled
|
||||
delete(byProvider, definition.Provider)
|
||||
}
|
||||
result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: values, Fields: integrationFieldsDTO(definition.Fields)})
|
||||
result = append(result, &dto.IntegrationConfigResponse{Kind: definition.Kind, Provider: definition.Provider, Name: definition.Name, Description: definition.Description, Enabled: enabled, Configured: configured, Config: redactIntegrationJSON(values), Fields: integrationFieldsDTO(definition.Fields)})
|
||||
}
|
||||
for _, config := range configs {
|
||||
if _, exists := byProvider[config.Provider]; !exists {
|
||||
continue
|
||||
}
|
||||
result = append(result, &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values})
|
||||
result = append(result, &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: redactIntegrationJSON(config.Values)})
|
||||
}
|
||||
return result, nil
|
||||
}
|
||||
|
|
@ -55,16 +56,44 @@ func (s *IntegrationConfigService) Find(ctx context.Context, kind, provider stri
|
|||
return nil, err
|
||||
}
|
||||
definition, found := integrationbiz.IntegrationDefinition(config.Kind, config.Provider)
|
||||
response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: config.Values}
|
||||
response := &dto.IntegrationConfigResponse{Kind: config.Kind, Provider: config.Provider, Name: config.Provider, Enabled: config.Enabled, Configured: true, Config: redactIntegrationJSON(config.Values)}
|
||||
if found {
|
||||
response.Name = definition.Name
|
||||
response.Description = definition.Description
|
||||
response.Fields = integrationFieldsDTO(definition.Fields)
|
||||
response.Config = mergeConfigJSON(definition.Defaults, config.Values)
|
||||
response.Config = redactIntegrationJSON(mergeConfigJSON(definition.Defaults, config.Values))
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
|
||||
func redactIntegrationJSON(raw json.RawMessage) json.RawMessage {
|
||||
var value any
|
||||
if json.Unmarshal(raw, &value) != nil {
|
||||
return raw
|
||||
}
|
||||
var walk func(any)
|
||||
walk = func(v any) {
|
||||
switch x := v.(type) {
|
||||
case map[string]any:
|
||||
for k, item := range x {
|
||||
n := strings.ToLower(strings.ReplaceAll(k, "-", "_"))
|
||||
if strings.Contains(n, "secret") || strings.Contains(n, "password") || strings.Contains(n, "private") || strings.Contains(n, "credential") || strings.Contains(n, "token") || strings.HasSuffix(n, "_key") || strings.Contains(n, "cert") || strings.Contains(n, "p12") {
|
||||
x[k] = "******"
|
||||
} else {
|
||||
walk(item)
|
||||
}
|
||||
}
|
||||
case []any:
|
||||
for _, item := range x {
|
||||
walk(item)
|
||||
}
|
||||
}
|
||||
}
|
||||
walk(value)
|
||||
out, _ := json.Marshal(value)
|
||||
return out
|
||||
}
|
||||
|
||||
func (s *IntegrationConfigService) Save(ctx context.Context, kind, provider string, req *dto.IntegrationConfigRequest) error {
|
||||
if req == nil {
|
||||
return s.uc.Save(ctx, nil)
|
||||
|
|
|
|||
|
|
@ -53,7 +53,13 @@ func (s *ExportService) IssueToken(ctx context.Context, templateID string, param
|
|||
}
|
||||
|
||||
func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBlank bool) (*ExportToken, error) {
|
||||
raw, ok, err := s.cache.Get(ctx, "export:"+token)
|
||||
key := "export:" + token
|
||||
raw, ok, err := s.cache.Get(ctx, key)
|
||||
if atomic, supported := s.cache.(interface {
|
||||
GetAndDelete(context.Context, string) (string, bool, error)
|
||||
}); supported {
|
||||
raw, ok, err = atomic.GetAndDelete(ctx, key)
|
||||
}
|
||||
if err != nil || !ok {
|
||||
return nil, ErrExportTokenInvalid
|
||||
}
|
||||
|
|
@ -64,7 +70,11 @@ func (s *ExportService) ConsumeToken(ctx context.Context, token string, expectBl
|
|||
if value.Blank != expectBlank {
|
||||
return nil, ErrExportTokenType
|
||||
}
|
||||
_ = s.cache.Delete(ctx, "export:"+token)
|
||||
if _, supported := s.cache.(interface {
|
||||
GetAndDelete(context.Context, string) (string, bool, error)
|
||||
}); !supported {
|
||||
_ = s.cache.Delete(ctx, key)
|
||||
}
|
||||
return &ExportToken{TemplateID: value.TemplateID, Params: value.Params, Blank: value.Blank}, nil
|
||||
}
|
||||
func exportTemplateDomain(value *dto.ExportTemplateRequest) *system.ExportTemplate {
|
||||
|
|
|
|||
|
|
@ -40,5 +40,5 @@ func convertUser(user *system.User) *dto.UserResponse {
|
|||
if user.Department != nil {
|
||||
department = departmentResponse(user.Department)
|
||||
}
|
||||
return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, DeletedAt: nil, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt}
|
||||
return &dto.UserResponse{ID: user.ID, CreatedAt: user.CreatedAt, UpdatedAt: user.UpdatedAt, UUID: user.UUID, Username: user.Username, NickName: user.NickName, HeaderImg: user.HeaderImg, AuthorityID: user.AuthorityID, Authority: convertAuthority(user.Authority), Authorities: authorities, DeptID: user.DeptID, Department: department, Departments: departments, Positions: positions, Phone: user.Phone, Email: user.Email, Enable: user.Enable, OriginSetting: user.OriginSetting, PasswordUpdatedAt: user.PasswordUpdatedAt}
|
||||
}
|
||||
|
|
|
|||
Loading…
Reference in New Issue