From 9f8636663838f9b93acd91e466cb5aeb8c71ee36 Mon Sep 17 00:00:00 2001 From: yanweidong Date: Tue, 22 Sep 2026 18:53:53 +0800 Subject: [PATCH] adult and update go.mod --- .workbuddy/memory/2026-09-22.md | 29 ++ README.md | 5 +- docs/README.md | 66 +++++ docs/address.md | 186 +++++++++++++ docs/ads.md | 145 ++++++++++ docs/cloud.md | 373 ++++++++++++++++++++++++++ docs/cms.md | 217 +++++++++++++++ docs/feed.md | 292 ++++++++++++++++++++ docs/feedback.md | 179 ++++++++++++ docs/fts.md | 177 ++++++++++++ docs/group.md | 244 +++++++++++++++++ docs/initial.md | 222 +++++++++++++++ docs/logs.md | 135 ++++++++++ docs/mall.md | 362 +++++++++++++++++++++++++ docs/market.md | 233 ++++++++++++++++ docs/mgt.md | 156 +++++++++++ docs/order.md | 301 +++++++++++++++++++++ docs/passport.md | 171 ++++++++++++ docs/pkgs-all.md | 198 ++++++++++++++ docs/pkgs-ecmall.md | 205 ++++++++++++++ docs/relation.md | 305 +++++++++++++++++++++ docs/sender.md | 216 +++++++++++++++ docs/wallet.md | 347 ++++++++++++++++++++++++ docs/workspace-and-scripts.md | 179 ++++++++++++ go.work | 2 +- go.work.sum | 36 +++ module/base/ads/go.mod | 13 +- module/base/ads/go.sum | 46 +--- module/base/cloud/go.mod | 13 +- module/base/cloud/go.sum | 46 +--- module/base/cms/go.mod | 44 ++- module/base/cms/go.sum | 46 +--- module/base/feedback/go.mod | 13 +- module/base/feedback/go.sum | 46 +--- module/base/fts/README.md | 6 +- module/base/fts/go.mod | 60 ++--- module/base/fts/go.sum | 58 ++-- module/base/initial/go.mod | 46 ++-- module/base/initial/go.sum | 46 +--- module/base/logs/go.mod | 19 +- module/base/logs/go.sum | 50 ++-- module/base/mgt/README.md | 4 +- module/base/mgt/go.mod | 32 +-- module/base/mgt/go.sum | 58 ++-- module/base/passport/README.md | 4 +- module/base/passport/go.mod | 48 ++-- module/base/passport/go.sum | 46 +--- module/base/sender/README.md | 2 +- module/base/sender/go.mod | 24 +- module/base/sender/go.sum | 49 +--- module/ec/address/go.mod | 13 +- module/ec/address/go.sum | 46 +--- module/ec/mall/go.mod | 13 +- module/ec/mall/go.sum | 46 +--- module/ec/market/go.mod | 13 +- module/ec/market/go.sum | 46 +--- module/ec/order/go.mod | 40 ++- module/ec/order/go.sum | 46 +--- module/finance/wallet/go.mod | 38 ++- module/finance/wallet/go.sum | 46 +--- module/social/feed/go.mod | 13 +- module/social/feed/go.sum | 46 +--- module/social/group/go.mod | 13 +- module/social/group/go.sum | 46 +--- module/social/relation/go.mod | 13 +- module/social/relation/go.sum | 46 +--- pkgs/all/go.mod | 36 ++- pkgs/all/go.sum | 66 ++--- pkgs/ecmall/go.mod | 53 ++-- pkgs/ecmall/go.sum | 64 ++--- scripts/api-docgen/go.mod | 2 +- wiki/README.md | 8 +- wiki/audit-2026-09-22-总体审计报告.md | 152 +++++++++++ wiki/产品与服务目录.md | 109 ++++++++ 74 files changed, 5796 insertions(+), 1018 deletions(-) create mode 100644 .workbuddy/memory/2026-09-22.md create mode 100644 docs/README.md create mode 100644 docs/address.md create mode 100644 docs/ads.md create mode 100644 docs/cloud.md create mode 100644 docs/cms.md create mode 100644 docs/feed.md create mode 100644 docs/feedback.md create mode 100644 docs/fts.md create mode 100644 docs/group.md create mode 100644 docs/initial.md create mode 100644 docs/logs.md create mode 100644 docs/mall.md create mode 100644 docs/market.md create mode 100644 docs/mgt.md create mode 100644 docs/order.md create mode 100644 docs/passport.md create mode 100644 docs/pkgs-all.md create mode 100644 docs/pkgs-ecmall.md create mode 100644 docs/relation.md create mode 100644 docs/sender.md create mode 100644 docs/wallet.md create mode 100644 docs/workspace-and-scripts.md create mode 100644 wiki/audit-2026-09-22-总体审计报告.md create mode 100644 wiki/产品与服务目录.md diff --git a/.workbuddy/memory/2026-09-22.md b/.workbuddy/memory/2026-09-22.md new file mode 100644 index 0000000..d310d29 --- /dev/null +++ b/.workbuddy/memory/2026-09-22.md @@ -0,0 +1,29 @@ +# 2026-09-22 工作日志 + +## 全仓逐微服务代码审计(第二轮,业务实现层) + +**任务**:重新分析并审计 BSM Infra Full 全部代码,按微服务名称输出 md 审计文档到 `docs/`;产品类文档放 `wiki/`;明确要求**不得提出过度设计/过度封装建议**。 + +**产出** +- `docs/` 新增 22 篇:18 个业务服务 + `pkgs-all.md`/`pkgs-ecmall.md`/`workspace-and-scripts.md` + `README.md` 索引。 +- `wiki/` 新增产品级文档 2 篇:`audit-2026-09-22-总体审计报告.md`(P0/P1 风险台账 + 共性根因 + 修复优先级)、`产品与服务目录.md`(服务域/清单/完成度/跨域耦合/拓扑)。 +- 更新 `wiki/README.md`、根 `README.md` 的文档索引。 +- 未修改任何源码/脚本。 + +**审计约定(本次确立,后续沿用)** +- 每篇结构:头部信息表 → 定位职责 → 代码结构与入口 → 接口清单 → 数据模型 → 核心流程(mermaid) → 审计发现(安全/正确性/未完成/健壮性) → 风险汇总表 → 修复建议(务实项)。 +- 结论必须带 `文件:行号` 证据;无法确认标【信息不足】。 +- 修复建议只给最小直接动作,禁止统一框架/抽象层/DTO/VO/DDD/CQRS/拆微服务边界一类建议。 + +**关键结论(P0 摘要,供后续跟踪)** +- 认证:passport 与 mall 的验证码登录/注册**不校验验证码**;passport `Forget.Reset` 凭 identity 即可改密且写未加盐 MD5;mgt `AppKey` 登录明文比对密码列;mgt dev 注入 `root/123456`。 +- 资金:wallet 支付回调**不验签**、提现只校验不扣减无幂等、余额扣减读后写;order 模拟支付/发货生产可达、`OrderApprove` 无鉴权、优惠券金额**做加法**致应付变高、扣库存不检查 RowsAffected。 +- 越权:address/cms/group/relation/feed/mall/market/feedback 共 9 个模块存在"只校验登录、不校验归属/角色"。 +- 上线阻断:pkgs 无 prod 配置、JWT/session 密钥默认值恰 32 字节可用、gateway 404 与业务 NotFound 无法区分;`scripts/api-docgen` 无法构建。 +- 静默故障:多处引用不存在的列(`relation_id`/`creator_id`/`createor_id`/`enabled`)与命名返回值 `reply` 未赋值。 +- 测试:全仓仅 21 个 `*_test.go`,base 多数模块、wallet、social 三服务零单测。 + +**方法备注** +- 本机 bash shim 缺 `dirname`(PATH 报错但不影响),需 `export PATH="/usr/bin:/bin:$PATH"` 后执行 `find`/`grep`/`ls`;PowerShell 输出未被捕获,改用 Bash/Glob/Grep。 +- 大批量逐服务审计采用并行子代理(general-purpose)分域产出,子代理默认模型触发 429 时切换 `model: "lite"` 可用;范例先由主代理写 `docs/logs.md` 固化为模板。 +- 子代理无法 SendMessage(非 team 会话),改为在回复中汇报。 diff --git a/README.md b/README.md index e4b8ad1..7422af4 100644 --- a/README.md +++ b/README.md @@ -301,11 +301,14 @@ Linux/macOS: ## 文档 - [Wiki 索引](wiki/README.md) +- [产品与服务目录](wiki/产品与服务目录.md) — 服务域、服务清单、实现完成度、跨域耦合 +- [总体审计报告(2026-09-22)](wiki/audit-2026-09-22-总体审计报告.md) — 风险台账与修复优先级 +- [逐微服务代码审计](docs/README.md) — 18 个业务服务 + 2 个聚合入口 + 工作区脚本,共 21 篇 - [API 接入与模块文档](wiki/api/00-overview.md) - [系统架构](wiki/architecture.md) - [电商聚合开发上下文](pkgs/ecmall/AGENT.md) — pkgs/ecmall 的 AGENT 上下文 - [开发与运维](wiki/operations.md) -- [审计报告](wiki/audit-2026-08-10.md) +- [审计报告(2026-08-10)](wiki/audit-2026-08-10.md) ## 开发约定 diff --git a/docs/README.md b/docs/README.md new file mode 100644 index 0000000..6994256 --- /dev/null +++ b/docs/README.md @@ -0,0 +1,66 @@ +# 代码审计文档索引(2026-09-22) + +本目录是 BSM Infra Full 后端 Go workspace 的**逐微服务代码审计报告**。审计基于静态源码阅读,覆盖 `module/` 下 18 个业务服务、`pkgs/all`、`pkgs/ecmall` 两个聚合入口,以及工作区与工程脚本。 + +> 审计方式:只读静态分析,不修改任何源码。结论均带 `文件:行号` 证据;无法确认处以【信息不足】标注。 +> 修复建议仅针对实际缺陷给出最小、直接的动作,**不包含引入统一框架、抽象层、DTO/VO、DDD/CQRS 等过度设计/过度封装方案**。 + +## 交接给后续的文档分层 + +| 目录 | 内容 | +| --- | --- | +| `docs/`(本目录) | 按微服务名称的审计报告(本文档为索引) | +| `wiki/` | 产品级文档:总体审计报告、产品与服务目录、架构、运维、API | + +## 基础与平台服务域(module/base) + +| 审计报告 | 服务 | 主要职责 | 最严重问题(摘) | +| --- | --- | --- | --- | +| [passport.md](passport.md) | passport | 用户账号与身份认证 | 验证码登录/注册/找回**不校验验证码**;找回密码凭 identity 即可改密且写未加盐 MD5 | +| [mgt.md](mgt.md) | mgt | 后台组织与权限管理 | AppKey 登录**明文比对密码列**(pass-the-hash);dev 配置注入 `root/123456` | +| [initial.md](initial.md) | initial | 客户端初始化与基础字典 | `check/config_cache.go:21` **ORDER BY 字符串拼接(SQL 注入)**;版本判断非语义版本→强制降级 | +| [sender.md](sender.md) | sender | 短信与邮件发送 | 腾讯云短信**空实现** `return nil,nil`;日发送量/黑名单限制**从未生效**;验证码可重放 | +| [fts.md](fts.md) | fts | 文件传输与上传存储 | 上传路由前缀不一致;文件类型/大小校验次序问题 | +| [cloud.md](cloud.md) | cloud | 个人云空间与资料管理 | 空间唯一键导致**多用户不可用**;私人资料"加密"名不副实;根目录空指针 panic | +| [cms.md](cms.md) | cms | 站点内容管理 | 文章/评论**越权删改**;评论发布 panic;赞踩无去重可刷负;`Site` 全系列占位 | +| [ads.md](ads.md) | ads | 公共广告位内容分发 | README 宣称的缓存**完全未实现**(Redis 零引用);鉴权口径自相矛盾 | +| [feedback.md](feedback.md) | feedback | 意见反馈管理 | `Modify/Delete/Remark` 以入参 identity 为唯一条件→**越权改他人反馈**;接口全匿名 | +| [logs.md](logs.md) | logs | 业务操作日志 | 路由**无鉴权**;`fetch` 键名不一致与 `level` 断言**双 panic**;分页参数失效;IP 过滤写反 | + +## 电商与交易服务域(module/ec) + +| 审计报告 | 服务 | 主要职责 | 最严重问题(摘) | +| --- | --- | --- | --- | +| [address.md](address.md) | address | 收货地址簿 | `Get/Modify/Delete` **无归属约束→越权读改删他人地址**(可遍历 id 拿姓名/手机号/住址);Fetch 恒空 | +| [mall.md](mall.md) | mall | 店铺与商品经营 | 员工验证码登录**不校验验证码**;店铺**支付/邮件凭据任意登录用户可读改**;12 处写操作无店铺归属校验 | +| [market.md](market.md) | market | 代理与供应商管理 | 审核**无鉴权**、开号/删号鉴权被注释;登录令牌 AES 与鉴权 JWT 互斥 + 解析失败 panic;`Data` 确认为空实现 | +| [order.md](order.md) | order | 购物车与订单生命周期 | **模拟支付/发货生产可达**且硬编码支付参数;`OrderApprove` 无鉴权;优惠券**做加法**致应付变高;扣库存不检查 RowsAffected→超卖 | + +## 资金与支付服务域(module/finance) + +| 审计报告 | 服务 | 主要职责 | 最严重问题(摘) | +| --- | --- | --- | --- | +| [wallet.md](wallet.md) | wallet | 钱包与支付接入 | 支付回调**不验签**、客户端可自证支付成功;提现**只校验不扣减**且无幂等;余额扣减读后写→双花;支付宝分支必然 panic;微信密钥明文入库 | + +## 社交服务域(module/social) + +| 审计报告 | 服务 | 主要职责 | 最严重问题(摘) | +| --- | --- | --- | --- | +| [feed.md](feed.md) | feed | 社交动态与内容互动 | 动态**越权删改** + 私密动态泄露;分页 `Limit(page)` 错误;`DeleteComment` 空函数;HTTP gateway 不可用 | +| [group.md](group.md) | group | 群组与成员管理 | 入群审批**无角色校验**;`Modify` 重写群 identity 致成员脱钩;列名错误使核心接口恒失败 | +| [relation.md](relation.md) | relation | 好友、关注与匹配 | 通过申请**可单方面加任意陌生人为好友**;`TagFetch` 必然 panic;7 个接口引用不存在的 `relation_id` 列 | + +## 聚合入口与工程 + +| 审计报告 | 对象 | 最严重问题(摘) | +| --- | --- | --- | +| [pkgs-all.md](pkgs-all.md) | pkgs/all(15 服务聚合) | grpc-gateway **404 与业务 NotFound 无法区分**→真实错误响应被覆盖;JWT 密钥默认值恰 32 字节可自签;无生产配置 | +| [pkgs-ecmall.md](pkgs-ecmall.md) | pkgs/ecmall(14 服务聚合) | 同源 404 分流缺陷与密钥问题;聚合不建表不播种→首次部署缺表缺管理员 | +| [workspace-and-scripts.md](workspace-and-scripts.md) | go.work 与 scripts | `api-docgen` 当前**无法构建**且版本长期漂移;SDK 指向仓库外相对路径不可移植;验证脚本 gofmt 门禁有缺口 | + +## 统计 + +- 审计对象:**21 篇报告**(18 业务服务 + 2 聚合入口 + 1 工作区/脚本) +- 覆盖:`module/` 903 个 `.go`、66 个 `.proto`、58 个 `.yaml`;`pkgs/` 54 个 `.go` +- 跨服务汇总与修复优先级:[`wiki/audit-2026-09-22-总体审计报告.md`](../wiki/audit-2026-09-22-总体审计报告.md) +- 产品与服务目录:[`wiki/产品与服务目录.md`](../wiki/产品与服务目录.md) diff --git a/docs/address.md b/docs/address.md new file mode 100644 index 0000000..1cbd5c4 --- /dev/null +++ b/docs/address.md @@ -0,0 +1,186 @@ +# address(收货地址簿)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/ec/address` | +| 服务域 | 电商域(EC)——用户收货地址簿 | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 22 个:`pb/` 生成代码 4 个 4536 行;非 `pb/` 18 个 734 行(其中 `internal/server/{new,library_server}.go` 81 行为 protoc-gen-slc 生成骨架、`test/rpc/rpc.go` 23 行整体被注释)。`proto/` 2 个 387 行;`etc/` 4 个 122 行;`sdk/typescript/` 2 个 136 行;`test/*.http` 3 个 30 行;`README.md` 2 行 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、`cmd/cli`(脚手架占位)、聚合入口 `pkgs/all` 经 `service.Expose` 复用共享 gRPC/Gateway | +| 对外协议 | gRPC + HTTP Gateway,路径 `/address.Library/`,全部 POST | +| 结论摘要 | 职责单一、逻辑层文件短小;但 **Get/Modify/Delete 三个接口只按主键定位、完全不做 `owner` 归属校验**(越权读/改/删他人地址),**列表接口 `Fetch` 的命名返回值 `reply` 从未赋值**(列表恒定返回空),默认地址唯一性依赖"先清后写"两次独立 SQL、无事务也无唯一约束,proto 声称的分页参数服务端从未实现。此外鉴权只校验 token 有效性、不校验角色,`const.pb.go` 3168 行生成代码在本模块无任何引用。 | + +## 1. 服务定位与职责 + +保存与查询 C 端用户的收货地址条目,供下单页选择/维护地址使用。数据模型 `address_library` 只有单一实体(收件人、电话、国家/省/市/区、详址、名称、图片、状态),归属字段是 `owner_id` / `owner_identity`(`internal/models/address_library.go:16-17`)。 + +它是用户地址簿的**存取**服务:不负责省市区字典维护(国家/省/市区只是自由文本字段)、不负责地址地理编码/校验、不参与订单地址快照(订单侧的地址是下单时另存的副本)——这些边界从代码中可见,本模块没有任何外部字典或校验调用。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New` → `impl.NewImpl` → `server.New(nil)` → `service.New` → `srv.Start` | +| `cmd/cli/main.go` | 脚手架占位,仅 `log.Println("Hello World!")`(`cmd/cli/main.go:5-7`) | +| `internal/config/config.go` | 配置结构(Base、Databases、MicroService、Rpc、Gateway、Apm、Etcd)与校验 | +| `internal/impl/impl.go` | 装配入口:Redis → DB → Etcd | +| `internal/impl/with.go` | Redis/DB/Etcd 实例初始化(`RedisCache`/`Etcd`/`DBService`) | +| `internal/models/address_library.go` | `AddressLibrary` 模型 + `TableName` | +| `internal/models/impl.go` | GORM 连接与 `AutoMigrate` | +| `internal/models/query.go` | 仅导出 `ErrNotFound = gorm.ErrRecordNotFound`(5 行) | +| `internal/server/new.go` | 生成骨架:建/复用 `grpc.Server`,注册 `LibraryServer`、reflection | +| `internal/server/library_server.go` | 生成骨架:5 个 RPC 转发到 `internal/logic/library` | +| `internal/logic/library/{create,modify,get,fetch,delete}.go` | 5 个业务实现(共 260 行) | +| `service/expose.go` | 聚合宿主注入:`applyDependencies` + `RegisterLibraryHandlerServer` | +| `service/dependencies.go` | `Dependencies{Redis,Etcd,DB,Cache}` 与覆盖逻辑 | +| `proto/{address,const}.proto` | 接口契约(`const.proto` 为跨模块复制的 blocks 定义) | +| `test/library/*.http` | 3 个手工请求脚本(非自动化测试) | +| `test/rpc/rpc.go` | 整文件被 `/* */` 注释掉的 gRPC 调用示例 | + +依赖注入:`service.Dependencies`(`service/dependencies.go:12-17`)支持外部传入 Redis/Etcd/DB,非 nil 时覆盖 `internal/impl` 的包级变量,供 `pkgs/all` 复用共享连接(实例见 `pkgs/all/internal/service/address.go:10-19`)。注意 `Dependencies.Cache` 字段被声明但 `applyDependencies`(`service/dependencies.go:19-29`)从未使用。 + +## 3. 接口清单 + +全部接口注册在 grpc-gateway 的 POST 路由上(`pb/address.pb.gw.go:179,199,219,239,259`),路径为 `/address.Library/`: + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/address.Library/Create` | 新增地址(可置默认) | token(`create.go:20`,不校验角色) | `internal/logic/library/create.go:18` | +| POST | `/address.Library/Modify` | 修改地址 | token(`modify.go:19`) | `internal/logic/library/modify.go:17` | +| POST | `/address.Library/Get` | 取单条地址(按 `id`) | token(`get.go:19`,解析结果被丢弃) | `internal/logic/library/get.go:17` | +| POST | `/address.Library/Fetch` | 当前用户地址列表 | token(`fetch.go:18`) | `internal/logic/library/fetch.go:16` | +| POST | `/address.Library/Delete` | 删除地址(`id` 数组,可批量) | token(`delete.go:19`,解析结果被丢弃) | `internal/logic/library/delete.go:17` | + +补充说明: + +- 5 个接口的鉴权一律是 `service.ParseMetaCtx(ctx, nil)`,只验"token 有效",**没有角色参数**(SDK `service/meta.go:36-45` 只有在 `opts.RoleValue` 非空时才校验角色,此处传 nil)。 +- 独立进程部署时,模块自身 `grpc.NewServer()` 未安装任何拦截器(`internal/server/new.go:23`),gRPC 端口监听在 `CheckIP(BindIP)` 得到的主机内网 IP(SDK `conf/new.go:102-107` → `utils.GetLocationIP()`,取非回环网卡地址),即**内网可达**。聚合入口 `pkgs/all` 另有 JWT 拦截器兜底(`pkgs/all/internal/server/authorization.go:42-53`),但同样只要求"任意合法 JWT"。`Authorization.Anonymous` 白名单不在仓库内(`pkgs/all/etc/` 不存在)→【信息不足】无法确认聚合部署下本模块哪些方法被列为匿名。 +- **声明但未实现/占位**:proto 中的 `FetchRequest`(`proto/address.proto:52-56`,含 `page_no`/`page_size`/`params`)在服务端**没有任何引用**——`Fetch` 的入参是 `IdentRequest`(`proto/address.proto:58-61`),没有分页字段;该 `FetchRequest` 与 TS 类型(`sdk/typescript/address/index.ts:50-57`)属于"声明了但服务端拿不到、也没实现"的契约。`AddressItem.identity`(`proto/address.proto:26`)在 `Modify` 中不是定位条件(定位用 `id`)。 + +## 4. 数据模型与表 + +### 表 `address_library`(GORM 自动迁移创建,`internal/models/impl.go:15-17,38`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键(SDK `types/db.go:29`) | +| `identity` | varchar(36) | uniqueIndex | 唯一标识,写入 `utils.UUID()`(`create.go:36`) | +| `created_at` / `updated_at` | TIMESTAMP | - | 时间戳 | +| `deleted_at` | TIMESTAMP | index | GORM 软删除列(`Delete` 实际写此列) | +| `status` | int8 | default 0, index | 状态(SDK 注释:0 默认 / -1 禁止 / 1 正常;本模块 proto 注释额外定义了 2=默认地址) | +| `owner_id` | uint | index | 归属用户 ID,取自 token(`create.go:37`) | +| `owner_identity` | varchar(36) | index | 归属用户标识,取自 token(`create.go:38`) | +| `phone` | varchar(20) | default '' | 地址电话(无 not null、无格式约束) | +| `country` / `province` / `city` / `area` | varchar(255) | default '' | 国/省/市/区(自由文本,无字典校验) | +| `detail` | varchar(255) | default '' | 详细地址 | +| `contact` | varchar(20) | default '' | 收件人 | +| `name` | varchar(255) | default '' | 地址名称(如"公司""家") | +| `pics` | text(无长度) | default '' | 图片信息 | + +模型定义见 `internal/models/address_library.go:14-32`(字段逐项:`:16` owner_id、`:17` owner_identity、`:18-26` 其余列)。 + +缺失的约束(与本次多数缺陷直接相关): + +- `(owner_id, status)` 上没有唯一约束,无法在数据库层保证"每个用户至多一个 `status=2` 的默认地址"。 +- `owner_id` / `owner_identity` 无外键,也没有"两个字段必须一致"的约束(`Create` 同时写入两者,但更新与查询口径不同:查询用 `owner_identity`,默认地址重置用 `owner_id`,见 `fetch.go:28` 与 `create.go:42`)。 +- `phone`/`contact`/`address` 类字段全部允许空串,`status` 无 check 约束。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /address.Library/Create"] --> B["ParseMetaCtx(auth.ID/auth.Identity)"] + B --> C["status==2 时先把本 owner 其他默认地址 UpdateColumn status=1"] + C --> D["DBService.Create 写入(与上一步不同事务)"] + E["POST /address.Library/Modify"] --> F["ParseMetaCtx"] + F --> G["status==2 时先清默认"] + G --> H["Where id=? Updates 结构体(无 owner 条件,零值被跳过)"] + I["POST /address.Library/Delete"] --> J["ParseMetaCtx 但丢弃返回值"] + J --> K["Delete Where id in ?(无 owner 条件,实为软删除)"] + L["POST /address.Library/Fetch"] --> M["Where owner_identity=? 全量 Find"] + M --> N["循环构建局部 result 切片"] + N --> O["return(命名返回值 reply 从未赋值 → nil)"] + P["POST /address.Library/Get"] --> Q["ParseMetaCtx 但丢弃返回值"] + Q --> R["Where id=? First(无 owner 条件)"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/library/modify.go:39` | **越权修改他人地址**。`Modify` 的定位条件只有主键 `Where("id=?", in.Id)`,`auth` 只被用于 `:36` 的默认地址重置,**没有 `owner_identity = auth.Identity` 约束**。任意已登录用户猜/遍历一个自增 `id`,即可改掉他人的收件人、电话、详细住址;结合 `:35-37` 还能把该记录改成 `status=2`(默认地址)。 | +| **高** | `internal/logic/library/get.go:19,29` | **越权读取他人地址**。鉴权结果被显式丢弃(`:19` `_, err = service.ParseMetaCtx(...)`),查询只有 `Where("id=?", in.Id)`。`AddressItem` 会返回 `name/contact/phone/detail` 等全部字段(`get.go:39-52`),知道或遍历 `id` 即可批量获取他人真实姓名、手机号与详细住址。 | +| **高** | `internal/logic/library/delete.go:19,24` | **越权删除他人地址**。同样丢弃鉴权结果,删除条件只有 `id in ?`(支持一次性传多个 id,见 `proto/address.proto:74-76`)。可批量软删除任意用户的地址;对下单流程而言等于让目标用户无法选到地址。 | +| **高** | `internal/logic/library/fetch.go:28` 对照 `get.go:29`、`modify.go:39`、`delete.go:24` | 同一模块内**两种归属口径并存**:`Fetch` 用了正确的 `Where("owner_identity=?", auth.Identity)`,其余三个接口完全不带 owner 条件。说明是遗漏而非有意设计,且恰恰是最需要隔离的"按 id 单条操作"没有隔离。 | +| 中 | `internal/logic/library/{create.go:20,modify.go:19,get.go:19,fetch.go:18,delete.go:19}` | 鉴权一律为 `service.ParseMetaCtx(ctx, nil)`,**不校验角色**(SDK `service/meta.go:36-39`)。任意业务域签发的合法 token(例如商城会员 token)都可调用地址簿全部接口;独立部署时模块 `grpc.NewServer()` 无拦截器(`internal/server/new.go:23`),gRPC 端口绑定主机内网 IP(`config.go:31` + SDK `conf/new.go:102-107`),内网任意主机可直接调用。 | +| 中 | SDK 侧,本模块触发路径:`internal/server/new.go:23` + SDK `crypto/token/jwt.go:64-73` + `golang-jwt/jwt/v5@v5.3.1/parser.go:138-140` | **非 JWT 的 `Authorization` 头可让进程崩溃**。`ParseMetaCtx` 最终调用 `token.ParseJwt`;`jwt.ParseWithClaims` 在字符串不是"三段点分"时返回 `nil, err`(parser.go:139-140),而 SDK 紧接着执行 `if claims, ok := token.Claims.(*Claims); ...`(jwt.go:68),**在 nil 接收者上取字段 → 空指针 panic**。本模块 `grpc.NewServer()` 未装 recover 拦截器,goroutine panic 会终止整个进程。发送任意 `Authorization: x` 即可复现(独立进程部署)。 | +| 低 | `etc/address_dev.yaml:1,19,27`、`etc/address_prod.yaml:1,19,27`、`etc/address_test.yaml:1,19,27` | 三个环境配置均从 order 模块复制:`Service: order`(本服务实际键名是 `Address`,见 `cmd/main/main.go:15`)、`MicroService.Anonymous: order.ping.hello`(本模块根本没有 ping 方法,`proto/address.proto` 只有 Library 服务)、`SecretKey: CHANGE_ME`、数据库口令 `password=CHANGE_ME`。 | +| 低 | `internal/impl/with.go:30` | `printer.Info(..., RedisCache.DB)` 无条件解引用 `RedisCache`;`config.go:35` 的 `conf.NotNil(Spec.Service, Spec.Cache)` 恰好保证了 `Spec.Cache` 非空所以当前不会崩,但该校验项一旦调整即成为启动期空指针。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/library/fetch.go:23-40` | **列表接口恒定返回空**。函数签名是命名返回值 `reply *pb.AddressListReply`,但查询结果被写进局部变量 `result`(`:25,35`),函数体末尾直接 `return`(`:40`)→ 实际返回 `(nil, nil)`,**从未构造 `AddressListReply`**。地址列表接口不可用;`:38` 的 `// TODO: add your logic code & delete this line.` 就是未完成的痕迹。 | +| **高** | `internal/logic/library/create.go:41-45`、`modify.go:35-39` | **默认地址唯一性无保障**。两条路径都是"先把该 owner 的 `status=2` 全部改成 1(`UpdateColumn`),再写目标行",两次写操作**不在同一事务**;并发请求或第二步失败时会留下 0 个默认地址(旧默认已被清掉)或 2 个默认地址(两次都先清后写)。表上也没有 `(owner_id, status)` 唯一约束可兜底(`internal/models/address_library.go:14-27`)。 | +| **高** | `internal/logic/library/modify.go:24-33,39` | **`Modify` 字段缺失 + GORM 零值语义**。`:39` 用 `Updates(&address)` 传结构体,GORM 只写非零字段:清空电话/详址/备注一律失败,`status` 传 0 也不生效;同时 `address` 只映射了 `Country/Phone/Province/City/Area/Detail/Contact/Status`(`:25-33`),**`in.Name` 与 `in.Pics` 完全没有赋值**(对照 `create.go:33-34`),即地址名称和图片永远改不了。 | +| 中 | `internal/logic/library/create.go:39` | `address.Status = int8(in.Status)` 直接把请求值落库,**无白名单校验**。proto 约定 `status ∈ {-1,1,2}`(`proto/address.proto:36`),实际传 0/7/-5 同样写库,产生既非正常也非默认的记录;配合 `fetch.go:28` 无 status 过滤,这些脏值会原样出现在列表里。 | +| 中 | `internal/logic/library/modify.go:33,36` | 同样不校验 `status`;且重置语句 `UpdateColumn("status", "1")` 把**字符串 `"1"` 写入 int8 列**(依赖数据库隐式转换),并跳过 GORM 的 `updated_at` 维护(`UpdateColumn` 不触发钩子)。 | +| 中 | `internal/logic/library/delete.go:24` + `proto/address.proto:36` | **删除语义两套并存**。`Delete` 执行的是 GORM 软删除(写 `deleted_at`),而 proto/模型注释把"删除"定义为 `status = -1`。结果:`status=-1` 的记录永远不会被删除路径处理,而 `deleted_at` 非空的记录也不会被任何 status 逻辑感知(`Get`/`Fetch` 完全没有 status 条件,见下条)。 | +| 中 | `internal/logic/library/fetch.go:28` | 列表查询**无 `status` 过滤、无 `ORDER BY`、无 `LIMIT`**:按注释已删除(`status=-1`)的记录照常返回;默认地址(2)与普通地址(1)无固定排序,分页/展示顺序不稳定。 | +| 中 | `internal/logic/library/get.go:24-29` | `IdentRequest.identity`(`proto/address.proto:60`)被完全忽略,仅 `id != 0` 时才继续并用 `id` 查询 → 前端拿 `identity` 取不到单条地址。 | +| 中 | `internal/logic/library/{create.go:52-56,delete.go:30-34}` | 返回的 `StatusReply.Identity` 恒为空,而 `proto/address.proto:65` 明确定义了该字段("标识码")。创建地址后拿不到新地址标识,调用方只能再查一次列表(而列表接口当前又不可用,见 6.2 第一条)。 | +| 低 | `internal/logic/library/get.go:31-37` | `First` 失败时仅对 `gorm.ErrRecordNotFound` 转成 `errcode.ErrRecordNotFound`,其余错误统一转 `errcode.ErrDB`(与 `models/query.go:5` 的 `ErrNotFound` 别名配合,写法本身没问题);但 `Create`/`Modify` 的错误只打印再返回 `errcode.ErrDB`(`create.go:47-50`、`modify.go:40-43`),丢失了"是唯一键冲突还是其他约束冲突"的区分。 | + +### 6.3 未完成实现 + +- `internal/logic/library/fetch.go:38` 保留 `// TODO: add your logic code & delete this line.`,且直接导致 6.2 第一条的"返回 nil"缺陷。 +- `proto/address.proto:52-56` 的 `FetchRequest`(`page_no`/`page_size`/`params`)与对应 TS 类型(`sdk/typescript/address/index.ts:50-57`)**已生成、无任何服务端引用**;`Fetch` 实际入参是没有分页字段的 `IdentRequest`,即 API 契约声称支持分页、服务端不具备该能力,且实现是无条件全量返回。 +- `proto/const.proto` + `pb/const.pb.go`(3168 行,占本模块生成代码的 70%)定义了 `ec_address_blocks` 的一整套消息(`MarketLoginReply`、`OrderSummaryItem`、`FeedPostItem`、`VerifyStatus`…),但 `address.proto` 未 import 该文件,**address 的 Go 代码也未引用其中任何类型**(全模块检索 `pb.<这些类型>` 无命中)→ 纯冗余生成物。 +- `cmd/cli/main.go:5-7` 仍是 `log.Println("Hello World!")` 脚手架。 +- `README.md` 仅有一行标题,无启动方式、配置说明与接口清单。 +- `internal/models/address_library.go:8-13` 的生成器注释仍写着 `OrderAddress / Version: 10 / Created: 2022-04-12`,与实际实体名和当前表结构不符(生成器残留,易误导后续维护者)。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/models/impl.go:74-84`(尤其 `:82`) | **生产环境强制打开 SQL 调试日志**。`NewPostgres` 无条件 `db = db.Debug()`,而三个 yaml 的 `Driver` 都是 `postgres`(`etc/address_dev.yaml:5`、`address_prod.yaml:5`、`address_test.yaml:5`),于是所有 SQL 与参数都会打印(含手机号、详址、pics 链接);同时 etc 里若写 `Debug: true` 也不会生效——`conf.DBConf` 只有 `Driver`/`Source` 两个字段(SDK `conf/types.go:18-21`),且 `internal/impl/with.go:41` 把 options 传的是 `nil`。 | +| 中 | `internal/models/impl.go:38` | 每次启动都执行 `AutoMigrate(&AddressLibrary{})`,生产同样执行;表结构变更依赖启动时隐式完成,没有版本化、没有回滚,也无法在发布前评审 DDL。 | +| 中 | `internal/server/new.go:13-40`(`Mux` 字段 `:16`)+ `cmd/main/main.go:32` | **独立进程下 HTTP 网关不可用**。`Server.Mux` 字段被声明后**从未赋值**(全仓库检索 `.Mux =` / `Mux:` 无命中,本文件 `:26-30` 的构造字面量也没有它),`cmd/main` 却把 `s.Mux`(nil)作为 `GatewayMux` 传给 SDK;SDK `service.Start` 用它调 `http.ListenAndServe(httpAddr, s.Opts.GatewayMux)`(SDK `service/service.go:106-108`),非空接口持有 nil 指针 → `(*ServeMux).ServeHTTP` 首次访问 `s.unescapingMode`(grpc-gateway `runtime/mux.go:407-420`)即空指针 panic,由 `net/http` recover 后断开连接。只有聚合入口 `pkgs/all`(自建 `gwRuntime.NewServeMux()`,`pkgs/all/internal/server/server.go:38`)才正常。注:同一缺陷在所有模块的生成骨架中一致存在,属生成器问题,本模块无法单独修好。 | +| 低 | `internal/impl/impl.go:9`、`internal/impl/with.go:24-31,50-88` | `RedisCache`、`Etcd` 初始化后**全模块无任何使用点**(死代码);`service/dependencies.go:16` 的 `Cache` 字段被 `applyDependencies`(`:19-29`)忽略,注入后也不生效。 | +| 低 | 全模块 | **没有任何 `*_test.go`**;`test/library/*.http` 是手工脚本、`test/rpc/rpc.go` 整体被注释。本次发现的三处越权与"列表返回 nil"若有一条集成测试即可暴露。 | +| 低 | `internal/models/impl.go:28,33,40` | `log.Fatalln` 之后仍写 `return`(不可达语句);错误处理混用标准库 `log` 与 SDK `printer`(`fetch.go:30`、`get.go:35` 用 `printer.Error`),日志出口不统一。 | +| 低 | `internal/config/config.go:35` | 只校验 `Spec.Service`、`Spec.Cache`;`Databases` 缺失靠 `impl/with.go:34-36` 的 `panic("No Database Source Found !")` 兜底(错误信息不含缺哪一项),`Gateway` 为 nil 时 `service.Start` 会静默不启网关(SDK `service/service.go:106`),配置错误与"故意不启用"无法区分。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| A1 | 高 | `Get`/`Modify`/`Delete` 无 `owner` 归属校验(`get.go:29`、`modify.go:39`、`delete.go:24`) | 用户隐私(姓名/手机号/住址)、地址数据完整性 | +| A2 | 高 | `Fetch` 命名返回值 `reply` 从未赋值(`fetch.go:23-40`) | 核心功能不可用(地址列表恒空) | +| A3 | 高 | 默认地址唯一性靠两次独立 SQL,无事务、无唯一约束(`create.go:41-45`、`modify.go:35-39`) | 数据一致性(多默认/无默认) | +| A4 | 高 | 非 JWT 的 `Authorization` 头触发 SDK `ParseJwt` 空指针,且无 recover 拦截器(`internal/server/new.go:23` + SDK `crypto/token/jwt.go:68`) | 服务可用性(进程崩溃,可被任意人触发) | +| A5 | 中 | `Modify` 走结构体更新且缺 `name`/`pics` 映射(`modify.go:24-33,39`) | 功能残缺(字段改不动/清不掉) | +| A6 | 中 | `status` 无校验 + 删除语义双轨(`create.go:39`、`modify.go:33`、`delete.go:24` vs `proto/address.proto:36`) | 数据语义混乱 | +| A7 | 中 | 分页契约缺失:proto 有 `page_no/page_size`,实现无入参、无过滤、无 LIMIT(`proto/address.proto:52-56`、`fetch.go:28`) | 性能(全量返回)、契约与实现不符 | +| A8 | 中 | 生产 Postgres 无条件 `db.Debug()`(`internal/models/impl.go:82`) | 性能、敏感信息落盘 | +| A9 | 中 | 独立进程 HTTP 网关因 `Mux` 从未赋值而不可用(`internal/server/new.go:16`、`cmd/main/main.go:32`) | 可用性(生成器级、全模块共有) | +| A10 | 低 | 配置复制自 order、`const.pb.go` 3168 行冗余生成物、`cmd/cli` 与 README 占位、无任何测试 | 可维护性、运维误判 | + +## 8. 修复建议(务实项) + +1. **补三处归属条件(A1,最高优先)**:`get.go:29`、`modify.go:39`、`delete.go:24` 的 `Where` 各追加 `owner_identity = ?`(值取 `auth.Identity`),与 `fetch.go:28` 口径对齐;`get.go:19`、`delete.go:19` 把丢弃的 `auth` 接回来。落地前建议先按 `id` 与 `owner_identity` 双条件做一次存量数据核对,确认不存在跨 owner 的重复 id 语义。 +2. **修 `Fetch` 返回(A2)**:`fetch.go:38-40` 改为 `return &pb.AddressListReply{Data: result}, nil`,并删除 `:38` 的 TODO 行。 +3. **默认地址(A3)**:把 `create.go:41-45`、`modify.go:35-39` 的"清默认 + 写入"用 `impl.DBService.Transaction(func(tx *gorm.DB) error {...})` 包成一个原子操作;同时在迁移里给 `address_library` 加 `UNIQUE(owner_id) WHERE status = 2` 索引(PostgreSQL 部分唯一索引),让数据库兜住并发。 +4. **`Modify` 修正(A5)**:`:39` 改为显式 `Updates(map[string]any{...})`(键为列名,含 `name`/`pics`),这样既能清空字段也能写入 0;同时把 `in.Name`、`in.Pics` 补进映射。 +5. **入参校验(A6)**:`create.go:39`、`modify.go:33` 之后加 status 白名单(只允许 1、2;需要 "删除" 就走 `Delete`),并在 `create.go` 补 `phone`/`contact`/`detail` 非空校验与长度上限(对应列 `varchar(20)`/`varchar(255)`)。 +6. **统一删除语义(A6)**:二选一并同步注释——保留 GORM 软删除就把 proto/模型的"删除=status -1"注释删掉;要按 `status=-1` 实现就把 `delete.go:24` 改成 `Update("status", -1)` 并让 `get/fetch` 都过滤 `status <> -1`。 +7. **列表可用性(A7)**:`fetch.go:28` 至少补 `Where("status <> ?", -1)`、`Order("id desc")` 与 `Limit(200)` 之类的硬上限;若确定不做分页,就删掉 `proto/address.proto:52-56` 的 `FetchRequest` 与 TS 里的分页类型,避免契约撒谎。 +8. **降低 panic 面(A4)**:本模块侧给 `internal/server/new.go:23` 的 `grpc.NewServer()` 装上带 `recover` 的 unary 拦截器(异常时返回 `codes.Internal`),避免任意请求把进程带走;根因在 SDK `crypto/token/jwt.go:68`(应为 `if err != nil { return nil, err }` 后再取 `token.Claims`),建议对 SDK 提缺陷单。 +9. **关闭生产 SQL 日志(A8)**:把 `internal/models/impl.go:82` 的无条件 `db.Debug()` 改为按配置决定,并让 `internal/impl/with.go:41` 传入真实的 `*types.SqlOptions`(或把 `Debug` 补进 `conf.DBConf`,当前 etc 中的 `Debug` 键是被静默忽略的)。 +10. **清理与运维(A9/A10)**:`internal/server/new.go:26-30` 补上 `Mux: gwRuntime.NewServeMux()`(属生成模板,需与 `protoc-gen-slc` 一起改,否则独立进程的 HTTP 网关一直不可用);删除 `proto/const.proto` 与 `pb/const.pb.go` 中本模块用不到的 blocks 定义;修正三个 yaml 的 `Service: order` 与 `Anonymous: order.ping.hello`;替 `cmd/cli` 或直接删除;用一条集成测试覆盖"用户 A 不能读/改/删用户 B 的地址 + 列表非空"这两个最关键的回归点。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。"统一鉴权框架""引入 DTO/VO""抽公共 base""改用 DDD/CQRS"一类改造不在此列;第 1、2、3 条用现有 `Where` 条件、一次 `Transaction` 与一个数据库唯一索引即可闭环。 diff --git a/docs/ads.md b/docs/ads.md new file mode 100644 index 0000000..6c5e04d --- /dev/null +++ b/docs/ads.md @@ -0,0 +1,145 @@ +# ads(公共广告位内容分发)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/ads` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件 12 个、385 行(含 `test/rpc/rpc.go` 23 行空壳);`proto` 2 个(`ads.proto`、`const.proto`);`pb/` 生成文件 4 个 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、聚合入口 `pkgs/all`(`pkgs/all/internal/service/ads.go`) | +| 对外协议 | gRPC + REST Gateway,REST 路径 `/ads.Fetch/ByPos` | +| 结论摘要 | 全模块只有 1 个只读接口 `ByPos`,落库查询用 `pos_key + status=1` 过滤,能正确屏蔽未启用广告;但 README 宣称的"Redis 缓存 10 分钟 / 广告位 30 分钟缓存"**从未实现**(`RedisService`/`MemorySerice` 初始化后无任何引用),因此不存在缓存一致性问题;鉴权策略在"模块 yaml 声明匿名"与"聚合部署未放行"之间自相矛盾;`conf.NotNil` 未校验 `Databases`,缺失时启动直接 panic。 | + +## 1. 服务定位与职责 + +按广告位 `key` 查询该广告位下**已启用**的广告内容(文本/图片/视频/音频/链接/附件),供客户端页面渲染。它只做"读取与分发",不提供广告位/广告的增删改,也不负责投放计费、定向或素材处理。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New` → `impl.NewImpl` → `server.New(nil)` → `service.New(...).Start()`;`Run()` 与 `main()` 分离供聚合入口复用 | +| `cmd/cli/main.go` | 命令行入口,仅 `log.Println("广告服务命令行工具")`(8 行,占位) | +| `internal/config/config.go` | 配置结构(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`APM`/`Etcd`)与校验 | +| `internal/impl/impl.go` | 初始化 DB / Redis / Etcd / 内存缓存实例 | +| `internal/logic/fetch/by_pos.go` | `ByPos` 唯一业务逻辑:按 `pos_key + status=1` 查库并转 pb | +| `internal/models/ads_item.go` | `ads_item` 表模型 + `database.AppendMigrate` 注册 | +| `internal/models/ads_pos.go` | `ads_pos`(广告位)表模型 + 迁移注册,全模块无读写 | +| `internal/server/fetch_server.go` | `FetchServer.ByPos` 转发到 `logic/fetch` | +| `internal/server/new.go` | 注册 `Fetch` 到 gRPC,独立运行时开 `reflection` | +| `internal/routers` | **不存在**(本模块走 gRPC + HTTP Gateway,无 Gin 路由) | +| `service/{expose,dependencies}.go` | 聚合宿主注入:`Expose` 注册 Gateway handler,`applyDependencies` 覆盖 Redis/Etcd/DB/Cache | +| `proto/{ads,const}.proto` | `ads.proto` 定义 `Fetch.ByPos`;`const.proto` 与本模块无关 | +| `etc/{ads_dev,ads_prod,ads_test}.yaml` | 三份内容一致,Port 12216、Gateway 12102 | +| `test/rpc/rpc.go` | 整个 `main` 被注释(23 行空壳) | + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/ads.Fetch/ByPos` | 按广告位 `key` 返回已启用广告列表 | 无(模块 yaml 登记匿名,`etc/ads_dev.yaml:15-16`) | `internal/logic/fetch/by_pos.go` | +| POST | gRPC `/ads.Fetch/ByPos` | 同上(Gateway 反向代理同一 handler) | 同上 | `pb/ads.pb.gw.go:71,77` → `internal/server/fetch_server.go:19` | +| HEAD | `/` | 独立入口健康检查 | 无 | 【信息不足】由 `git.apinb.com/bsm-sdk/core/service` 提供,本模块未声明 | + +**声明但未实现/占位的接口**:无。`proto/ads.proto:6-9` 只声明 `Fetch.ByPos` 一个 rpc,`pb/ads_grpc.pb.go` 的 `FetchServer` 接口也只有 `ByPos`,二者一致;不存在"proto 声明了但 server 未实现"的方法。 + +> 鉴权补充:`etc/ads_dev.yaml:14` 为 `MicroService.Enable: false`,而 `service.Start` 只在 `MsConf.Enable == true` 时才注册服务并发布匿名清单(`D:\work\bsm-sdk\core\service\service.go:66-90`),因此独立部署时模块 yaml 的匿名清单**不会生效**,接口是否免鉴权完全取决于前置网关。 + +## 4. 数据模型与表 + +### 表 `ads_item`(`internal/models/ads_item.go`,GORM 自动迁移) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键(来自 `gorm.Model`) | +| `created_at`/`updated_at`/`deleted_at` | timestamp | - | `gorm.Model` 提供,带软删 | +| `title` | varchar(255) | not null | 广告名称 | +| `pos_key` | varchar(255) | not null | 广告位 key(无索引声明) | +| `content` | varchar(255) | default '' | 广告内容 | +| `type` | int | default 0 | 广告类型,`ContentType` 常量从 1 起(1 文本…6 附件) | +| `to_url` | varchar(255) | default '' | 跳转链接 | +| `status` | int64 | default 0, index | 来自内嵌 `types.Std_Status`(`D:\work\bsm-sdk\core\types\db.go:74-76`) | + +### 表 `ads_pos`(`internal/models/ads_pos.go`) + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` | uint | PK(来自 `types.Std_IdCreated`) | +| `created_at` | timestamp | 创建时间 | +| `key` | varchar(255) | 广告位标识 | +| `name` | varchar(255) | 广告位名称 | + +> 该表模型在本模块内仅注册迁移(`ads_pos.go:21-23`),**全模块无任何读写**;`ByPos` 也不校验 `pos_key` 是否存在于 `ads_pos`。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /ads.Fetch/ByPos"] --> B["校验 in.Key 非空,否则 ErrInvalidArgument"] + B --> C["SELECT * FROM ads_item WHERE pos_key = ? AND status = 1"] + C --> D["逐条映射为 pb.AdsItem(含 CreatedAt 格式化)"] + D --> E["返回 ByPosReply{data: [...]}"] + C --> F["查询失败返回 errcode.ErrDB"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `etc/ads_dev.yaml:15-16`、`etc/ads_prod.yaml:15-16`、`etc/ads_test.yaml:15-16` | `- ads.Fetch.ByPos` 被列入 `MicroService.Anonymous`,属**无鉴权读取**的设计声明;同时 `MicroService.Enable: false`(`ads_dev.yaml:14`)使该清单在独立部署时根本不会发布到 etcd,等于"声明了但不会生效",容易让部署方误判防护已就位。 | +| 中 | `pkgs/all/etc/default_dev.yaml:24-43` | 聚合入口的 `Authorization.Anonymous` 白名单里**没有** `/ads.Fetch/ByPos`(只有 passport/mall/market 登录、`/rest/fts/ping|config`、`/rest/logs/ping`、`/rest/mgt/*`)。与模块 yaml 的匿名声明直接矛盾:按模块配置匿名调用会被聚合网关按 `pkgs/all/internal/server/authorization.go:42-66` 拒绝,"开发可用、上线 401"。 | +| 低 | `internal/logic/fetch/by_pos.go:21` | 广告查询只以 `pos_key` 为唯一凭据,无 app / 店铺 / 租户维度的归属过滤,`ads_item` 也没有归属字段。任何能调用该接口的客户端,只要知道 `pos_key`,就取到完全相同的一批广告;无法支持"按端/按租户下发不同广告"。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| — | `internal/logic/fetch/by_pos.go:21` | `Where("pos_key = ? AND status = ?", in.Key, 1)`:**已正确过滤启用状态,不存在"可查未启用广告"的问题**(按任务要求核实,特此说明)。 | +| 低 | `internal/logic/fetch/by_pos.go:21` | `Find` 无 `LIMIT`,单个 `pos_key` 下条目数不受约束,接口全量返回;大广告位会一次性吐出全部记录。 | +| 低 | `internal/models/ads_item.go:32` 与 `:18-25` | `type` 列 `default:0`,但 `ContentType` 常量从 1 开始(`CONTENT_TYPE_TEXT iota + 1`)。`0` 无对应类型,历史数据或未显式赋值的记录会返回前端无法识别的 `type`。 | +| 低 | `internal/models/ads_item.go:31` | `content` 为 `varchar(255)`,而图片/视频/链接类广告的 `content` 实际承载 URL,255 上限偏紧;超长时不同数据库行为不同(PostgreSQL 报错、MySQL 截断),仓库内 yaml 为 postgres【具体行为取决于驱动,信息不足】。 | +| 低 | `internal/logic/fetch/by_pos.go:13,21` | 入参 `ctx` 未被使用,请求上下文(超时/取消)不会传导到 DB 查询,无法对慢查询做超时控制。 | + +### 6.3 未完成实现 + +- **缓存完全未落地,README 属不实描述**:`README.md:13-14`("Redis缓存""智能缓存:10分钟缓存策略")、`README.md:110`("Redis缓存加速,缓存时间10分钟")、`README.md:319-322`("广告数据 10 分钟 / 广告位信息 30 分钟"缓存策略表)均宣称有缓存;但 `internal/logic/fetch/by_pos.go:21` 直接查库,`internal/impl/impl.go:13,16,22,24` 初始化的 `RedisService` 与 `MemorySerice` 在本模块**没有任何引用**(全仓检索仅命中 `internal/impl/impl.go` 与 `service/dependencies.go:21,30` 的赋值点)。结论:**不存在缓存,也就不存在缓存一致性问题**;`README.md:315-339` 的"性能优化/监控指标"章节同样与实现不符。 +- `proto/const.proto:1-326` 定义了 `OrderSummaryItem`、`FeedPostItem`、`GroupPostItem`、`RelationItem`、`MarketLoginReply` 等大量与广告无关的共享消息,本模块未引用任何一条 → 死定义,且被 `pb/const.pb.go` 一并编译进本模块。 +- `internal/models/ads_pos.go:15-28` 的 `ads_pos` 模型仅注册迁移,无任何查询/写入 → 预留实现(表由其它模块维护)。 +- `cmd/cli/main.go:1-8` 仅打印一行;`test/rpc/rpc.go:3-23` 整个函数体被注释,且引用的是不存在的 `pb.NewMethodClient`/`pb.Crc` → 占位/死代码。 +- `etc/ads_dev.yaml:26-29` 的 `Rpc` 段被注释,`internal/config/config.go:20` 的 `SrvConfig.Rpc` 字段全模块无使用点 → 死配置。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/config/config.go:37` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`。而 `with.Databases`(`D:\work\bsm-sdk\core\with\databases.go:13-15`)在 `cfg == nil \|\| len(cfg.Source) == 0` 时直接 `panic("No Database Source Found !")`,因此缺 `Databases` 段时 `impl.NewImpl()`(`internal/impl/impl.go:26`)在**启动阶段就 panic**,且报错文本不含配置项名,排查成本高。 | +| 低 | `internal/impl/impl.go:16` | 变量名拼写错误 `MemorySerice`(应为 `MemoryService`)。同一 workspace 的 `pkgs/all/internal/impl` 用的是正确拼写 `MemoryService`(见 `pkgs/all/internal/service/ads.go:15`),跨模块阅读容易误判为两个不同变量。 | +| 低 | `internal/impl/impl.go:13,16,22,24` | `RedisService`、`MemorySerice` 初始化后在本模块无使用点(`EtcdService` 仅透传给 `service.Options.EtcdClient`,`cmd/main/main.go:34`)→ 死代码。 | +| 低 | `README.md:88-96` | README 声明的 `swagger/`、`scripts/`、`Dockerfile`、`Makefile` 在仓库中均不存在(与本模块实际文件清单不符),属虚构文档。 | +| 低 | 全模块 | **无任何 `*_test.go`**;`test/rpc/rpc.go` 为注释空壳,无法覆盖 `ByPos` 的过滤语义。 | +| 低 | `internal/logic/fetch/by_pos.go:15-17` | 只校验 `in.Key == ""`,无长度/字符白名单与上限约束。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| A1 | 中 | 鉴权策略两处矛盾:模块 yaml 声明 `ByPos` 匿名,聚合部署 `Anonymous` 未放行 | 上线 401 / 误判防护已生效 | +| A2 | 中 | 广告读取仅凭 `pos_key`,无租户/端隔离 | 内容越权读取、无法按租户差异化投放 | +| A3 | 中 | `Databases` 未校验,缺失即启动 panic 且报错不明确 | 部署可用性、排障成本 | +| A4 | 低 | README 宣称的缓存/性能/监控能力全部未实现 | 文档误导、容量评估失真 | +| A5 | 低 | `ByPos` 无返回条数上限 | 大广告位全量返回、响应体膨胀 | +| A6 | 低 | 死代码与死配置(`const.proto`、`ads_pos`、`cmd/cli`、`test/rpc`、`Rpc` 段)+ 无自动化测试 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **鉴权策略对齐(二选一,必须选)**:若 `ByPos` 应当匿名,则把 `/ads.Fetch/ByPos` 加入 `pkgs/all/etc/default_dev.yaml:24-43` 的 `Authorization.Anonymous`(并同步生产配置);若应当鉴权,则从 `etc/ads_{dev,prod,test}.yaml:15-16` 的匿名清单中删除该条。注意 `MicroService.Enable: false` 时模块匿名清单不会生效,不要把它当作生效依据。 +2. **配置校验**:`internal/config/config.go:37` 的 `conf.NotNil` 补上 `Databases`;或在 `impl.NewImpl` 前显式判空并打印缺失的配置项名。 +3. **返回值上限**:`internal/logic/fetch/by_pos.go:21` 的 `Find` 增加 `.Limit(...)`(如 100),避免单广告位无界返回。 +4. **类型默认值**:`internal/models/ads_item.go:32` 的 `type` 默认值改为 `1`(文本),或在 `ByPos` 中跳过 `type == 0` 的记录,避免下发未定义类型。 +5. **上下文传导**:`by_pos.go` 改用 `impl.DBService.WithContext(ctx)` 发起查询,使上游超时/取消能生效。 +6. **文档与死代码清理**:删除/改写 `README.md:13-14,110,319-339` 中"Redis 缓存 10 分钟""广告位 30 分钟缓存""swagger/scripts/Dockerfile/Makefile"等不实内容;删除 `proto/const.proto` 与本模块无关的消息、`test/rpc/rpc.go`、`etc/ads_dev.yaml:26-29` 的 `Rpc` 注释段;变量名 `MemorySerice` → `MemoryService`,并删除未使用的 `RedisService`/`MemorySerice` 初始化(或按其文档用途落地)。 +7. **补测试**:为 `ByPos` 增加 2~3 条最小用例——命中启用广告、同 `pos_key` 下含 `status != 1` 的记录(须被过滤)、`key` 为空。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 diff --git a/docs/cloud.md b/docs/cloud.md new file mode 100644 index 0000000..64cc298 --- /dev/null +++ b/docs/cloud.md @@ -0,0 +1,373 @@ +# cloud(个人云空间与资料管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/cloud` | +| 服务域 | 基础与平台服务(个人云空间:网盘 / 相册 / 笔记 / 收藏 / 私人资料 / 分享 / 空间统计) | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件约 80 个(全部手写:7 个 server 注册、59 个 logic 实现、11 个 models、config/impl/cmd 各 1);proto 9 个、etc yaml 3 个、README 1 个;生成代码 `pb/*.go` 仅看接口签名 | +| 入口 | 无独立 `cmd/main`(仅 `cmd/cli/main.go` 为 "Hello World" 桩);作为 gRPC 服务经聚合入口 `pkgs/all`、`pkgs/ecmall` 接入 | +| 对外协议 | gRPC(7 个 service)+ HTTP Gateway(grpc-gateway 默认路由 `/cloud./`,proto 无 `google.api.http` 注解) | +| 结论摘要 | 接口全集已实现、均以 `service.ParseMetaCtx` 做网关鉴权、并以 `passport_id = auth.ID` 做归属隔离(无跨用户越权读)。但存在**私人资料"加密"未真正落地(明文存储、密钥由客户端传入、加解密为无状态工具不落库)**、**空间默认 `key_identifier="default"` 与全局唯一索引冲突导致多用户仅首名可用**、**GetDir/GetDirTree 对根目录 `ParentID` 空指针解引用 panic**、**容量配额仅统计不限制**、**分享链路断裂(收件人无法取得他人资源、CreateShare 不校验资源归属)**等严重缺陷。 | + +## 1. 服务定位与职责 + +个人云空间聚合服务,包含七个子域:云盘目录/文件(disk)、相册/照片(album)、笔记/附件(note)、书签收藏(bookmark)、私人加密资料(private)、分享(share)、空间统计(space)。每个子域通过 gRPC 暴露 CRUD 与列表/搜索接口,由 HTTP Gateway 透出 REST。多用户数据隔离通过 `passport_id`(网关注入的身份)实现,所有归属校验在 logic 层以 `WHERE passport_id = ?` 完成。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/cli/main.go` | CLI 桩,仅打印 "Hello World",无实质逻辑 | +| `internal/config/config.go` | 配置结构(Base、Databases、MicroService、Rpc、Gateway、Apm、Etcd)与校验 | +| `internal/impl/impl.go` | 初始化 Redis、Etcd、DB、Memory 等共享实例 | +| `internal/server/*.go` | 7 个 gRPC server 注册(Disk/Album/Bookmark/Note/Private/Share/Space),逐方法转发到 logic | +| `internal/logic/disk/*` | 目录树、文件上传/移动/复制/搜索(15 个实现) | +| `internal/logic/album/*` | 相册与照片(12 个实现) | +| `internal/logic/note/*` | 笔记与附件(10 个实现) | +| `internal/logic/bookmark/*` | 书签与导入(6 个实现) | +| `internal/logic/private/*` | 私人资料与加解密(9 个实现) | +| `internal/logic/share/*` | 分享与密码校验(5 个实现) | +| `internal/logic/space/*` | 空间统计(2 个实现) | +| `internal/models/*.go` | 11 个 GORM 模型 + `query.go`(`CloudBase` 内嵌结构) | +| `proto/*.proto` | 7 个 service 定义 + `const.proto` 公共消息 | +| `etc/cloud_dev.yaml` 等 | 运行配置(Gateway 开启、MicroService.Anonymous 含失效项 `cloud.ping.hello`) | + +依赖注入与 `logs` 模块不同:本模块通过 `service.ParseMetaCtx(ctx, nil)` 从网关注入的 metadata 解析 `auth.ID/Identity`,每个 logic 函数首行即调用并校验,缺失即返回错误,因此**所有接口均在网关层强制鉴权**。 + +## 3. 接口清单 + +> 鉴权列统一说明:全部接口经网关鉴权(`ParseMetaCtx` 缺失即报错)。除特别标注外,归属校验均为 `WHERE passport_id = auth.ID`。REST 路径为 grpc-gateway 默认格式 `/cloud./`(proto 未定义 http 注解)。 +> +> **声明但未实现 / 占位**:本次审计中,全部 59 个 proto 方法均有对应 logic 实现,未发现 TODO 占位。但"私人资料加密存储"与"分享向他人交付资源"两块在逻辑上未真正闭合(见 6.2/6.3)。 + +### Disk(15) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreateDir | `/cloud.Disk/CreateDir` | 建目录(校验父目录归属) | passport 归属 | `logic/disk/create_dir.go:20` | +| GetDir | `/cloud.Disk/GetDir` | 目录详情(含子目录/文件) | passport 归属 | `logic/disk/get_dir.go:16` | +| UpdateDir | `/cloud.Disk/UpdateDir` | 改名/重算路径 | passport 归属 | `logic/disk/update_dir.go:19` | +| DeleteDir | `/cloud.Disk/DeleteDir` | 删目录(空目录才允许) | passport 归属 | `logic/disk/delete_dir.go:17` | +| ListDirs | `/cloud.Disk/ListDirs` | 分页列表 | passport 归属 | `logic/disk/list_dirs.go:14` | +| GetDirTree | `/cloud.Disk/GetDirTree` | 目录树 | passport 归属 | `logic/disk/get_dir_tree.go:16` | +| MoveDir | `/cloud.Disk/MoveDir` | 移动目录 | passport 归属 | `logic/disk/move_dir.go:19` | +| UploadFile | `/cloud.Disk/UploadFile` | 上传文件(校验目录归属) | passport 归属 | `logic/disk/upload_file.go:19` | +| GetFile | `/cloud.Disk/GetFile` | 文件详情(JOIN dir) | passport 归属 | `logic/disk/get_file.go:16` | +| UpdateFile | `/cloud.Disk/UpdateFile` | 改文件名等 | passport 归属 | `logic/disk/update_file.go:18` | +| DeleteFile | `/cloud.Disk/DeleteFile` | 删文件 | passport 归属 | `logic/disk/delete_file.go:17` | +| ListFiles | `/cloud.Disk/ListFiles` | 分页列表(JOIN dir) | passport 归属 | `logic/disk/list_files.go:14` | +| MoveFile | `/cloud.Disk/MoveFile` | 移动文件 | passport 归属 | `logic/disk/move_file.go:17` | +| CopyFile | `/cloud.Disk/CopyFile` | 复制文件 | passport 归属 | `logic/disk/copy_file.go:19` | +| SearchFiles | `/cloud.Disk/SearchFiles` | 关键字搜索 | passport 归属 | `logic/disk/search_files.go:15` | + +### Album(12) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreateAlbum | `/cloud.Album/CreateAlbum` | 建相册 | passport 归属 | `logic/album/create_album.go:19` | +| GetAlbum | `/cloud.Album/GetAlbum` | 相册详情(含照片) | passport 归属 | `logic/album/get_album.go:16` | +| UpdateAlbum | `/cloud.Album/UpdateAlbum` | 改相册 | passport 归属 | `logic/album/update_album.go:18` | +| DeleteAlbum | `/cloud.Album/DeleteAlbum` | 删相册(先删照片) | passport 归属 | `logic/album/delete_album.go:17` | +| ListAlbums | `/cloud.Album/ListAlbums` | 分页列表(含照片) | passport 归属 | `logic/album/list_albums.go:14` | +| SetCoverPhoto | `/cloud.Album/SetCoverPhoto` | 设封面(校验照片归属相册) | passport 归属 | `logic/album/set_cover_photo.go:17` | +| UploadPhoto | `/cloud.Album/UploadPhoto` | 传照片(校验相册归属) | passport 归属 | `logic/album/upload_photo.go:19` | +| GetPhoto | `/cloud.Album/GetPhoto` | 照片详情(JOIN album) | passport 归属 | `logic/album/get_photo.go:16` | +| UpdatePhoto | `/cloud.Album/UpdatePhoto` | 改照片 | passport 归属 | `logic/album/update_photo.go:17` | +| DeletePhoto | `/cloud.Album/DeletePhoto` | 删照片 | passport 归属 | `logic/album/delete_photo.go:17` | +| ListPhotos | `/cloud.Album/ListPhotos` | 分页列表 | passport 归属 | `logic/album/list_photos.go:14` | +| MovePhoto | `/cloud.Album/MovePhoto` | 移动照片到其他相册 | passport 归属 | `logic/album/move_photo.go:17` | + +### Bookmark(6) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreateBookmark | `/cloud.Bookmark/CreateBookmark` | 建书签 | passport 归属 | `logic/bookmark/create_bookmark.go:19` | +| GetBookmark | `/cloud.Bookmark/GetBookmark` | 书签详情(仅 id) | passport 归属(`id+passport`) | `logic/bookmark/get_bookmark.go:16` | +| UpdateBookmark | `/cloud.Bookmark/UpdateBookmark` | 改书签 | passport 归属 | `logic/bookmark/update_bookmark.go:18` | +| DeleteBookmark | `/cloud.Bookmark/DeleteBookmark` | 删书签(仅 id) | passport 归属(`id+passport`) | `logic/bookmark/delete_bookmark.go:17` | +| ListBookmarks | `/cloud.Bookmark/ListBookmarks` | 分页列表 | passport 归属 | `logic/bookmark/list_bookmarks.go:14` | +| ImportBookmarks | `/cloud.Bookmark/ImportBookmarks` | 批量导入 | passport 归属 | `logic/bookmark/import_bookmarks.go:21` | + +### Note(10) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreateNote | `/cloud.Note/CreateNote` | 建笔记 | passport 归属 | `logic/note/create_note.go:19` | +| GetNote | `/cloud.Note/GetNote` | 笔记详情(含附件) | passport 归属 | `logic/note/get_note.go:16` | +| UpdateNote | `/cloud.Note/UpdateNote` | 改笔记 | passport 归属 | `logic/note/update_note.go:18` | +| DeleteNote | `/cloud.Note/DeleteNote` | 删笔记(先删附件) | passport 归属 | `logic/note/delete_note.go:17` | +| ListNotes | `/cloud.Note/ListNotes` | 分页列表 | passport 归属 | `logic/note/list_notes.go:14` | +| TogglePin | `/cloud.Note/TogglePin` | 置顶切换 | passport 归属 | `logic/note/toggle_pin.go:17` | +| IncrementViews | `/cloud.Note/IncrementViews` | 浏览+1 | passport 归属(仅本人) | `logic/note/increment_views.go:17` | +| SearchNotes | `/cloud.Note/SearchNotes` | 关键字搜索 | passport 归属 | `logic/note/search_notes.go:15` | +| InsertAttachment | `/cloud.Note/InsertAttachment` | 加附件 | passport 归属 | `logic/note/insert_attachment.go:17` | +| DeleteAttachment | `/cloud.Note/DeleteAttachment` | 删附件(JOIN note) | passport 归属 | `logic/note/delete_attachment.go:17` | + +### Private(9) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreatePrivateData | `/cloud.Private/CreatePrivateData` | 建私人资料 | passport 归属 | `logic/private/create_private_data.go:19` | +| GetPrivateData | `/cloud.Private/GetPrivateData` | 详情 | passport 归属 | `logic/private/get_private_data.go:16` | +| UpdatePrivateData | `/cloud.Private/UpdatePrivateData` | 改资料 | passport 归属 | `logic/private/update_private_data.go:18` | +| DeletePrivateData | `/cloud.Private/DeletePrivateData` | 删资料 | passport 归属 | `logic/private/delete_private_data.go:17` | +| ListPrivateData | `/cloud.Private/ListPrivateData` | 分页列表 | passport 归属 | `logic/private/list_private_data.go:14` | +| GetPrivateDataByType | `/cloud.Private/GetPrivateDataByType` | 按类型查 | passport 归属 | `logic/private/get_private_data_by_type.go:16` | +| SearchPrivateData | `/cloud.Private/SearchPrivateData` | 搜索 | passport 归属 | `logic/private/search_private_data.go:15` | +| EncryptData | `/cloud.Private/EncryptData` | AES 加密(无状态工具) | 仅网关鉴权,`auth` 被丢弃、不落库 | `logic/private/encrypt_data.go:19` | +| DecryptData | `/cloud.Private/DecryptData` | AES 解密(无状态工具) | 仅网关鉴权,`auth` 被丢弃、不落库 | `logic/private/decrypt_data.go:17` | + +### Share(5) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| CreateShare | `/cloud.Share/CreateShare` | 建分享 | passport 归属(不校验 resource_id 归属) | `logic/share/create_share.go:19` | +| GetShare | `/cloud.Share/GetShare` | 分享详情 | passport 归属(仅创建者可见) | `logic/share/get_share.go:16` | +| DeleteShare | `/cloud.Share/DeleteShare` | 删分享 | passport 归属 | `logic/share/delete_share.go:17` | +| ListShares | `/cloud.Share/ListShares` | 列表 | passport 归属 | `logic/share/list_shares.go:14` | +| ValidateSharePassword | `/cloud.Share/ValidateSharePassword` | 校验密码 | 仅网关鉴权;按 `identity` 查、无 passport 限制、不校验 is_public | `logic/share/validate_share_password.go:18` | + +### Space(2) + +| 方法 | REST 路径 | 功能 | 鉴权/归属 | 实现位置 | +| --- | --- | --- | --- | --- | +| Get | `/cloud.Space/Get` | 空间统计(缺则建默认) | passport 归属 | `logic/space/get.go:16` | +| GetByKeyIdentifier | `/cloud.Space/GetByKeyIdentifier` | 按 key 查空间 | passport 归属 | `logic/space/get_by_key_identifier.go:16` | + +## 4. 数据模型与表 + +> 公共内嵌:`types.Std_IICUDS`(id、created_at、updated_at、deleted_at)、`types.Std_Passport`(passport_id、passport_identity)、`CloudBase`(cloud_id、cloud_identity)。表名由 GORM 取 struct 蛇形复数。 + +### 表 `cloud_disk_dir`(云盘目录) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id | uint | PK | 自增 | +| passport_id / passport_identity | uint / varchar | 归属 | 来自 Std_Passport | +| cloud_id / cloud_identity | uint / varchar(36) | Index | 来自 CloudBase | +| parent_id | *uint | index, 可空 | 父目录;根目录为 nil | +| name | varchar(100) | - | 目录名 | +| path | varchar(500) | - | 完整路径 | +| created_at/updated_at/deleted_at | - | - | 时间戳 | + +### 表 `cloud_disk_file`(云盘文件) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id | uint | PK | 自增 | +| passport_id 等 | - | 归属 | Std_Passport | +| directory_id | *uint | index, 可空 | 所属目录 | +| name / original_name | varchar(255) | - | 文件名 | +| size | int64 | - | 字节 | +| mime_type | varchar(100) | - | 类型 | +| storage_path | varchar(500) | - | 实际存储路径 | +| hash | varchar(64) | - | 去重哈希 | + +### 表 `cloud_album`(相册) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| name | varchar(100) | - | 名称 | +| description | varchar(500) | - | 描述 | +| cover_photo | varchar(255) | - | 封面 URL | +| is_private | bool | default false | 是否私有 | + +### 表 `cloud_photo`(照片) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id | uint | PK | 自增 | +| **无 passport 列** | - | - | 归属经 album 间接隔离 | +| album_id | uint | index | 所属相册 | +| title/description | varchar(100)/(500) | - | - | +| file_path | varchar(500) | - | 路径 | +| file_size | int64 | - | 大小 | +| mime_type | varchar(100) | - | 类型 | +| width/height | int | - | 尺寸 | +| taken_at | time | - | 拍摄时间 | +| location | varchar(200) | - | 地点 | +| tags | varchar(500) | - | 标签 | + +### 表 `cloud_note`(笔记) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| title | varchar(200) | - | 标题 | +| content | text | - | 内容 | +| category | varchar(50) | - | 分类 | +| tags | varchar(500) | - | 标签 | +| is_markdown | bool | default true | - | +| is_pinned | bool | default false | 置顶 | +| is_private | bool | default false | 私有 | +| views | int | default 0 | 浏览数 | + +### 表 `note_attachments`(笔记附件) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id | uint | PK | 自增 | +| note_id | uint | index | 笔记 ID | +| file_name | varchar(255) | - | 文件名 | +| file_path | varchar(500) | - | 路径 | +| file_size | int64 | - | 大小 | +| mime_type | varchar(100) | - | 类型 | +| created_at | time | - | 创建时间 | + +> 注:该表**无 identity 列**,故 `DeleteAttachment` 只能按 id 删除。 + +### 表 `cloud_bookmark`(书签) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| title | varchar(200) | - | 标题 | +| url | varchar(500) | - | 网址 | +| description/category | varchar(500)/(50) | - | - | +| tags/icon | varchar(500) | - | - | +| is_private | bool | default false | 私有 | + +### 表 `cloud_private`(私人资料) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| data_type | varchar(50) | - | password/card/document… | +| title | varchar(200) | - | 标题 | +| description | varchar(500) | - | 描述 | +| data | text | - | **明文存储的实际数据**(未真正加密) | +| is_encrypted | bool | default true | **由客户端请求标记**,非服务端计算 | +| tags | varchar(500) | - | 标签 | + +### 表 `cloud_share`(分享) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| share_type | varchar(20) | - | file/album/note… | +| resource_id | uint | - | 资源 ID(**建分享时不校验归属**) | +| share_token | varchar(32) | uniqueIndex | 分享令牌 | +| password | varchar(100) | - | **明文存储的分享密码** | +| expires_at | time | - | 过期时间 | +| view_count / download_count | int | default 0 | 统计 | +| is_public | bool | default false | **读取链路未使用该字段** | + +### 表 `cloud_space`(空间统计) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| id / passport_id 等 | - | PK/归属 | - | +| key_identifier | varchar(32) | **uniqueIndex(全局)** | 默认写入字面量 `"default"` | +| total_storage / used_storage / max_storage | int64 | - | 容量(**写入从不校验上限**) | +| file_count/album_count/photo_count/note_count/bookmark_count/private_count | int | - | 各类统计 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["客户端请求 GetFile(id)"] --> B["ParseMetaCtx 解析 passport"] + B --> C{"有鉴权?"} + C -- 否 --> E["返回错误"] + C -- 是 --> D["JOIN cloud_disk_dirs WHERE passport_id=auth.ID AND file.id=?"] + D --> F{"命中记录?"} + F -- 否 --> G["返回 ErrInvalidArgument"] + F -- 是 --> H["返回文件详情"] +``` + +```mermaid +flowchart TD + A["分享者 CreateShare(resource_id)"] --> B["仅存分享记录, 不校验 resource_id 归属"] + C["收件人 ValidateSharePassword(identity,password)"] --> D["WHERE identity=? (无 passport 限制, 不校验 is_public)"] + D --> E{"密码/过期校验"} + E -- 失败 --> F["返回错误"] + E -- 通过 --> G["ViewCount++, 返回 OK"] + G --> H["收件人尝试 GetFile/GetNote(resource_id)"] + H --> I["WHERE passport_id=收件人(非分享者)"] + I --> J["无权限, 返回 ErrInvalidArgument"] + J --> K["资源实际无法被收件人获取"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/private/encrypt_data.go:19-75`、`decrypt_data.go:17-81`、`create_private_data.go:54` | **私人资料"加密"未真正落地**。README 宣称"加密存储",但 `EncryptData`/`DecryptData` 是无状态 AES 工具:密钥由客户端 `in.Key` 传入(仅填充/截断到 32 字节),函数不读写任何数据库、不绑定任何 `CloudPrivate` 记录(`auth` 被 `_` 丢弃)。`CreatePrivateData` 直接把请求里的 `Data` 原文与客户端自报的 `IsEncrypted` 落库(`data` 为明文/客户端原文,非服务端密文)。即"加密"仅是客户端行为 + 一个标记位,服务端未对存储数据做任何加密保护。 | +| **高** | `internal/logic/share/create_share.go:19-87`、`get_share.go:30` | **分享链路断裂且可构造越权分享**。`GetShare`/`ListShares`/`DeleteShare` 均以 `passport_id = auth.ID` 限定,仅有创建者能看到自己的分享;而底层资源(文件/笔记/相册)读取仍按 `passport_id` 隔离,收件人即便通过 `ValidateSharePassword` 也无法取得他人资源(见第 5 节流程图)。此外 `CreateShare` 仅校验 `share_type/resource_id/cloud_identity`,**不校验 `resource_id` 是否属于当前用户**,可对任意资源 ID 创建分享记录。 | +| 中 | `internal/logic/share/create_share.go:71`、`get_share.go:54`、`list_shares.go:57`、`models/cloud_share.go:18` | **分享密码明文存储并在响应中回传**。`Password` 以 `varchar(100)` 明文入库,未做哈希;`GetShare`/`ListShares` 直接把 `Password` 回传给调用方。一旦数据库泄露,分享密码即暴露。 | +| 中 | `internal/logic/share/validate_share_password.go:32,43` | **`is_public` 在读取链路完全未生效**。该校验按 `identity` 查(无 passport 限制),密码判断为 `if share.Password != "" && share.Password != in.Password`。对于 `is_public=false` 但 `password=""` 的分享,任意持有 identity(UUID)者都能校验通过。该标志位仅被存储与回传,未参与任何访问控制。 | +| 中 | `internal/config/config.go:35`、`impl/impl.go:23` | **配置未校验 `Databases`**。`conf.NotNil(Spec.Service, Spec.Cache)` 仅校验 Service/Cache,`Databases` 缺失时 `with.Databases(config.Spec.Databases, nil)` 可能返回 nil,`DBService` 为 nil,首个查询即空指针 panic。 | +| 低 | `internal/logic/*/create_*.go`、`upload_*.go` | **`cloud_id`/`cloud_identity` 由请求方自报**,未与网关 `auth` 校验一致。因实际隔离靠 `passport_id`,该字段被当作装饰性元数据;但 `ImportBookmarks` 又硬编码为 `1/"default"`(见 6.2),多处来源不一致,容易被误用。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/disk/get_dir.go:91`、`get_dir_tree.go:97` | **根目录 `ParentID` 空指针解引用 → panic**。模型 `CloudDiskDir.ParentID` 为 `*uint`(根目录为 nil,`create_dir.go:71` 仅当 `ParentId>0` 才赋值)。`GetDir` 返回体 `ParentId: uint64(*dir.ParentID)` 与 `GetDirTree` 第 97 行均无条件解引用,**对任意根目录调用即 panic(gRPC 返回 internal 错误)**。`list_dirs.go:53` 已做 nil 判断,说明作者知晓该可空性,但此处遗漏。 | +| **高** | `internal/logic/space/get.go:36-38,97`、`get_by_key_identifier.go:12`、`models/cloud_space.go:12` | **空间默认 `key_identifier="default"` 与全局唯一索引冲突,多用户仅首名可用**。`CloudSpace.KeyIdentifier` 带 `uniqueIndex`(全局唯一),而 `Space.Get` 在无记录时新建并写入字面量 `"default"`。用户 A 首次调用成功建空间;用户 B 查不到自身空间 → 也尝试建 `key="default"` → 唯一索引冲突 → `Create` 失败 → `Space.Get` 永久报错。**即除首名用户外,所有用户空间统计接口不可用**。 | +| **高** | 全模块写入路径(`upload_file.go`、`copy_file.go`、`create_*.go` 等) | **容量配额从未校验**。`Space` 表虽有 `total_storage/max_storage/used_storage`,但 `Space.Get` 只统计不限制;`UploadFile`/`CopyFile` 等在写入前**没有任何 `used_storage >= max_storage` 检查**。配额是可被无限突破的"展示值"。 | +| 中 | `internal/logic/disk/move_dir.go:47-49` | **目录环检测不全**。`MoveDir` 仅在 `dir.ID == parentDir.ID`(直接自环)时拒绝,未检测"把目录移入其自身后代"这类深层环。一旦成环,`GetDir`/`GetDirTree` 通过 `Preload("Subdirectories")` 加载自关联子树时会无限递归(预加载不防环),导致请求挂起或栈溢出。 | +| 中 | `internal/logic/note/insert_attachment.go:59` | **返回 `Details` 为乱码而非数字 ID**。`Details: string(rune(attachment.ID))` 把整数 ID 当作 Unicode 码点转成单个字符(如 ID=65→"A"),调用方无法据此定位附件。应为 `strconv.FormatUint(...)`。 | +| 中 | `internal/logic/bookmark/import_bookmarks.go:68-70` | **导入书签硬编码 `CloudID:1, CloudIdentity:"default"`**,与 `create_bookmark.go:47` 使用请求 `in.CloudId/in.CloudIdentity` 不一致;导入的书签永远归属到固定云空间,与创建接口语义割裂。 | +| 中 | `internal/logic/space/get.go:61-67`、`get_by_key_identifier.go:52-58` | **`used_storage` 全量加载文件行到内存再累加**:先 `Find(&files)` 取全部文件记录,再 `for` 循环加 `Size`。文件量大时内存与 DB 压力显著,应使用 `SELECT COALESCE(SUM(size),0)`。 | +| 中 | `internal/logic/space/get.go:46-100`、`get_by_key_identifier.go:88-107` | **读取接口内含写副作用**。`Space.Get`/`GetByKeyIdentifier` 在统计后执行 `Save(&space)`,每次读取都会更新 `updated_at` 与统计字段(且保存失败仅打印不报错),违背只读语义、放大写竞争。 | +| 低 | `internal/logic/note/increment_views.go:31` | **`IncrementViews` 受 `passport_id` 限制**,仅本人可自增浏览数;访客/他人打开笔记无法计数,浏览统计实际只能反映"本人打开次数"。 | +| 低 | `internal/logic/note/delete_attachment.go:34-39` | **`identity` 分支为死代码**:if/else 两个分支都写 `note_attachments.id = ?` 且都用 `in.Id`;且 `NoteAttachment` 无 identity 列,按 identity 删除本就不可能。 | + +### 6.3 未完成实现 + +- **私人资料加密存储未落地**:仅有客户端可控的 AES 工具与 `is_encrypted` 标记,服务端未对落库 `data` 做加解密(同 6.1)。README 宣称"数据加密存储""安全的加密算法"与实际不符。 +- **分享向他人交付资源未闭环**:无"凭 share_token/identity 取他人资源"的读取路径,收件人无法通过分享取得内容(同 6.1/6.2)。`is_public` 形同虚设。 +- **无独立进程入口**:`cmd/cli/main.go` 仅为 "Hello World" 桩,模块须作为 gRPC 库经 `pkgs/all`/`pkgs/ecmall` 聚合运行;README 描述的 `cmd/main`、`make run`、`swagger`、`docker-compose`、`tests` 等在本仓库均不存在。 +- **README 与实现不符**:README 声称 EXIF 提取、MIME 自动识别、全文搜索、Redis 缓存策略、swagger 文档、健康检查 `/health`、`/metrics` 等;实际代码中均无对应实现,且 README 内嵌 SQL DDL(`parent_id BIGINT DEFAULT 0`、`CREATE TABLE-cloud_disk_file` 等)与真实 GORM 模型(`*uint` 可空、表名不同)不一致。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `list_dirs.go:25-27`、`list_files.go:25-27`、`list_albums.go:25-27`、`list_photos.go:25-27`、`list_notes.go:25-27`、`search_notes.go:25-27`、`list_bookmarks.go:25-27`、`get_private_data*.go:24-29`、`search_private_data.go:23-28` 等 | **分页 `page_size` 无上限**。各列表均以 `if in.GetPageSize() < 10 { in.PageSize = 50 }` 兜底,但可传入极大值(如 1000000),无上限保护,存在一次拉取全量导致的内存/响应膨胀风险。 | +| 中 | `internal/config/config.go:35` | `conf.NotNil` 未覆盖 `Databases`,配置遗漏时无清晰报错即进入运行期(同 6.1)。 | +| 低 | 全模块 | **无任何单元测试**(`*_test.go` 缺失)。本次发现的空指针 panic、唯一索引冲突、乱码返回等问题若有测试极易暴露。 | +| 低 | `etc/cloud_dev.yaml:14-16` | `MicroService.Anonymous` 含 `cloud.ping.hello`,但本模块**不存在该 service/method**(无 ping/hello 实现),属失效配置;当前因无其它匿名项,副作用是所有接口强制鉴权(结果安全,但配置语义错误)。 | + +> 正向确认:全模块所有 DB 查询均使用 GORM 参数化 `Where("... = ?", ...)`,未发现 SQL 字符串拼接或 `db.Exec/db.Raw(fmt.Sprintf(...))`(已 grep 验证),无 SQL 注入风险。所有写操作均按 `passport_id = auth.ID` 隔离,未发现跨用户越权读取他人目录/相册/笔记/书签/私人资料。 + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| C1 | 高 | 私人资料加密未落地(明文存储、密钥客户端传入、加解密不落库) | 隐私数据保密性 | +| C2 | 高 | 分享链路断裂 + CreateShare 不校验资源归属,收件人无法取得资源、可对任意资源建分享 | 功能不可用 / 潜在越权构造 | +| C3 | 高 | 根目录 `ParentID` 空指针解引用 → GetDir/GetDirTree panic | 接口可用性(500) | +| C4 | 高 | `key_identifier` 全局唯一索引 + 默认 `"default"` → 仅首名用户空间可用 | 多用户空间功能 | +| C5 | 高 | 容量配额仅统计不限制,写入无上限校验 | 资源滥用 / 存储失控 | +| C6 | 中 | 分享密码明文存储并回传 | 凭证泄露 | +| C7 | 中 | `is_public` 读取链路未生效 | 访问控制语义错误 | +| C8 | 中 | 配置未校验 `Databases`,DB 可能为 nil → 运行期 panic | 启动后稳定性 | +| C9 | 中 | MoveDir 目录环检测不全 → 预加载递归死循环 | 接口可用性 / 资源耗尽 | +| C10 | 中 | `insert_attachment` 返回乱码 ID | 调用方无法定位资源 | +| C11 | 中 | `ImportBookmarks` 硬编码 cloud_id=1/"default" | 数据归属不一致 | +| C12 | 中 | Space 统计全量加载文件到内存 + 读取接口内含写副作用 | 性能 / 语义 | +| C13 | 中 | 所有列表 `page_size` 无上限 | 大响应 / 资源膨胀 | +| C14 | 低 | `cloud_id/cloud_identity` 自报且多来源不一致 | 数据一致性 | +| C15 | 低 | `IncrementViews` 仅本人可自增 | 统计失真 | +| C16 | 低 | `delete_attachment` 死代码分支 | 可维护性 | +| C17 | 低 | 无单测 | 可维护性 | +| C18 | 低 | `MicroService.Anonymous` 失效配置项 | 配置正确性 | + +## 8. 修复建议(务实项) + +1. **私人资料加密(C1)**:若需服务端加密,应在 `CreatePrivateData`/`UpdatePrivateData` 落库前用服务端持有的密钥对 `Data` 字段做 AES-GCM 加密、置 `IsEncrypted=true` 并**忽略客户端传入的 `IsEncrypted`**;`EncryptData`/`DecryptData` 应改为对指定 `CloudPrivate` 记录做加/解密并回写,而非无状态工具(或明确改文档说明其为客户端辅助工具,不在服务端落密)。 +2. **空间唯一索引冲突(C4)**:将 `KeyIdentifier` 的唯一索引改为 `(passport_id, key_identifier)` 复合唯一,或在 `Get` 创建默认空间时使用 `utils.UUID()` 而非字面量 `"default"`,避免全局冲突。 +3. **空指针 panic(C3)**:`get_dir.go:91`、`get_dir_tree.go:97` 改为与 `list_dirs.go:53` 一致的 nil 判断(`if dir.ParentID != nil { parentId = uint64(*dir.ParentID) }`,否则置 0)。 +4. **容量配额(C5)**:在 `UploadFile`/`CopyFile` 写入前,按 `passport_id` 取 `CloudSpace`,校验 `used_storage + size <= max_storage`,超限返回明确错误。 +5. **分享闭环(C2)**:为收件人提供"凭 `share_token/identity` + 密码取资源"的读取路径(资源读取接口支持分享者 passport 或经分享校验放行),并在 `CreateShare` 校验 `resource_id` 归属当前用户。 +6. **分享密码(C6/C7)**:`Password` 入库前做哈希(如 bcrypt),`ValidateSharePassword` 用哈希比对;并在该校验中真正使用 `is_public`(非公开且要求密码时才校验密码,公开分享直接放行)。响应中不再回传 `Password`。 +7. **目录环(C9)**:`MoveDir` 在移动前递归检查目标目录是否为当前目录的后代,是则拒绝;或限制 `GetDirTree`/`GetDir` 的预加载深度。 +8. **小缺陷**:`insert_attachment.go:59` 改用 `strconv.FormatUint(attachment.ID,10)`;`import_bookmarks.go` 与 `create_*` 统一 `cloud_id/cloud_identity` 来源;列表 `page_size` 增加上限(如 `min(in.PageSize, 200)`);`Space.Get` 统计改为 `SELECT COALESCE(SUM(size),0)` 且不带写副作用(或拆出独立统计刷新接口);`config.NotNil` 补 `Databases`;删除 `delete_attachment.go` 死代码分支;清理失效的 `cloud.ping.hello` 匿名配置。 +9. **补测试**:针对根目录 GetDir、多用户 Space.Get、大 page_size、目录环、附件 ID 返回等补最小正例/边界用例。 + +> 本报告只列与现有实现直接相关的修复项,不引入统一框架、抽象层、DTO/VO 或 DDD/CQRS 改造。 diff --git a/docs/cms.md b/docs/cms.md new file mode 100644 index 0000000..a64f06d --- /dev/null +++ b/docs/cms.md @@ -0,0 +1,217 @@ +# cms(站点内容管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/cms` | +| 服务域 | 基础与平台服务(内容管理:文章 / 页面 / 分类 / 标签 / 评论 / 赞踩) | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件约 62 个(logic 41、models 10、server 6、service 2、config 1、impl 1、utils 1、cmd 1);pb 生成代码 17 个(post/pages/tags/site/category 三件套 + const + blocks_compat);proto 6 个;`.http` 测试 36 个;无 `cmd/main`、无 yaml 配置、无 `*_test.go` 单测 | +| 入口 | 仅聚合入口 `pkgs/all`、`pkgs/ecmall`(经 `service.Expose` 注册 gRPC + HTTP Gateway);`cmd/cli/main.go` 仅为 `Hello World` 占位,无独立可运行进程 | +| 对外协议 | gRPC(`cms.Post`/`cms.Pages`/`cms.Category`/`cms.Tags`/`cms.Site`)+ grpc-gateway HTTP(`/cms./`);站点 `Site` 系列为**声明但未实现**的占位 | +| 结论摘要 | 文章/页面查询、评论、计数增改等核心逻辑已实现,但存在**评论发布 panic(未命中 mall_staff 即崩溃)**、**赞/踩无去重且可刷成负数**、**删除父评论时文章评论计数不一致**、**Search 无分页上限(可拖全表)**、**文章/评论增删改不校验归属(越权)**、**Site 全系列占位空实现**等真实缺陷。 | + +## 1. 服务定位与职责 + +站点内容管理(CMS)微服务,提供文章、单页、分类、标签的 CRUD 与查询,以及评论、点赞/踩等互动能力。数据模型以 `cms_post`/`cms_pages`/`cms_category`/`cms_tags`/`cms_comment` 为核心,通过 `cms_relate_category`/`cms_relate_tags`/`cms_accessory` 维护关联。它是内容存储与查询服务,不负责全文检索、富媒体转码或内容审核(这些不在本模块代码内)。`Site`(站点配置)在 proto 与 server 中均已声明并注册,但逻辑层全部为占位空实现。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/cli/main.go` | 占位入口,仅打印 `Hello World`,不启动任何服务 | +| `service/expose.go` | 聚合宿主注入依赖 + 注册 5 个 gRPC 服务与 5 个 Gateway Handler | +| `service/dependencies.go` | `Dependencies`(Redis/Etcd/DB/Cache)覆盖 `internal/impl` 全局对象 | +| `internal/config/config.go` | 配置结构(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`Apm`/`Etcd`)与校验 | +| `internal/impl/impl.go` | 初始化 Redis、DB、Etcd、内存缓存为全局对象 | +| `internal/server/new.go` | gRPC Server 注册入口(`Register*Server`) | +| `internal/server/{post,pages,category,tags,site}_server.go` | gRPC 服务实现,转发到对应 logic 包 | +| `internal/logic/post/*` | 文章 CRUD、查询、搜索、评论、赞/踩(20 文件) | +| `internal/logic/category/*` `internal/logic/tags/*` `internal/logic/pages/*` `internal/logic/site/*` | 分类、标签、页面、站点逻辑 | +| `internal/models/*.go` | 9 张表模型 + `query.go`(增删改查与计数) | +| `internal/utils/string.go` | `FormatKey`(小写、去空格) | +| `proto/*.proto` `pb/*.go` | 接口契约与生成代码(仅看签名) | +| `test/**/*.http` | HTTP 调用示例(非自动化测试) | + +依赖注入:`service.Dependencies` 由聚合入口 `pkgs/all` 传入共享连接(Redis/Etcd/DB/Memory),非 nil 时覆盖 `internal/impl` 全局对象;`config.NotNil` 仅校验 `Service`/`Cache`。 + +## 3. 接口清单 + +> 鉴权列:除 `CommentList` 外,所有 handler 均调用 `service.ParseMetaCtx(ctx, nil)` 解析登录态(无 token 即报错),即**已认证、未授权**(不校验资源归属)。`Site` 系列虽调用 `ParseMetaCtx`,但后续无实际操作。 + +| 服务 | 方法 | proto / 路径 | 功能 | 鉴权 | 实现位置 | 状态 | +| --- | --- | --- | --- | --- | --- | --- | +| Post | Fetch | `/cms.Post/Fetch` | 文章分页列表(按分类/关键词/类型) | 有 | `logic/post/fetch.go` | 已实现 | +| Post | GetByIdentity | `/cms.Post/GetByIdentity` | 按 identity 获取详情(含点击量+1) | 有 | `logic/post/get_by_identity.go` | 已实现 | +| Post | GetByKey | `/cms.Post/GetByKey` | 按 key(hash) 获取详情 | 有 | `logic/post/get_by_key.go` | 已实现 | +| Post | Search | `/cms.Post/Search` | 标题/正文关键词搜索 | 有 | `logic/post/search.go` | 已实现(分页缺陷) | +| Post | Create | `/cms.Post/Create` | 创建文章+关联 | 有 | `logic/post/create.go` | 已实现 | +| Post | Modify | `/cms.Post/Modify` | 修改文章 | 有 | `logic/post/modify.go` | 已实现(无归属校验) | +| Post | Delete | `/cms.Post/Delete` | 删除文章+关联 | 有 | `logic/post/delete.go` | 已实现(无归属校验) | +| Post | IncrPostLike / DescPostLike | `/cms.Post/IncrPostLike` 等 | 文章点赞/取消 | 有 | `logic/post/incr_post_like.go` 等 4 文件 | 已实现(无去重) | +| Post | CommentList | `/cms.Post/CommentList` | 评论分页(树形) | **无(已注释)** | `logic/post/comment_list.go:24-27` | 已实现 | +| Post | AddComment | `/cms.Post/AddComment` | 发布评论(更新计数) | 有 | `logic/post/add_comment.go` | 已实现(panic 风险) | +| Post | ModifyComment | `/cms.Post/ModifyComment` | 修改评论内容 | 有 | `logic/post/modify_comment.go` | 已实现(无归属校验) | +| Post | DeleteComment | `/cms.Post/DeleteComment` | 删除评论(含子级) | 有 | `logic/post/delete_comment.go` | 已实现(无归属校验) | +| Post | IncrCommentLike/DescCommentLike/IncrCommentUnlike/DescCommentUnlike | `/cms.Post/...` | 评论赞/踩 | 有 | `logic/post/incr_comment_like.go` 等 4 文件 | 已实现(无去重) | +| Category | Fetch/Create/Modify/Delete | `/cms.Category/...` | 分类 CRUD + 树形列表 | 有 | `logic/category/*` | 已实现 | +| Pages | Fetch/GetByIdentity/GetByKey/Create/Modify/Delete | `/cms.Pages/...` | 页面 CRUD + 查询 | 有 | `logic/pages/*` | 已实现(GetByIdentity 点击量不增) | +| Tags | Fetch/Create/Modify/Delete | `/cms.Tags/...` | 标签 CRUD + 列表 | 有 | `logic/tags/*` | 已实现 | +| **Site** | **Fetch** | `/cms.Site/Fetch` | 站点列表 | 有 | `logic/site/fetch.go` | **占位:返回空** `site/fetch.go:18,20` | +| **Site** | **Get** | `/cms.Site/Get` | 站点详情 | 有 | `logic/site/get.go` | **占位:返回 nil** `site/get.go:24` | +| **Site** | **Create** | `/cms.Site/Create` | 新增站点 | 有 | `logic/site/create.go` | **占位:成功但不落库** `site/create.go:20-22` | +| **Site** | **Modify** | `/cms.Site/Modify` | 修改站点 | 有 | `logic/site/modify.go` | **占位:成功但不落库** `site/modify.go:21-23` | +| **Site** | **Delete** | `/cms.Site/Delete` | 删除站点 | 有 | `logic/site/delete.go` | **占位:成功但不删** `site/delete.go:27` | + +> 对照 proto(`post.proto`/`pages.proto`/`category.proto`/`tags.proto`/`site.proto`)与 `internal/server/*_server.go` + `service/expose.go`,5 个服务的所有 rpc 均已在 gRPC 与 Gateway 注册;不存在"声明但未注册"的方法。但 **`Site` 全部 5 个方法为"已注册、无逻辑"的占位实现**(代码内含 `// TODO: add your logic code & delete this line.`),且 `cms_site` 表已通过 `init()` 纳入自动迁移,却无任何写入路径——属于"声明但未实现"。 + +## 4. 数据模型与表 + +> 模型 gorm tag 存在反直觉之处:`cms_post.types` 为 `int32` 但 `gorm:"type:varchar(255)"`(`cms_post.go:26`);关联表 `CmsRelateCategory.Category`、`CmsRelateTags.Tags` 的 `foreignKey` 写反(`cms_relate_category.go:12`、`cms_relate_tags.go:13`),导致 `Preload("Categories.Category")`/`Preload("Tags.Tags")` 关联数据取不到(见 6.2)。 + +### 表 `cms_post`(经 GORM 自动迁移创建) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `identity` | varchar(36) | index | 业务唯一标识(UUID) | +| `owner_id`/`owner_identity` | - | - | 所有者(来自 `Std_Owner`) | +| `site_identity` | varchar(36) | index | 站点标识 | +| `types` | int32 | `gorm:"type:varchar(255)"` | 文章类型(tag 与字段类型矛盾) | +| `title` | varchar(255) | - | 标题 | +| `hash` | varchar(255) | uniqueIndex | URL 唯一键 | +| `content`/`description`/`cover_path`/`target_url`/`source_url` | text/varchar | - | 内容/描述/封面/外链 | +| `author`/`author_identity` | varchar | - | 作者 | +| `hits`/`like_hits`/`unlike_hits`/`comment_hits` | int64 | default 0 | 统计计数 | +| `has_accessory` | bool | - | 是否有附件 | +| `categories`/`tags`/`accessories` | 关联 | - | 关联 `CmsRelateCategory`/`CmsRelateTags`/`CmsAccessory` | + +### 表 `cms_comment` + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK(`gorm.Model`) | - | +| `identity` | - | (来自 `Std_Identity`) | 业务标识 | +| `post_identity`/`owner_identity`/`owner_name`/`role` | varchar | index | 归属/作者 | +| `parent_id` | uint | index | 父评论 id(0 为顶级) | +| `reply_identity` | varchar(36) | index | 被回复评论 | +| `Cms` | text | - | 评论内容 | +| `hits`/`like_hits`/`unlike_hits`/`comment_hits` | int64 | default 0 | 计数 | + +### 表 `cms_category` / `cms_tags` / `cms_pages` / `cms_site` + +| 表 | 关键字段 | 说明 | +| --- | --- | --- | +| `cms_category` | `category_key` uniqueIndex、`title` not null、`parent_id` index、`Children` 自关联 | 层级分类 | +| `cms_tags` | `title` not null、`cover_path`、`intro` | 标签(无 uniqueKey 约束) | +| `cms_pages` | `key` uniqueIndex、`title`/`content` 默认空串、`hits` 计数、`Tags`/`Accessories` 关联 | 单页 | +| `cms_site` | `title`/`description`/`icon_path`/`keywords`/`domain`/`seo`/`theme`/`configs` 均 `not null` | 站点配置(**无任何写入路径**) | + +### 关联表 `cms_relate_category` / `cms_relate_tags` / `cms_accessory` + +| 表 | 关键字段 | 说明 | +| --- | --- | --- | +| `cms_relate_category` | `identity` uniqueIndex、`post_identity` index、`category_identity` index、`Category` 关联 | 文章-分类(foreignKey 写反) | +| `cms_relate_tags` | `post_identity`/`pages_identity`/`tags_identity` index、`Tags` 关联 | 文章/页面-标签(foreignKey 写反) | +| `cms_accessory` | `post_id`/`post_identity`/`pages_id`/`pages_identity`、`file_path` not null | 附件 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /cms.Post/IncrPostLike"] --> B["ParseMetaCtx 校验登录态"] + B --> C["校验 op_identity / post_identity 非空"] + C --> D["IncrOrDescPostField: UPDATE cms_post SET like_hits = like_hits + 1 WHERE identity=?"] + D --> E["返回 OK"] +``` + +```mermaid +flowchart TD + A["POST /cms.Post/AddComment"] --> B["ParseMetaCtx 取 auth"] + B --> C["impl.DBService.Table('mall_staff').Take(&authName, 'identity=?', auth.Identity)"] + C --> D{"命中 mall_staff 行?"} + D -- "否 / name 为 NULL" --> P["authName['name'].(string) 触发 panic → 500"] + D -- "是" --> E["事务: 写 cms_comment + post.comment_hits +1 + 父评论 comment_hits +1"] + E --> F["返回 comment identity"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/post/delete.go:29`、`modify.go:15-22`、`delete_comment.go:24`、`modify_comment.go:33` | **越权删除/修改**:文章 `Delete`/`Modify`、评论 `DeleteComment`/`ModifyComment` 只校验"是否登录",不校验调用者是否为资源 owner/作者。`DeletePost(in.Identity, "")` 第二参 `authorIdentity` 恒为空且函数体未使用(`query.go:207`),`ModifyComment` 直接 `Where("identity=?", in.Identity).Updates(data)`。任意登录用户可删改他人文章/评论。 | +| **高** | `internal/logic/post/search.go:31-34` | **无分页上限 / 可拖全表**:`if pageSize < 50 { pageSize = 50 }` 把小于 50 的请求强制放大到 50,且**完全没有 `page_size > 50` 的上限**。客户端传 `page_size=1000000` 即可一次拉走整张 `cms_post`(含 `content` 大字段),造成全表返回与读取放大(DoS)。proto 注释明确"最多 50",实现与之相反。 | +| 中 | `internal/logic/post/comment_list.go:24-27` | `CommentList` 的 `ParseMetaCtx` 被整段注释,评论列表接口**对匿名开放**,可遍历任意文章全部评论与作者信息。 | +| 中 | `internal/logic/post/incr_post_like.go:38`、`incr_comment_like.go:29` 等 4+4 处 | **赞/踩无去重、可刷量**:`op_identity` 仅做"非空"校验,从不记录"谁点过",同一用户可重复 `IncrPostLike` 无限累加;`Desc*` 路径可把计数刷成负数(无 floor 0 约束)。模型与计数仅用 `int64` 无 `CHECK`。 | +| 低 | `internal/logic/tags/create.go:28-30` | 标签重复创建走 `First` 命中即返回 `errcode.ErrDB`(不是"已存在"语义),且 dedup 依赖"先查后插"非原子,存在 TOCTOU 竞态。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/post/add_comment.go:35-40` | **取 mall_staff 名字 panic**:`Take(&authName, "identity=?", auth.Identity)` 若当前用户不在 `mall_staff` 表(如普通会员/客户),`Take` 返回 `ErrRecordNotFound`,紧随其后的 `authName["name"].(string)` 对缺失 key 取到 `nil` 触发 **panic**(服务端 500,且 gRPC 默认无 recover 会中断该请求链路)。即便命中,`name` 列为 NULL 同样 panic。 | +| **高** | `internal/models/query.go:303-334`(`DeleteComment`)+ `277-297`(`AddComment`) | **评论计数不一致**:`AddComment` 中**每条**评论(含子评论)都会让 `post.comment_hits +1`;而 `DeleteComment` 删除父评论时只做 `post.comment_hits -1`,再级联删子评论(`parent_id = comment.ID`)。当父评论下有 N 个子评论被一并删除时,文章评论数只减 1,实际应减 N+1 → 计数长期偏离真实值。 | +| **高** | `internal/logic/pages/get_by_identity.go:21,76` | **页面点击量不增**:`GetPages("Cms_pages.identity", in.Identity)` 查询用 `identity`,但计数更新写成 `Where("key = ?", val)`(`val` 是 identity 值而非 `key`),匹配不到行,`hits` 永远不 +1。仅 `GetByKey` 路径(`GetPages("key", in.Key)`)计数正确。 | +| 中 | `internal/models/cms_relate_category.go:12`、`cms_relate_tags.go:13` | **Preload 外键写反**:`Category CmsCategory gorm:"foreignKey:Identity;references:CategoryIdentity"` 实际生成 `cms_category.category_identity = cms_relate_category.identity`(应为 `cms_category.identity = cms_relate_category.category_identity`);`Tags` 同理。导致 `Preload("Categories.Category")`/`Preload("Tags.Tags")`(`query.go:136,242`、`post/fetch.go:52-72`、`get_by_identity.go:68-79`、`pages/fetch.go:52-62`)取到的分类/标签对象为空或错位,列表与详情的分类/标签数据失真。 | +| 中 | `internal/models/query.go:162-169` | **修改文章时附件路径被覆盖**:`ModifyPost` 的 `accessory` 参数实际接收的是 `AccessoryIdentityArray`(`post/modify.go:43`),却把每个字符串直接赋给 `CmsAccessory.FilePath`(`FilePath: v`)。即修改文章后,附件记录的文件路径会被写成"附件 identity 字符串",原 `file_path` 丢失。 | +| 中 | `internal/logic/pages/modify.go:33` vs `create.go:46` | **页面 key 不一致**:`Create` 用 `utils.FormatKey(in.Key)` 格式化,`Modify` 直接 `Key: in.Key` 不做格式化,二者对同名 key 可能写入不同值,破坏 uniqueIndex 一致性。 | +| 中 | `internal/logic/tags/create.go:19,28-33` | **死代码导致语义错误**:`var cnt int64 = 0` 声明后从未赋值,下方 `if cnt > 0 { return ... ErrAlreadyExists }` 永远为假;重复标签判定实际依赖 `First` 的 `err == nil`,但返回的是 `errcode.ErrDB` 而非"已存在"。 | +| 低 | `internal/models/cms_post.go:26` | `Types int32` 但 gorm `type:varchar(255)`;`PostList` 用 `cms_post.types = ?`(int)比较 varchar 列(`query.go:130`),类型语义错位,依赖数据库隐式转换。 | +| 低 | `internal/logic/post/modify.go:38`、`post/delete.go:30` | 错误透传不一致:`ModifyPost`/`DeletePost` 失败统一返回 `errcode.ErrDB`,但 `modify_comment.go:34` 把原始 `err` 直接返回,错误体格式不统一。 | + +### 6.3 未完成实现 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/site/{create,modify,delete,fetch,get}.go` | **Site 全系列占位**:`Fetch`/`Get` 仅 `TODO` 后返回空(`site/fetch.go:18,20`、`site/get.go:24`);`Create`/`Modify`/`Delete` 解析登录态后直接 `return StatusReply{OK}` 不落库/不删除(`site/create.go:20-22`、`site/modify.go:21-23`、`site/delete.go:27`)。proto 与 server 已对外暴露 `cms.Site`,调用方会拿到"成功但无数据/数据不变"的假象。`cms_site` 表已迁移却无任何写入路径。 | +| 中 | `internal/models/query.go:346` | 评论点赞明确留 `// TODO: 记录点赞对象避免重复点赞`,去重能力长期缺失(与 6.1 赞踩刷量同一根因)。 | +| 低 | `internal/logic/post/delete.go:29`、各 `Modify`/`Delete` 的 `// Todo:` 注释 | 归属校验、作者校验多处以 `// Todo:` 留空,未落地。 | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/models/query.go:119`、`category/fetch.go:20`、`pages/get_by_identity.go:71`、`post/comment_list.go:44` | 多处 `.Debug()` 打开 SQL 全量日志,生产环境会刷屏并可能泄露数据内容。 | +| 低 | `internal/logic/post/comment_list.go:30`、`category/fetch.go:21`、`tags/fetch.go:21` | 调试用 `fmt.Println("err = ", ...)`/`fmt.Println("in = ", in)` 残留,泄漏请求参数到标准输出。 | +| 低 | `internal/logic/post/ext.go`、`pages/ext.go` | `fmtKey` 函数定义但从未被调用(create/modify 用的是 `utils.FormatKey`),属死代码。 | +| 低 | `internal/logic/tags/delete.go:24` | 删除标签时不清理 `cms_relate_tags`,遗留孤儿关联(文章/页面仍指向已删标签)。 | +| 低 | `internal/logic/category/delete.go:23` → `query.go:370-389` | `DeleteCategory` 仅禁止"有子分类"的删除,未检查分类是否仍被 `cms_relate_category` 引用,删除被引用的分类会留下孤儿关联。 | +| 低 | 全模块 | **无任何 `*_test.go` 单元测试**;36 个 `.http` 仅为手工调用示例。上述 panic、计数不一致、分页越界等问题若有单测可早期暴露。 | +| 低 | `internal/config/config.go:49` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`;但 `impl.go:43` 的 `with.Databases` 多数实现在配置缺失时返回 nil,首个查询即空指针(`pkgs/all` 已注入共享 DB 故当前不触发,独立运行时风险高)。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| C1 | 高 | 文章/评论 增/删/改 不校验归属(越权) | 数据完整性、内容安全 | +| C2 | 高 | `AddComment` 未命中 `mall_staff` 时 `authName["name"].(string)` panic | 接口可用性(500/链路中断) | +| C3 | 高 | `Search` 无 `page_size` 上限(可拖全表) | 性能/DoS、数据泄露 | +| C4 | 高 | 删除父评论时 `post.comment_hits` 计数不一致 | 统计数据失真 | +| C5 | 高 | `Site` 全系列占位空实现(声明未实现) | 功能缺失、调用方误判成功 | +| C6 | 高 | 赞/踩无去重且可刷成负数 | 互动数据可信性、刷量 | +| C7 | 中 | `GetByIdentity`(页面) 点击量不增 | 统计失真 | +| C8 | 中 | `CommentList` 鉴权被注释(匿名可读全部评论) | 信息泄露 | +| C9 | 中 | 关联表 Preload 外键写反,分类/标签数据取不到 | 列表/详情数据错误 | +| C10 | 中 | `ModifyPost` 附件路径被写成 identity 字符串 | 附件数据损坏 | +| C11 | 中 | 页面 `Modify` 的 key 未格式化,与 `Create` 不一致 | 唯一键语义混乱 | +| C12 | 中 | `Tags.Create` 死代码 `cnt` + dedup 返回 ErrDB | 重复标签处理语义错误 | +| C13 | 低 | 多处 `.Debug()` + `fmt.Println` 残留 | 日志泄露、性能 | +| C14 | 低 | 标签/分类删除遗留孤儿关联 | 数据一致性 | +| C15 | 低 | 无单测、死代码 `fmtKey` | 可维护性 | +| C16 | 低 | `config.NotNil` 未校验 `Databases` | 独立运行崩溃风险 | + +## 8. 修复建议(务实项) + +1. **越权(C1)**:在 `post.Delete`/`post.Modify`/`post.DeleteComment`/`post.ModifyComment` 中,先按 identity 取资源,校验 `owner_identity`/`author_identity` 与 `ParseMetaCtx` 返回的登录身份一致(或调用方具备管理角色)再执行;`DeletePost` 应接收并比较 `authorIdentity`。不要新增抽象层,仅在现有函数开头加一段归属判定。 +2. **评论 panic(C2)**:`add_comment.go:35-40` 不要用 `map[string]any` + 类型断言取 `name`。改为先用 `Select("name")` 查 `mall_staff`,对 `ErrRecordNotFound`/NULL 给出兜底(如空串或调用方已下发的昵称),删除 `.(string)` 断言。 +3. **分页上限(C3)**:`search.go:31` 改为"若 `page_size<=0` 默认 10,若 `page_size>50` 截断为 50",与 `Fetch` 的既有逻辑(`post/fetch.go:18-23`)对齐;同时给 `pages.Fetch`/`tags.Fetch` 等补上分页(当前 tags 列表无分页)。 +4. **计数一致性(C4)**:`DeleteComment` 级联删除子评论时,按"被删评论及子评论总条数"一次性 `post.comment_hits - N`;或在删除前 `Count` 子评论数量后再减,避免只减 1。 +5. **页面点击量(C7)**:`get_pages.go:76` 的 `Where("key = ?", val)` 改为 `Where("identity = ?", val)`(与查询 key 一致);或统一用 `in.Identity` 作为更新条件,与 `GetPost`(`query.go:248`)保持一致写法。 +6. **Preload 外键(C9)**:把 `CmsRelateCategory.Category`、`CmsRelateTags.Tags` 的 gorm tag 改为 `foreignKey:CategoryIdentity;references:Identity` / `foreignKey:TagsIdentity;references:Identity`,使 `Preload` 正确取到分类/标签对象。 +7. **赞/踩去重(C6,对应 TODO `query.go:346`)**:在 `IncrOrDescPostField`/`IncrOrDescCommentField` 前,先查"该 op_identity 是否已对目标点过赞"的记录(新增轻量关系表或唯一索引),已点过则忽略/抵消;并对 `desc` 路径加 `WHERE col > 0` 约束防止负数。 +8. **Site 占位(C5)**:要么补齐 `site/*` 五个方法的真实读写逻辑(建/改/删走 `models.CmsSite`、列表/详情查询),要么在 proto 与 server 中移除该服务以免对外暴露假成功;当前不应让其返回"OK 但无副作用"。 +9. **清理(C13)**:删除 `comment_list.go:30`、`category/fetch.go:21`、`tags/fetch.go:21` 的 `fmt.Println`,以及 `query.go`/`get_pages.go`/`comment_list.go`/`category/fetch.go` 的 `.Debug()`;删除未使用的 `fmtKey`(`post/ext.go`、`pages/ext.go`)。 +10. **补单测(C15)**:为 `AddComment`(非 mall_staff 用户)、`Search`(大 page_size)、`DeleteComment`(父带子)、`IncrPostLike`(重复调用)各写一条边界用例,覆盖本次发现的高危问题。 + +> 本报告只列出与现有实现直接相关的修复项,不引入统一框架、DTO/VO 层、DDD/CQRS 等额外抽象。所有结论均附 `文件:行号` 证据;未发现证据处已标注【信息不足】或明确为推断。 diff --git a/docs/feed.md b/docs/feed.md new file mode 100644 index 0000000..466a862 --- /dev/null +++ b/docs/feed.md @@ -0,0 +1,292 @@ +# feed(社交动态与内容互动)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/social/feed`(module 路径 `bsm/full/module/social/feed`) | +| 服务域 | 社交与内容 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 32 个文件 / 1160 行;`pb/` 14 个文件 / 6527 行(含手写兼容文件 `pb/blocks_compat.go`);`proto/` 5 个文件 / 433 行;`etc/` 6 个 yaml + 1 个 supervisor conf;`*_test.go` 0 个 | +| 入口 | 仅独立入口 `cmd/main/main.go`(gRPC + gateway 单进程,端口 12212);`service/` 为**空目录**,未被 `pkgs/all`、`pkgs/ecmall` 引用;`cmd/cli/main.go` 只打印配置 | +| 对外协议 | gRPC(4 个 service / 17 个 RPC);gateway 路由定义为 `POST /feed.{Service}/{Method}`,但模块内**从未注册任何 gateway handler** | +| 结论摘要 | 代码量小且 `go vet` 通过,但核心读写链路大面积失效:分页与过滤条件因 GORM 链式未回写而全部失效、列表恒不返回标签/附件、标签列表恒为空、删除评论为空实现;删除/修改动态与互动计数接口均无归属校验;9 个 RPC 为 TODO 占位;`etc/feed_*.yaml` 字段名与 SDK 配置结构不匹配,服务启动即退出。 | + +## 1. 服务定位与职责 + +承载用户发布动态(`feed_post`)、动态附件与标签关联、评论(`feed_comment`)、标签字典(`feed_tags`)与四类时间线(推荐/好友/关注/热门)的社交内容服务。 + +它是社交内容流的**读写与互动计数**服务。时间线排序与分发、好友/关注关系、推荐算法均不在本模块内实现(四类 Timeline 方法全部为占位);模块也不负责用户资料、关系链、消息推送或媒体存储。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("Feed")` → `impl.NewImpl()` → `server.New(addr)` → `service.New` → `srv.Use(models.InitData)` → `srv.Start()` | +| `cmd/cli/main.go` | 仅 `config.New("feed")` + `fmt.Println(config.Spec.Databases)` 的调试入口 | +| `internal/config/config.go` | `SrvConfig`(Base、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChatConf、Token、Kyc)与校验 | +| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 | +| `internal/server/new.go` | 创建 grpc.Server、注册 4 个 service(`protoc-gen-slc` 生成) | +| `internal/server/{post,tag,timeline,setting}_server.go` | 17 个 RPC 的 1:1 转发(生成代码) | +| `internal/logic/post/{fetch,create,change,remove,comment_list,add_comment,delete_comment,action}.go` | 动态 CRUD、评论、互动计数 | +| `internal/logic/tag/{list,create,post_list}.go` | 标签列表/创建/话题下动态 | +| `internal/logic/timeline/{recommend,friend,follow,hot}.go` | 四类时间线(全部占位) | +| `internal/logic/setting/{info,rights}.go` | 设置(全部占位) | +| `internal/models/{feed_post,feed_comment,feed_tags,feed_relate_tags,feed_relate_attachs,query}.go` | 5 张表的模型 + 迁移注册 + `query.go` 中全部数据访问函数 | +| `service/` | **空目录**(无 `expose.go`,因此无法被聚合宿主接入) | +| `pb/blocks_compat.go` | 手写类型别名文件(`PostListReply = FeedPostListReply` 等),非生成代码 | +| `proto/*.proto` | 4 个 service 契约;公共消息放在 `proto/const.proto`(`package blocks`,与 service `package feed` 不同) | +| `etc/{feed_dev,feed_prod,feed_test,passport_dev,passport_prod,passport_test}.yaml` | 配置;`passport_*.yaml` 的内容才是与本模块结构匹配的那一份(见 6.4) | + +依赖注入:模块内无 `service.Expose`,`impl.NewImpl()` 是唯一初始化路径;`pkgs/all` 与 `pkgs/ecmall` 的 `go.mod` 均未引用本模块,全仓库对 `module/social/feed` 的引用只有 `scripts/api-docgen`(仅 import `pb`)与根 `README.md`。 + +## 3. 接口清单 + +HTTP 路径由生成代码固定为 `/feed.{服务}/{方法}`(`pb/post.pb.gw.go:600-607`、`pb/tag.pb.gw.go:280-282`、`pb/timeline.pb.gw.go:344-347`、`pb/setting.pb.gw.go:216-217`)。注意:这些 handler 在模块内**从未被注册到任何 `ServeMux`**(见 6.4)。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/feed.Post/Fetch` | 推文列表(支持 `params.key` 按标签过滤) | JWT | `logic/post/fetch.go:14` | +| POST | `/feed.Post/Create` | 新建推文(含附件、标签) | JWT | `logic/post/create.go:17` | +| POST | `/feed.Post/Change` | 修改推文 | JWT(无归属校验) | `logic/post/change.go:17` | +| POST | `/feed.Post/Remove` | 删除推文 | JWT(无归属校验) | `logic/post/remove.go:16` | +| POST | `/feed.Post/CommentList` | 评论列表 | JWT | **占位** `logic/post/comment_list.go:11` | +| POST | `/feed.Post/AddComment` | 新建评论 | JWT | `logic/post/add_comment.go:18` | +| POST | `/feed.Post/DeleteComment` | 删除评论 | JWT(无归属校验) | `logic/post/delete_comment.go:16` + **空实现** `models/query.go:122-125` | +| POST | `/feed.Post/Action` | 点赞/点踩计数 | JWT(不校验身份来源) | `logic/post/action.go:17` | +| POST | `/feed.Tag/List` | 标签列表 | JWT | `logic/tag/list.go:13`(返回值被丢弃,恒为空) | +| POST | `/feed.Tag/Create` | 新建标签 | JWT | `logic/tag/create.go:15` | +| POST | `/feed.Tag/PostList` | 话题相关动态 | JWT | **占位** `logic/tag/post_list.go:11` | +| POST | `/feed.Timeline/Recommend` | 推荐时间线 | JWT | **占位** `logic/timeline/recommend.go:11` | +| POST | `/feed.Timeline/Friend` | 好友动态 | JWT | **占位** `logic/timeline/friend.go:11` | +| POST | `/feed.Timeline/Follow` | 关注动态 | JWT | **占位** `logic/timeline/follow.go:11` | +| POST | `/feed.Timeline/Hot` | 热门动态 | JWT | **占位** `logic/timeline/hot.go:11` | +| POST | `/feed.Setting/Info` | 修改个人信息 | JWT | **占位** `logic/setting/info.go:11` | +| POST | `/feed.Setting/Rights` | 修改权限信息 | JWT | **占位** `logic/setting/rights.go:11` | + +> 鉴权方式:全部 17 个 logic 入口都调用 `service.ParseMetaCtx(ctx, nil)`(`git.apinb.com/bsm-sdk/core/service/meta.go:19-49`),要求有效 JWT;所有 `opts` 均为 `nil`,即**不校验角色、不校验私有 IP**。多个入口用 `_, err = ...` 丢弃 claims(如 `logic/post/fetch.go:15`),因此拿不到调用者身份。 + +### 声明但未实现 / 空转的接口 + +| 类别 | 接口 | 证据 | +| --- | --- | --- | +| TODO 占位(4 个时间线) | `Timeline.Recommend` / `Friend` / `Follow` / `Hot` | `internal/logic/timeline/{recommend,friend,follow,hot}.go:26`(`// TODO: add your logic code`) | +| TODO 占位 | `Tag.PostList` | `internal/logic/tag/post_list.go:26` | +| TODO 占位 | `Post.CommentList` | `internal/logic/post/comment_list.go:26` | +| TODO 占位(2 个) | `Setting.Info` / `Setting.Rights` | `internal/logic/setting/{info,rights}.go:18-20` | +| 有路由有转发但底层空实现 | `Post.DeleteComment` | `internal/models/query.go:122-125` 函数体只有 `return`,永远返回 nil | +| 有实现但返回值被丢弃 | `Tag.List` | `internal/logic/tag/list.go:32` 返回新建的空 `TagListReply`,忽略了上面组装好的 `res` | + +> 9 个 TODO 占位函数统一写成 `func(...) (reply *XxxReply, err error) { ...; return }`,即返回 `(nil, nil)`;gRPC 侧回空消息,gateway 侧对 nil 响应序列化会失败(表现为 500/空响应)。 + +## 4. 数据模型与表 + +模型统一在 `init()` 中通过 `database.AppendMigrate` 注册(`internal/models/feed_post.go:20` 等为 `TableName` 方法;迁移注册在各文件内)。公共字段来自 SDK:`types.Std_IICUDS` = `id`(PK) / `identity varchar(36) uniqueIndex` / `created_at` / `updated_at` / `deleted_at`(index) / `status int8 index`;`types.Std_IDIdentity` = `id`(PK) / `identity`(uniqueIndex);`types.Std_Passport` = `passport_id`(index) / `passport_identity varchar(36)`(index)。 + +### 表 `feed_post`(`internal/models/feed_post.go:8-22`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `identity` | varchar(36) | uniqueIndex | 动态唯一码 | +| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 | +| `status` | int8 | index, 默认 0 | 状态(-1 禁止 / 1 正常),**查询中未使用** | +| `passport_id` | uint | index | 发布者 ID | +| `passport_identity` | varchar(36) | index | 发布者唯一标识 | +| `content` | text | 默认 `''` | 正文 | +| `is_open` | bool | 默认 true | true 公开 / false 不公开,**查询中未过滤** | +| `cnt_like` | int64 | 默认 0 | 点赞数 | +| `cnt_unlike` | int64 | 默认 0 | 点踩数 | +| `cnt_comment` | int64 | 默认 0 | 评论数 | +| `Attachs` | `[]FeedRelateAttach` | `gorm:"-"` | **非持久化**,不会自动迁移,也永远不会被 GORM 加载 | +| `Tags` | `[]FeedRelateTags` | `gorm:"-"` | 同上 | + +### 表 `feed_comment`(`internal/models/feed_comment.go:6-20`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 主键与唯一码 | +| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 | +| `status` | int8 | index | 状态,未使用 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 评论作者;**新增评论时从不写入**(见 6.2) | +| `post_identity` | varchar(36) | index | 所属动态唯一码 | +| `parent_identity` | varchar(36) | index | 上级评论唯一码 | +| `content` | text | 默认 `''` | 评论正文 | +| `SubComment` | `[]FeedComment` | `foreignkey:parent_identity` | 子评论关联,无任何查询使用 | +| `cnt_like`/`cnt_unlike`/`cnt_comment` | int64 | 默认 0 | 互动计数 | + +### 表 `feed_tags`(`internal/models/feed_tags.go:8-16`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 | +| `key` | varchar(36) | not null,**无唯一索引** | 标签键;由 `tag/create.go:26` 写入随机 UUID | +| `content` | varchar(255) | not null | 标签名称 | + +> 该表**没有 `identity` 列**,但 `models/query.go:81` 与 `:89` 使用 `Where("identity = ?", ...)`(见 6.2 与 6.3)。 + +### 表 `feed_relate_tags`(`internal/models/feed_relate_tags.go:6-15`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 主键与唯一码 | +| `post_identity` | varchar(36) | index | 动态唯一码 | +| `key` | varchar(36) | index | 标签键 | +| `Content` | string | `gorm:"-"` | **非持久化**,`create.go:46` 写入的标签名会被静默丢弃 | + +### 表 `feed_relate_attach`(`internal/models/feed_relate_attachs.go:6-16`,表名单数) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK(内嵌 `Std_IDIdentity`) | 自增主键 | +| `identity` | varchar(36) | uniqueIndex | 唯一码(内嵌字段与显式字段同名,见 6.4) | +| `post_identity` | varchar(36) | index | 动态唯一码 | +| `attach_type` | varchar(36) | index | 附件类型 | +| `url` | varchar(255) | index | 附件地址 | + +## 5. 核心流程 + +### 5.1 发布动态 → 落库 → 列表读取 + +```mermaid +flowchart TD + A["POST /feed.Post/Create
service.ParseMetaCtx JWT"] --> B["校验 content 非空"] + B --> C["models.CreatePost 事务"] + C --> D["tx.Create feed_post"] + D --> E["tx.Create feed_relate_attach"] + E --> F["tx.Create feed_relate_tags
Content 字段 gorm 忽略 标签名丢失"] + F --> G["返回 identity"] + H["POST /feed.Post/Fetch
service.ParseMetaCtx 丢弃 claims"] --> I["page_no 小于等于 0 置 1
page_size 小于等于 0 置 10"] + I --> J["models.PostList
Order created_at desc"] + J --> K["tx.Limit(page).Offset(...) 未回写
分页整体失效"] + K --> L["Find 到 FeedPost 切片
Tags 与 Attachs 为 gorm 忽略字段"] + L --> M["AssembleList 中两个循环永不执行
标签与附件恒为空"] + M --> N["fmt.Print 把整页数据打到 stdout"] + N --> O["无 is_open 无 status 过滤
含私密与禁用用户的动态"] +``` + +### 5.2 评论与互动计数 + +```mermaid +flowchart TD + A["POST /feed.Post/AddComment"] --> B["models.AddComment 事务"] + B --> C["Create feed_comment
passport_identity 未写入 作者为空"] + C --> D["Table feed_post 后 UpdateColumn
cnt_comment 加 1"] + D --> E{"ParentIdentity 非空"} + E -->|是| F["未指定 Model 的 UpdateColumn
目标表只能靠 GORM 内部状态推断"] + G["POST /feed.Post/Action"] --> H["models.LikeAction"] + H --> I{"action_type"} + I -->|post| J["Model FeedPost"] + I -->|comment| K["Model FeedComment"] + I -->|其他| L["未设置 Model 目标表不确定"] + J --> M{"action_op"} + K --> M + M -->|ilike| N["column 为 cnt_like 计数加 1"] + M -->|unlike| O["条件误写成 action_type 等于 unlike
column 保持空串"] + O --> P["UpdateColumn 空列名 SQL 错误"] + Q["POST /feed.Post/DeleteComment"] --> R["models.DeleteComment 空函数
永远返回 nil"] + R --> S["接口返回成功但无任何删除"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/post/remove.go:17-29` + `internal/models/query.go:34-37` | **越权删除动态**。`Remove` 拿到 JWT 后直接用 `in.Identity` 调 `DeletePost`,模型层是 `Where("identity = ?", identity).Delete(&FeedPost{})`,**没有任何 `passport_identity` 归属条件** → 任意登录用户可删除他人动态(软删除)。 | +| **高** | `internal/logic/post/change.go:17-40` + `internal/models/query.go:30-33` | **越权修改动态**。`Change` 同样只校验 `in.Identity != ""`,`ChargePost` 为 `Where("identity = ?", data.Identity).Updates(&data)` → 可篡改任意用户的动态正文与公开状态。 | +| **高** | `internal/logic/post/fetch.go:15-36` + `internal/models/query.go:60-71` | **私密动态泄露**。`Fetch` 丢弃 claims,`PostList` 的查询**没有 `is_open` 条件、没有 `status` 条件、没有作者条件** → `is_open=false` 的"不公开"动态、`status=-1` 的被禁用用户动态都会被返回(`internal/models/feed_post.go:12` 明确 `false-不公开`)。 | +| **高** | `internal/logic/post/action.go:17-28` + `internal/models/query.go:40-57` | **互动计数可无限刷量**。`Action` 只校验三个字符串非空,不校验调用者身份、不落库点赞人、无去重 → 任意登录用户对任意 `identity` 循环调用即可把 `cnt_like` 刷到任意值;且 `action_type` 可传任意值绕过模型绑定。 | +| 中 | `internal/logic/post/delete_comment.go:16-28` | 删除评论接口只校验 `in.Identity` 非空,**不校验评论归属**(底层当前是空实现,但设计上即越权入口)。 | +| 中 | `internal/logic/post/add_comment.go:23-34` + `internal/models/query.go:103-120` | 评论不记录作者:`data` 只设了 `Identity`/`PostIdentity`/`Content`/`ParentIdentity`,`Std_Passport` 全空,且模型函数签名里的 `authorIdentity` 形参**在函数体内从未被使用**(`query.go:103-120`)→ 无法做归属校验、无法展示/追溯评论人。同时不校验 `PostIdentity` 是否存在或可见,可对任意/不存在的动态灌评论。 | +| 中 | `internal/logic/tag/create.go:20-36` | 任何登录用户可批量创建标签,无权限校验、无去重、无长度限制;`Key` 用 `utils.UUID()`(`create.go:26`)与标签内容无关联,标签字典的键不可读。 | +| 低 | `internal/logic/post/create.go:22-24` | 只校验 `content != ""`,无长度上限、无发布频率限制、无敏感内容校验;附件 `url` 与 `attach_type` 由请求方直接写入(`create.go:32-41`)。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/models/query.go:67` | **分页完全失效,且页码被当成条数**。`tx.Limit(int(page)).Offset(int((page - 1) * size))` 中第一个参数应为 `size` 却传了 `page`;更关键的是 `tx.Limit(...).Offset(...)` 的返回值**未回写 `tx`**,后续 `tx.Find` 用的是没有 Limit/Offset 的语句 → 实际返回**全表**(并受第 68 行 Count 影响)。`logic/post/fetch.go:26` 传入 `(in.PageNo, in.PageSize)` 的顺序本身是对的,缺陷在模型层。 | +| **高** | `internal/models/query.go:122-125` | **`DeleteComment` 是空实现**,函数体只有 `return`,永远返回 nil → `Post.DeleteComment` 永远返回"成功",但没有任何删除动作;`logic/post/delete_comment.go:24` 的 `err` 判断因此永远不会触发。 | +| **高** | `internal/models/query.go:60-71` + `internal/logic/post/fetch.go:51-63` | **列表恒不返回标签与附件**。`PostList` 只 `tx.Find(&list)`,没有 `Preload("Tags")`/`Preload("Attachs")`;而 `FeedPost.Tags`/`Attachs` 的标签是 `gorm:"-"`(`internal/models/feed_post.go:16-17`),GORM 永远不会加载它们 → `AssembleList` 里的两个 `for` 循环永不执行,`post.Tags`/`post.Attachs` 恒为空切片。 | +| **高** | `internal/models/query.go:40-57` | **点踩功能不可用**。第 50 行把判断写成了 `} else if action_type == "unlike" {`(应为 `action_op == "unlike"`)→ 点踩时 `column` 保持空串,第 53 行执行 `UpdateColumn("", gorm.Expr(" + ?", 1))`,列名非法导致 SQL 错误、接口报错。另:`action_type` 非 `"post"`/`"comment"` 时第 43-47 行不会调用 `.Model(...)`,`tx` 仍是裸 `impl.DBService`,目标表不确定。 | +| **高** | `internal/logic/post/change.go:25-40` + `internal/models/query.go:30-33` | **修改动态无法改为不公开,附件被静默丢弃**。`ChargePost` 用结构体 `Updates(&data)`,GORM 结构体更新会忽略零值 → `is_open=false` 永远写不进去;`change.go:31-39` 组装的 `postData.Attachs` 只放在 `gorm:"-"` 字段上,`ChargePost` 完全不处理 → 附件修改无效(且是"改了但没报错")。 | +| **高** | `internal/models/query.go:81,89` | `ModifyTags`/`DeleteTags` 使用 `Where("identity = ?", ...)`,而 `FeedTags` **没有 `identity` 列**(`internal/models/feed_tags.go:8-12` 只有 `gorm.Model` + `key` + `content`)→ SQL 会因列不存在而报错。两函数当前无调用点(死代码),一旦接入即报错。 | +| 中 | `internal/logic/tag/list.go:20-32` | **标签列表恒为空**。第 25-31 行已经把 `tags` 组装进 `res`,第 32 行却 `return &pb.TagListReply{}, nil` 新建了一个空对象返回 → `tags` 与 `total` 全部丢失。 | +| 中 | `internal/models/query.go:68,99` | `err = tx.Find(&list).Count(&cnt).Error`:`Find` 与 `Count` 共用同一链,`Count` 会重新构建并执行一次统计查询,`Find` 的错误可能被 `Count` 的结果覆盖,`RowsAffected`/总数语义不清;`TagsList`(`query.go:96-101`)同样写法,且具名返回值 `list` 未被赋值。 | +| 中 | `internal/models/query.go:63-64` | 标签过滤构造不可靠:`tx.Joins("join feed_relate_tags on ...", impl.DBService.Where(&FeedRelateTags{Key: tag}))` 把一个 `*gorm.DB` 当作 `Joins` 的参数值传入(应为 `?` 参数);紧接着第 64 行又用未限定表名的 `Where("key = ?", tag)` 二次过滤 → 生成的 SQL 与预期语义不一致(该分支同时因分页失效而难以验证)。 | +| 中 | `internal/models/query.go:103-120` | `AddComment` 事务内链式顺序颠倒:第 106 行 `tx.Create(comment).Table("feed_post").Where(...).UpdateColumn(...)` 先插入评论、再切换目标表去更新帖子计数,与注释"添加评论,更新文章评论量"的书写顺序相反;第 112 行对"被评论的评论"更新时**未指定 `Model`/`Table`**,目标表只能由 GORM 内部语句状态决定(可能落到 `feed_comment` 自身的 `cnt_comment`),无法从代码确证。 | +| 中 | `internal/logic/post/fetch.go:19-24`、`internal/logic/timeline/*.go:19-24`、`internal/logic/tag/post_list.go:19-24`、`internal/logic/post/comment_list.go:19-24` | `page_size` 只做了下限兜底、**没有上限**:`if in.GetPageSize() <= 0 { in.PageSize = 10 }`(fetch)与 `if in.GetPageSize() < 10 { in.PageSize = 50 }`(其余)两种语义还不一致(后者把传 5 放大成 50,传 20 却保持 20);配合分页失效可一次要求全表。 | +| 中 | `internal/logic/post/add_comment.go:23-30` | 允许 `in.PostIdentity` 为空串(无任何校验)→ 可写入 `post_identity` 为空的孤儿评论;同时 `in.Identity` 被当作 `ParentIdentity`,但未校验该父评论是否属于同一 `post_identity`。 | +| 中 | `internal/logic/post/fetch.go:26` | 标签过滤参数取自 `in.Params["key"]`,键名是硬编码的魔法字符串,与 `TagItem.Key` 语义耦合;传其他键(如 `tag`)会被静默忽略。 | +| 低 | `internal/models/query.go:11` | `InitData()` 直接 `return nil`,而 `cmd/main/main.go:38` 仍以 `srv.Use(models.InitData)` 注册(空初始化)。 | +| 低 | `internal/models/feed_relate_attachs.go:6-12` | `FeedRelateAttach` 内嵌 `types.Std_IDIdentity`(已含 `identity varchar(36) uniqueIndex`)后又显式声明 `Identity string`,**同名列遮蔽**;实际生效的是显式字段,但列定义重复声明,依赖 GORM 的字段冲突消解规则。表名 `feed_relate_attach` 为单数,与 `feed_relate_tags`、`feed_post` 命名风格不一致。 | + +### 6.3 未完成实现 + +| 类别 | 内容 | 证据 | +| --- | --- | --- | +| TODO 占位 RPC(9 个) | `Timeline.Recommend`/`Friend`/`Follow`/`Hot`、`Tag.PostList`、`Post.CommentList`、`Setting.Info`、`Setting.Rights` | `internal/logic/timeline/{recommend,friend,follow,hot}.go:26`、`internal/logic/tag/post_list.go:26`、`internal/logic/post/comment_list.go:26`、`internal/logic/setting/{info,rights}.go:18-20` | +| 空实现函数 | `models.DeleteComment`(只有 `return`) | `internal/models/query.go:122-125` | +| 空实现函数 | `models.InitData`(只有 `return nil`) | `internal/models/query.go:10-12` | +| 无调用点的死函数 | `models.ModifyTags`、`models.DeleteTags`(且 SQL 列名不存在,见 6.2) | `internal/models/query.go:80-94` | +| 未接入聚合 | `service/` 为空目录,无 `Expose`/`Dependencies`;`pkgs/all`、`pkgs/ecmall` 的 `go.mod` 均未引用本模块 | 全仓库对 `module/social/feed` 的引用只有 `scripts/api-docgen/go.mod:18,47` 与根 `README.md:218` | +| gateway 未接线 | 模块内无任何 `RegisterPostHandlerServer` 等调用,`Server.Mux` 也从未被赋值 | `internal/server/new.go:16-36`(`grep RegisterPostHandlerServer module/social/feed` 无匹配) | +| 配置文件名错位 | `etc/passport_dev.yaml`/`passport_prod.yaml`/`passport_test.yaml` 的内容(`Service: feed`、含 `Databases`/`MicroService`/`WeChatConf`/`Kyc`/`Token`)才是与本模块匹配的配置,但 `conf.New` 按 `feed_.yaml` 取名(SDK `conf/new.go:35-36`)→ 这三个文件**永远不会被加载** | `etc/passport_prod.yaml:1-2`、`internal/config/config.go:15-26`、SDK `git.apinb.com/bsm-sdk/core/conf/new.go:35-42` | +| README 为空壳 | `module/social/feed/README.md` 全文只有标题 `# content` | `README.md:1` | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `etc/feed_dev.yaml:1-4`、`etc/feed_prod.yaml:1-4`、`etc/feed_test.yaml:1-4` | 三个 feed 配置都用 `Name`/`ListenOn`/`Dsn`,而 `SrvConfig` 只识别 `Service`/`Port`/`Databases`(`internal/config/config.go:17-18`)。SDK 在 YAML 解析前先检查文件是否含子串 `"Service:"`(`git.apinb.com/bsm-sdk/core/conf/new.go:58-60`),这三个文件只含 `ServiceName:` → 直接 `log.Fatalln("ERROR: Service Not Nil")` 退出;即使能加载,`Spec.Service` 仍为空,`conf.NotNil`(`config.go:56`)同样致命。 | +| **高** | `internal/impl/impl.go:24` + `etc/feed_*.yaml` | 三个 feed 配置均无 `Databases` 段(用的是 `Dsn`),`with.Databases(nil, nil)` 会直接 `panic("No Database Source Found !")`(`git.apinb.com/bsm-sdk/core/with/databases.go:13-15`)。 | +| **高** | `etc/feed_dev.yaml:16-19`、`feed_prod.yaml:15-18`、`feed_test.yaml:16-19` | 匿名清单写成顶层 `Anonymous: {Key, Urls}`,而 SDK 的匿名清单在 `conf.MicroServiceConf.Anonymous`(`conf/types.go:25`)→ `Anonymous` 整块被忽略;三个配置也都没有 `MicroService` 段,`MsConf` 为 nil → `service.Start` 跳过 etcd 注册(SDK `service/service.go:66`)。`passport_prod.yaml:15` 的匿名项还写成 `feed.ping.hello`(模块内不存在此方法)。 | +| 中 | `internal/server/new.go:16-36`、`cmd/main/main.go:33` | 既没有 `gwRuntime.NewServeMux()`,也没有 `pb.RegisterXxxHandlerServer` 调用,`Server.Mux` 恒为 nil → `cmd/main/main.go:33` 把 nil 传给 `GatewayMux`,`service.Start` 里 `http.ListenAndServe(addr, nil)` 走 `DefaultServeMux`,所有 `/feed.*` 路径 404。模块自身也无法像 wallet 那样通过 `service.Expose` 被聚合宿主接线。 | +| 中 | `etc/feed_prod.yaml:4`、`etc/feed_dev.yaml:4` | 数据库连接串明文提交入库,含生产库账号与口令(`postgres://prod:MakeW2023~PROD@192.168.0.224:5432/scf`)、开发库地址(`47.108.57.74:5432/milu`)。 | +| 中 | `etc/supervisor.bsm-apps-passport.conf:1` | 文件名仍是 passport,内容 program 名却是 `bsm-apps-feed`;与 `etc/passport_*.yaml` 一并说明该目录是从 passport 服务复制而来,未清理。 | +| 中 | `etc/passport_prod.yaml:31-35` | 同一份配置里同时存在 `Kyc:` 与 `KycConf:` 两个段,而 `config.SrvConfig` 只映射 `Kyc`(`internal/config/config.go:25`)→ `KycConf` 整块为死配置。 | +| 中 | `internal/impl/impl.go:21-24` | `MemorySerice`、`RedisService` 被初始化,但全模块无任何使用点(死代码);`EtcdService` 仅在微服务注册路径使用,而 `MsConf` 恰好为 nil。 | +| 低 | `internal/models/query.go:69` | 残留调试语句 `fmt.Print("posts", list)`,会把整页动态实体(含 content)打印到进程标准输出。 | +| 低 | `cmd/cli/main.go:9-12` | CLI 入口只做 `config.New("feed")` 并 `fmt.Println(config.Spec.Databases)`,调试残留;且服务键传小写 `"feed"`,与主入口的 `ServiceKey = "Feed"` 不一致(`conf.New` 拼文件名时会 `ToLower`,但 `vars.ServiceKey` 保留原样,影响日志/注册键大小写)。 | +| 低 | 全模块 | 0 个 `*_test.go`;`test/` 下只有一个**空的** `lint/` 目录,无任何请求样例。 | +| 低 | `internal/logic/post/fetch.go:39-67` | `AssembleList` 是包级导出函数但只在本文件使用,且内部对 `res` 使用具名返回追加(`res = append(res, post)`),在 data 为空时返回 nil 切片而非空切片,前端需要额外判空。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| F1 | 高 | 删除动态无归属校验,可删他人动态 | 数据完整性、越权 | +| F2 | 高 | 修改动态无归属校验,可篡改他人动态 | 数据完整性、越权 | +| F3 | 高 | 列表无 `is_open`/`status` 过滤,私密动态泄露 | 隐私泄露 | +| F4 | 高 | 互动计数无去重、无身份绑定,可无限刷量 | 数据可信性 | +| F5 | 高 | 分页 `Limit(page)` + 未回写 → 分页失效并全表返回 | 功能不可用 + 性能风险 | +| F6 | 高 | `DeleteComment` 空实现,删除接口永远"成功" | 功能不可用 | +| F7 | 高 | 列表恒不返回标签与附件(`gorm:"-"` + 无 `Preload`) | 功能不可用 | +| F8 | 高 | 点踩计数条件写错导致 SQL 列名为空 | 功能不可用 | +| F9 | 高 | 修改动态无法改为不公开,附件修改被静默丢弃 | 功能不正确 | +| F10 | 高 | `feed_*.yaml` 字段名不匹配 → 启动即退出;无 `Databases` → panic | 部署可用性 | +| F11 | 高 | gateway handler 从未注册、`Mux` 为 nil、无 `service.Expose` | 接口不可用、无法聚合 | +| F12 | 高 | `feed_tags` 无 `identity` 列却被 `Where("identity")` 使用 | 接入即报错 | +| F13 | 中 | 评论不记录作者、不校验所属动态 | 越权、数据不可追溯 | +| F14 | 中 | 标签列表返回值被丢弃,恒为空 | 功能不可用 | +| F15 | 中 | `AddComment` 事务内 `Table` 用法与未指定 Model 的更新 | 结果不确定、可能整事务回滚 | +| F16 | 中 | 9 个 RPC 为 TODO 占位,返回 `(nil, nil)` | 功能缺失、网关异常 | +| F17 | 中 | 生产/开发数据库连接串明文入库 | 凭据泄露 | +| F18 | 中 | `page_size` 无上限、上下限语义不一致 | 资源滥用 | +| F19 | 低 | 死代码、调试 `fmt.Print`、无测试、README 空壳 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **补归属校验**(F1/F2/F13):`Remove`/`Change` 在 logic 层先按 `identity + passport_identity` 查一次动态,查不到即返回"无权操作";或把模型函数改为 `DeletePost(identity, passportIdentity)`、`ChargePost(data, passportIdentity)` 并加上 `Where("passport_identity = ?", ...)`。评论同理,把 `AddComment` 的 `authorIdentity` 真正写进 `FeedComment.Std_Passport`,删除评论按 `identity + passport_identity` 定位。 +2. **修可见性过滤**(F3):`PostList` 增加 `is_open = true` 与作者本人可见的 `OR` 条件,并按需过滤 `status <> -1`;`Fetch` 不要丢弃 claims,用调用者身份决定是否可见私密动态。 +3. **修分页**(F5/F18):`query.go:67` 把 `Limit(int(page))` 改为 `Limit(int(size))`,并把 `tx = tx.Limit(...).Offset(...)` 回写;给 `page_size` 设上限(如 50),并把 `fetch.go`、`comment_list.go`、四个 timeline、`tag/post_list.go` 的兜底逻辑统一为 `<= 0 则取默认值`。 +4. **修删除与计数**(F6/F8):`DeleteComment` 补实现(软删除 + 同事务把对应 `feed_post.cnt_comment` 减 1,并做下限保护);`LikeAction` 的第 50 行改为 `action_op == "unlike"`,`action_type` 用白名单(`post`/`comment`)校验并显式设置 `Model`,`column` 为空时直接返回参数错误。 +5. **修标签与附件读写**(F7/F9/F12/F14):`PostList` 用 `Preload("Tags")`/`Preload("Attachs")` 之前,需先把 `FeedPost.Tags`/`Attachs` 的 `gorm:"-"` 去掉或改写为显式二次查询(按 `post_identity IN (...)` 批量取关联表再回填);`FeedRelateTags.Content` 去掉 `gorm:"-"` 才能真正存标签名;`Change` 用 `Select("content","is_open").Updates(...)` 或 map 更新以写入 `is_open=false`,并显式处理附件的增删;`FeedTags` 的 `Where("identity = ?")` 改为按主键或 `key` 定位;`tag/list.go:32` 改为 `return res, nil`。 +6. **配置**(F10):把 `etc/feed_dev.yaml`/`feed_prod.yaml`/`feed_test.yaml` 按 `SrvConfig` 重写(`Service`/`Port`/`Databases`/`Cache`/`MicroService.Anonymous`/`Gateway`),或直接把已正确的 `etc/passport_*.yaml` 重命名为 `feed_*.yaml` 并删除冗余文件;`config.New` 里补 `conf.NotNil(Spec.Databases)`;把 `Dsn` 中的口令改为环境变量注入并轮换已泄露的生产口令。 +7. **接线与启动**(F11):在 `internal/server/new.go` 中给 `Mux` 赋 `gwRuntime.NewServeMux()` 并注册 4 组 handler,或在 `service/` 下补一份与 wallet 同构的 `expose.go`/`dependencies.go` 以便接入 `pkgs/all`;否则 `Gateway.Enable=true` 只会得到一个 404 网关。 +8. **补初始化与占位反馈**(F16):`models.InitData` 要么实现必要的基础数据,要么在 `cmd/main/main.go:38` 去掉 `srv.Use(models.InitData)`;9 个 TODO RPC 在实现前应返回明确的 `Unimplemented` 错误,而不是 `(nil, nil)`。 +9. **清理**(F19):删除 `internal/models/query.go:69` 的 `fmt.Print`、`internal/models/query.go:80-94` 的 `ModifyTags`/`DeleteTags`(无调用点且 SQL 错误)、`internal/impl/impl.go` 中未使用的 Redis/Memory 初始化、`etc/passport_prod.yaml:28-33` 的 `KycConf` 死配置;`README.md` 至少补一段服务说明。 +10. **测试**:为 `PostList`(分页与可见性过滤)、`LikeAction`(点赞/点踩各一次)、`DeleteComment`、`AddComment`(作者落库与计数一致)各写一条正例 + 一条边界用例。 + +> 本报告只列出与现有实现直接相关的修复项。为 feed 引入"统一内容框架""时间线抽象层""互动事件总线""DTO/VO 分层"一类改造不在建议范围内——上述 10 条都是对现有函数与配置的最小修正。 diff --git a/docs/feedback.md b/docs/feedback.md new file mode 100644 index 0000000..04abe7a --- /dev/null +++ b/docs/feedback.md @@ -0,0 +1,179 @@ +# feedback(意见反馈管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/feedback` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件 19 个、821 行(含 `test/rpc/rpc.go` 23 行空壳);`proto` 2 个(`feedback.proto`、`const.proto`);`pb/` 生成文件 4 个 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、聚合入口 `pkgs/all`(`pkgs/all/internal/service/feedback.go`) | +| 对外协议 | gRPC + REST Gateway,前缀 `/feedback.Method` | +| 结论摘要 | 越权缺陷成堆:`Get`/`Delete`/`Remark` 连身份都不解析,`Modify` 解析了身份却仍按调用方传入的 `identity` 更新,四个接口**均无归属校验**(IDOR),且 `Modify` 会把记录 `identity` 改写成随机 UUID 并把归属改成攻击者;`Modify` 处理附件时未生成 `identity`,子表 `identity` 是唯一索引,附件 ≥2 条必然唯一键冲突;`List`/`Get` 只 `Preload("Images")`,附件永远为空;此外 `etc/` 下 4 份 yaml 全部不符合 SDK 配置约定(`Name`/`ListenOn`/`Dsn` + 顶层 `Anonymous`),独立进程启动即 `log.Fatalln`。 | + +## 1. 服务定位与职责 + +用户意见/反馈的提交与后台管理:提交反馈(含图片、附件)、按条件分页查询、查看详情、修改、删除、填写备注并流转状态。数据模型为"反馈主表 + 图片子表 + 附件子表"。它不负责附件文件本身的上传(那是 `module/base/fts` 的职责),只保存 URL/路径。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("Feedback")` → `impl.NewImpl` → `server.New(nil)` → `service.New(...).Start()` | +| `cmd/cli/main.go` | 命令行入口,仅 `log.Println("Hello World!")`(7 行,占位) | +| `internal/config/config.go` | 配置结构(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`APM`/`Etcd`)与 `conf.NotNil` 校验 | +| `internal/impl/impl.go` | 初始化 DB / Redis / Etcd / 内存缓存 | +| `internal/logic/method/list.go` | `List`:分页 + 按 identity/agency/username/status/category 过滤 | +| `internal/logic/method/get.go` | `Get`:按 `identity` 查详情 | +| `internal/logic/method/add.go` | `Add`:创建主表 + 图片 + 附件 | +| `internal/logic/method/modify.go` | `Modify`:删子表 → 更新主表 → 重建子表 | +| `internal/logic/method/delete.go` | `Delete`:按 `identity` 删主表 | +| `internal/logic/method/remark.go` | `Remark`:更新 `remark` + `status` | +| `internal/logic/method/ref.go` | `convert`:模型 → pb 转换(含图片、附件) | +| `internal/models/feedback_item.go` | `feedback_item` 主表模型 + 迁移注册 | +| `internal/models/feedback_images.go` | `feedback_images` 图片子表 | +| `internal/models/feedback_accessory.go` | `feedback_accessory` 附件子表 | +| `internal/server/{new,method_server}.go` | 注册 `Method` 到 gRPC,handler 转发 logic | +| `internal/routers` | **不存在**(走 gRPC + HTTP Gateway) | +| `service/{expose,dependencies}.go` | 聚合宿主注入接口 | +| `etc/{feedback.yaml,feedback_dev,feedback_prod,feedback_test}.yaml` | 4 份配置,字段命名与 SDK 约定不符(见 6.3) | +| `test/add.http`、`test/rpc/rpc.go` | 手工用例(JSON 非法)+ 注释空壳 | + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/feedback.Method/List` | 分页查询反馈(条件过滤) | 网关 JWT + `service.ParseMetaCtx`(`list.go:19`) | `internal/logic/method/list.go` | +| POST | `/feedback.Method/Get` | 按 `identity` 查详情(返回 email/phone) | 网关 JWT,**进程内不解析身份**(`get.go` 无 ParseMetaCtx) | `internal/logic/method/get.go` | +| POST | `/feedback.Method/Add` | 新增反馈 | 网关 JWT + `ParseMetaCtx`(`add.go:21`) | `internal/logic/method/add.go` | +| POST | `/feedback.Method/Modify` | 按 `identity` 修改 | 网关 JWT + `ParseMetaCtx`(`modify.go:24`) | `internal/logic/method/modify.go` | +| POST | `/feedback.Method/Delete` | 按 `identity` 删除 | 网关 JWT,**进程内不解析身份**(`delete.go`) | `internal/logic/method/delete.go` | +| POST | `/feedback.Method/Remark` | 按 `identity` 更新备注 + 状态 | 网关 JWT,**进程内不解析身份**(`remark.go`) | `internal/logic/method/remark.go` | + +**声明但未实现的接口**:无。`proto/feedback.proto:6-13` 的 `Method` 服务 6 个 rpc 与 `pb/feedback_grpc.pb.go` 的 `MethodServer`、`internal/server/method_server.go:18-40` 的转发方法一一对应。 + +> 鉴权补充: +> 1. 模块 yaml 里列出的 `feedback.Check.Hello/Updates`、`feedback.Data.Configure/Areas/Tags`(`etc/feedback_dev.yaml:19-23`)在本模块 proto 中**根本不存在**,属死配置,详见 6.3。 +> 2. 聚合入口 `pkgs/all/etc/default_dev.yaml:24-43` 的 `Authorization.Anonymous` 也未包含 `/feedback.Method/*`,故上线形态下 6 个接口都要求携带有效 JWT——但"有效 JWT"仅代表"是某个登录用户",接口内部不做任何归属判断,见 6.1。 + +## 4. 数据模型与表 + +### 表 `feedback_item`(`internal/models/feedback_item.go`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 来自 `types.Std_IICUDS` | +| `identity` | varchar(36) | uniqueIndex | 记录唯一标识(`D:\work\bsm-sdk\core\types\db.go:30`) | +| `created_at`/`updated_at`/`deleted_at` | timestamp | - | 带软删 | +| `passport_id` | uint | Index | 提交人 ID(`types.Std_Passport`) | +| `passport_identity` | varchar(36) | Index | 提交人标识,`List` 的归属过滤字段 | +| `status` | int32 | default 1 | 1 未处理 / 2 已处理(**与内嵌 `Std_IICUDS.Status` int8 同列冲突**,见 6.2) | +| `category` / `user_name` / `email` / `phone` | varchar | - | 分类 / 用户名 / 邮箱 / 手机(**含 PII**) | +| `title`(255)/ `content`(500)/ `remark`(500) | varchar | - | 标题 / 内容 / 备注 | +| `agency` | varchar(255) | - | 机构 | +| `images` / `accessories` | - | `foreignKey:ItemIdentity` | GORM 关联字段(非列) | + +### 表 `feedback_images` / `feedback_accessory` + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 子表唯一标识(来自 `Std_IICUDS`) | +| `item_identity` | varchar(36) | default '' | 关联主表 `identity`,无 DB 级外键 | +| `url`(图片) | varchar(255) | default '' | 图片地址 | +| `title` / `file_path`(附件) | varchar(255) / varchar(500) | not null | 附件标题 / 附件路径 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /feedback.Method/Modify"] --> B["service.ParseMetaCtx 解析 auth"] + B --> C["校验 in.Identity 非空"] + C --> D["构造 record:Identity = utils.UUID(),归属改写为 auth"] + D --> E["按 item_identity 删除 feedback_images / feedback_accessory"] + E --> F["Where(identity = in.Identity).Updates(record)"] + F --> G["GORM 顺带 upsert 非空 HasMany 关联"] + G --> H["显式 Create(&record.Images) / Create(&record.Accessories)"] + I["POST /feedback.Method/Get"] --> J["Where(identity = in.Identity).First:无身份、无归属校验"] + J --> K["返回含 email / phone 的完整记录"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/method/get.go:19-41`(关键:`:21` 只校验 `identity` 非空、`:29` 按 `identity` 查询) | `Get` **完全不解析调用方身份**,仅按请求体里的 `identity` 取记录,且返回体包含 `email`/`phone`(`ref.go:17-18`)。任何持有效 JWT 的用户遍历 `identity` 即可读取**全部用户**的反馈与联系方式(IDOR + PII 泄露)。 | +| **高** | `internal/logic/method/delete.go:19-29`(关键:`:26`) | `Delete` **不解析身份、不做归属校验**,`Where("identity = ?", in.GetIdentity()).Delete(...)` 直接删除。任何登录用户可删除他人反馈(软删,`deleted_at`)。 | +| **高** | `internal/logic/method/remark.go:19-35`(关键:`:21`,`:28`,`:32`) | `Remark` **不解析身份、不做归属校验**,且 `status` 取自请求体(`remark.go:28`)→ 任何登录用户可改写任意反馈的备注与处理状态(可把"已处理"回退或伪造)。 | +| **高** | `internal/logic/method/modify.go:22-27,30,89` | `Modify` 虽然解析了 `auth`,但**只用它覆写归属字段**(`modify.go:39-42`),更新条件仍是调用方传入的 `in.Identity`(`modify.go:89`)→ 可篡改他人反馈;更严重的是会把该记录的 `passport_identity` 改成攻击者自己,**完成"夺权"**:原主人因 `List` 按 `passport_identity` 过滤(`list.go:43`)而再也看不到自己的反馈。 | +| 中 | `internal/logic/method/list.go:38-45` | `List` 的过滤是"二选一":传入 `agency` 时**只**按 `agency` 过滤,`passport_identity` 条件被跳过 → 任意登录用户只要知道机构名,就能枚举该机构下**全部**反馈,且响应含 `email`/`phone`(`ref.go:17-18`)。 | +| 中 | `internal/logic/method/list.go:41-44` | 当 `auth.Identity` 为空串时,`passport_identity` 过滤被跳过(`if userIdentity != ""`),查询退化为无归属条件的分页查询。`service.ParseMetaCtx` 只校验 JWT 签名与有效期、不校验 `identity` 非空(`D:\work\bsm-sdk\core\service\meta.go:19-47`),因此该分支在 token 未携带 `identity` 时可达。 | +| 低 | `internal/logic/method/ref.go:13-26` | 列表/详情统一输出 `email`、`phone`,PII 在所有读接口上无脱敏、无权限分级。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/method/modify.go:35-38,89` | `Updates(record)` 的 `record` 里 `Std_IICUDS.Identity = utils.UUID()`(新 UUID)。GORM 结构体 `Updates` 只忽略零值,`Identity` 非零 → **主表 `identity` 列被改写成新 UUID**。由于 `Where` 用的是旧 `identity`,更新后原 `identity` 立即失效,`Get`/`Modify`/`Delete`/`Remark` 全部找不到该记录,任何外部引用(如工单、通知)随之断裂。 | +| **高** | `internal/logic/method/modify.go:70-76` | 构造附件时**未设置 `Identity`**(对比 `add.go:57-66` 明确赋 `utils.UUID()`),`FeedbackAccessory` 的 `identity` 取零值 `""`;而子表 `identity` 是 `uniqueIndex`(`D:\work\bsm-sdk\core\types\db.go:30`)→ **附件 ≥2 条时必然唯一键冲突**;即使只传 1 条,也会写入一行 `identity=""` 的脏数据,后续再改就再也插不进去。 | +| **高** | `internal/logic/method/modify.go:50-52,89,95-106` | 关联被写入两遍。GORM v2 的 Update 处理器注册了 `gorm:save_after_associations`(`gorm@v1.31.2/callbacks/callbacks.go:72`),`SaveAfterAssociations(false)` 对 ReflectValue 为 Struct 的 `HasMany` 字段会调用 `saveAssociations`(`callbacks/associations.go:190-236`),最终执行 `tx.Create(values)`(`callbacks/associations.go:431`,非 `FullSaveAssociations` 时带 `ON CONFLICT DO NOTHING`,见 `:371-372`)。也就是说 `modify.go:89` 的 `Updates` 已把 `Images`/`Accessories` 插入库中,随后 `modify.go:96`/`:102` 又对同一批(同 `identity`)显式 `Create` → 第二次插入撞唯一索引,接口返回错误,而主表与子表**其实已经改了一半**(无事务包裹)。 | +| 中 | `internal/logic/method/delete.go:25-26` | 注释写"GORM会自动处理关联的图片和附件删除",但模型只在关联字段上声明了 `foreignKey:ItemIdentity`(`feedback_item.go:23-24`),**没有 `OnDelete:CASCADE`**,子表也无 DB 级外键 → `Delete` 只删主表,`feedback_images`/`feedback_accessory` 成为孤儿数据(`Modify` 是手动先删子表的,`Delete` 没做)。 | +| 中 | `internal/models/feedback_item.go:12` 与 `:18` | `status` 列被定义两次:内嵌 `types.Std_IICUDS.Status`(int8,`default:0;index`)与自定义 `Status int32`(`default:1`)。GORM 按"最短绑定路径优先"解析(`gorm@v1.31.2/schema/schema.go:238`),外层字段胜出、内嵌字段被遮蔽 → 实际建表丢失 `index` 标签,默认值语义也从 SDK 约定的 0 变成 1。 | +| 中 | `internal/logic/method/list.go:35` 与 `internal/logic/method/get.go:29` | 两处只 `Preload("Images")`,**没有 `Preload("Accessories")`**,而 `ref.go:38-45` 会转换并输出附件 → `List`/`Get` 返回的 `accessories` 恒为空数组,与 `proto/feedback.proto:46`(`repeated FeedbackAccessory accessories = 12`)的契约不符。 | +| 低 | `internal/logic/method/list.go:25-32,71` | `page` 无上限钳制(只保证 `>= 1`),`offset = (page-1)*size` 直接下推为 `OFFSET`,传超大 `page` 会造成大偏移慢查询。 | +| 低 | `internal/logic/method/remark.go:37-40`、`internal/logic/method/delete.go:31-34` | 返回 `pb.StatusReply` 只填 `Message`/`Timeseq`,`code` 恒为 0(`proto/feedback.proto:126`),调用方无法从 `code` 判断成功/失败。 | +| — | `internal/logic/method/list.go:71` | `.Find(&list).Count(&count)` 复用同一 `Statement` 看似可疑,但 GORM 在 `Execute` 结束时会重置 SQL(`gorm@v1.31.2/callbacks.go:149-152`),`Count` 重建为 `SELECT count(*) ... LIMIT n OFFSET m`,聚合结果单行不受 `LIMIT` 影响,`count` 等于命中总数。**非缺陷**(特此说明)。 | +| — | `internal/logic/method/add.go:36` | `Add` 接受调用方传入的 `status`,与 `proto/feedback.proto:74` 注释"也可以调用方自行设置"一致,属设计约定;但结合 6.1 的 `Remark` 问题,状态流转整体缺少权限约束。 | + +### 6.3 未完成实现 + +- **独立进程无法启动:4 份 etc 配置全不符合 SDK 约定**。`etc/feedback_dev.yaml:1-4` 使用 `Name:` / `ListenOn:` / `Dsn:`,而 SDK 的 `conf.Base` yaml 标签是 `Service`/`Port`/`Cache`/`SecretKey`/`BindIP`(`D:\work\bsm-sdk\core\conf\types.go:7-16`);更关键的是 `conf.New` 在解析前会校验文件文本必须包含 `Service:`,否则 `log.Fatalln("ERROR: Service Not Nil")`(`D:\work\bsm-sdk\core\conf\new.go:58-60`)。`feedback.yaml`、`feedback_dev.yaml`、`feedback_prod.yaml`、`feedback_test.yaml` 都只有 `ServiceName:`(不匹配 `Service:` 子串),默认 `BSM_RuntimeMode=dev`(`D:\work\bsm-sdk\core\env\env.go:20`)会加载 `etc/feedback_dev.yaml` → **进程直接 Fatal 退出**。即便绕过该校验,因无 `Databases:` 段,`with.Databases` 也会 `panic("No Database Source Found !")`(`D:\work\bsm-sdk\core\with\databases.go:13-15`)。`etc/feedback_prod.yaml:4` 是 `Dsn: postgres://prod:***@192.168.0.224:5432/scf...`,即"生产配置"同样不可用。 +- **匿名清单整段失效且内容不存在**:`etc/feedback_dev.yaml:16-23` 用的是顶层 `Anonymous:` + `Key:`/`Urls:` 结构,而 SDK 期望 `MicroService: {Enable: bool, Anonymous: []string}`(`conf\types.go:23-26`);`internal/config/config.go:17-25` 的 `SrvConfig` 也没有对应字段 → 该段被 yaml **静默忽略**(`conf.New` 未启用 strict 模式)。同时 `MicroService:` 整段缺失 → `Spec.MicroService == nil` → `service.Start` 既不注册服务也不发布匿名清单(`D:\work\bsm-sdk\core\service\service.go:66-90`)。所列 `feedback.Check.Hello/Updates`、`feedback.Data.Configure/Areas/Tags` 在本模块 proto 中不存在(`proto/feedback.proto:6-13` 只有 `Method`)→ 死配置。 +- `proto/feedback.proto:119-122` 的 `IdentRequest` 消息未被任何 rpc 引用 → 死消息。 +- 未发现显式 TODO/占位。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/config/config.go:39` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`(与 ads 等模块同模式),配合 `with.Databases` 的 panic,缺配置时表现为启动崩溃且报错不含配置项名。 | +| 中 | `internal/logic/method/modify.go:79-106` | 删子表 → 更新主表 → 建子表 三步**没有事务包裹**(对比 `add.go:69` 的单次 `Create` 由 GORM 自动开事务),任一步失败都会留下半成品数据。 | +| 低 | `internal/impl/impl.go:13-16` | `RedisService`/`EtcdService`/`MemorySerice` 初始化后本模块无任何使用点(死代码,同 ads)。 | +| 低 | `test/add.http:9,16` | 用例 JSON 含 `"type"` 字段(proto 无此字段)且末尾多一个逗号(非法 JSON),无法直接执行;`status` 未传。 | +| 低 | `test/rpc/rpc.go:3-23` | 整个 `main` 被注释,且引用不存在的 `pb.NewMethodClient`/`pb.Crc` → 死代码。 | +| 低 | 全模块 | **无任何 `*_test.go`**(`test/rpc/rpc.go` 在 `package rpc` 下且函数体全注释),上述 IDOR 与附件唯一键冲突均无测试覆盖。 | +| 低 | `internal/logic/method/modify.go:56-59` | 图片 `identity` 缺省时生成新 UUID,意味着"不传 identity 就重建",与"按 identity 幂等更新"的语义不一致;结合 6.2 的重复写入问题,同一图片会被覆盖或冲突。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| F1 | 高 | `Get`/`Delete`/`Remark` 不解析身份 + 四接口均无归属校验(IDOR) | 越权读取/删除/篡改他人反馈 | +| F2 | 高 | `Get`/`List` 返回 `email`/`phone`(PII) | 用户隐私泄露 | +| F3 | 高 | `Modify` 把主表 `identity` 改写为随机 UUID | 记录"消失"、外部引用断裂 | +| F4 | 高 | `Modify` 附件未赋 `identity` → 子表唯一键冲突 | 修改功能不可用 | +| F5 | 高 | 关联被 `Updates` 与显式 `Create` 写两遍 + 无事务 | 修改报错且数据半更新 | +| F6 | 中 | `List` 的 `agency` 分支绕过归属过滤;`auth.Identity` 为空时返回全量 | 机构级数据枚举 | +| F7 | 中 | `List`/`Get` 未 Preload 附件 | 接口返回与 proto 契约不符 | +| F8 | 中 | `Delete` 不清理子表 | 孤儿数据堆积 | +| F9 | 中 | 4 份 etc 配置不符合 SDK 约定,独立进程 Fatal;匿名清单整段失效且引用不存在的服务 | 独立部署不可用、鉴权策略失真 | +| F10 | 中 | `status` 列重复定义(`Std_IICUDS.Status` 与自定义 `Status`) | 建表丢索引、默认值语义漂移 | +| F11 | 低 | 死代码(Redis/Etcd/Memory、`IdentRequest`、`test/rpc`)、非法 http 用例、无测试、无事务 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **补齐归属校验(最高优先)**:`Get`/`Delete`/`Remark` 先 `service.ParseMetaCtx(ctx, nil)` 取 `auth`,再按 `in.Identity` 取出记录,比对 `record.PassportIdentity == auth.Identity`;`Modify` 在现有 `auth` 基础上加同一比对。管理侧(跨用户操作)另设显式判断,不要用"任意有效 JWT"代表权限。 +2. **`Modify` 不要重设 `identity`**:从更新结构体里移除 `Std_IICUDS.Identity`(参考 `Map` 更新或 `Select(...)` 只选允许字段),保留原 `identity`;同时不要把 `Std_Passport` 用调用方身份覆写(会造成归属转移)。 +3. **消除重复关联写入**:`modify.go:89` 的 `Updates` 前加 `.Omit(clause.Associations)`(或 `Select` 限定主表字段),只保留 `:95-106` 的显式 `Create`;二者只能留一个。 +4. **补附件 `identity`**:`modify.go:70-76` 与 `add.go:57-66` 对齐,为 `FeedbackAccessory` 赋 `Identity: utils.UUID()`。 +5. **`Delete` 清理子表**:按 `item_identity = in.Identity` 先删 `feedback_images`/`feedback_accessory`,再删主表(与 `Modify` 的现有动作一致)。 +6. **事务**:把 `modify.go:79-106` 的删/更新/建三步放进 `impl.DBService.Transaction(func(tx *gorm.DB) error {...})`。 +7. **`Preload` 补齐**:`list.go:35`、`get.go:29` 增加 `Preload("Accessories")`。 +8. **`List` 权限收敛**:`agency` 过滤仅对管理角色开放;普通用户强制附加 `passport_identity = auth.Identity`;`auth.Identity` 为空时直接返回拒绝而不是放宽条件。 +9. **`Remark` 限制状态**:仅允许管理角色传 `status`,或把 `status` 从请求体移除、由服务端按流程计算。 +10. **`Get`/`Delete`/`Remark` 返回值补 `code`**:`StatusReply.Code` 填明确成功/失败码,避免调用方只能看 `message`。 +11. **配置修正**:把 `etc/feedback*.yaml` 改成 SDK 约定(`Service:`/`Port:`/`BindIP:`/`Cache:`/`Databases:` + `MicroService: {Enable, Anonymous}`),删除 `feedback.Check.*`/`feedback.Data.*` 等不存在的匿名项;`conf.NotNil` 补 `Databases`。 +12. **模型去重**:`feedback_item.go` 保留一处 `status` 定义(建议保留自定义 `int32` 并显式写 `index`,或移除自定义字段改用 `Std_IICUDS.Status` 并统一类型)。 +13. **清理与补测**:删 `impl.go` 中未使用的 Redis/Etcd/Memory 初始化、`proto/feedback.proto:119-122` 的 `IdentRequest`、`test/rpc/rpc.go`;修正 `test/add.http` 的非法 JSON;为 `Get`/`Modify`/`Delete`/`Remark` 各写 1 条越权用例与 1 条正常用例。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 diff --git a/docs/fts.md b/docs/fts.md new file mode 100644 index 0000000..6ba8674 --- /dev/null +++ b/docs/fts.md @@ -0,0 +1,177 @@ +# fts(文件传输与上传存储)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/fts` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件 19 个、1101 行(含 `test/fts_test.go` 167 行、`internal/routers/register_test.go` 34 行、`internal/errors`+`internal/response` 262 行死包);**无 `proto`/`pb`**(本模块不提供 gRPC) | +| 入口 | `cmd/main`(Gin 单进程,Port 16290)、聚合入口 `pkgs/all`(`pkgs/all/internal/service/fts.go`,注入 `Engine: srv.HTTP`) | +| 对外协议 | 仅原生 REST,前缀 `/rest/fts` | +| 结论摘要 | 上传路由确实挂了 `middleware.JwtAuth(true)`(`internal/routers/uploader.go:13`),**鉴权本身已配置**;真正的高危点是**本地存储路径穿越**(`bucket` 来自表单、仅做 `ToLower`,直接参与 `filepath.Join`,可写出 `UploadDir` 之外)、`NewSubdir` 对 JWT `identity` 直接切片导致的 panic、以及上传体大小校验晚于 `FormFile` 造成的资源耗尽;此外路由前缀 `/rest/fts` 与自带单测期望的 `/rest/fts/v1` 不一致(`go test ./...` 必失败),本地分支存在**同一响应写两遍**,且 262 行的 `internal/errors`+`internal/response` 包整体未被引用。 | + +## 1. 服务定位与职责 + +接收客户端上传的文件,按 `provider` 落到本地磁盘或 MinIO,计算 sha256,往 `fts_record` 写一条记录并返回可访问的 `ResultUrl`。它是文件**传输与存储**服务:不负责下载鉴权、病毒扫描、图片处理,也不提供文件列表/删除/闪传(这些接口在 `internal/logic/fetch.go` 中只留了注释)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("fts")` → `impl.NewImpl` → `gin.Default()` → `routers.Register` → `app.Run(:Port)` | +| `cmd/cli/main.go` | CLI,实际是**生成 licence 文件**的构建辅助(硬编码 `/data/app/etc/licence.key`),与 fts 业务无关 | +| `internal/config/config.go` | 配置结构:`Base`/`Databases`/`Rpc`/`APM`/`Etcd`/`MinioOss`/`Local`/`FtsConfig`,含 `conf.NotNil` 校验 | +| `internal/impl/impl.go` | 初始化 Memory / Redis / DB / Etcd | +| `internal/routers/register.go` | 路由注册:匿名组 `GET /rest/fts/ping`、`GET /rest/fts/config` | +| `internal/routers/uploader.go` | 鉴权组 `POST /rest/fts/uploader`(`middleware.JwtAuth(true)`) | +| `internal/routers/register_test.go` | 路由断言单测,期望 `/rest/fts/v1/*`(与实现不一致) | +| `internal/logic/handler.go` | `Handler`:上传主流程(鉴权解析、参数校验、大小/后缀校验、sha256、分发 provider、落库) | +| `internal/logic/provider.go` | `LocalUpload`(本地写盘)、`OssUpload`(MinIO)、`NewSubdir` | +| `internal/logic/config.go` | `GET /config`:直接返回 `FtsConfig` | +| `internal/logic/ping.go` | `GET /ping` | +| `internal/logic/fetch.go` | **整文件被注释**,历史 `List` 逻辑 | +| `internal/models/fts_record.go` | `fts_record` 表模型 + 迁移注册;48-98 行为注释掉的旧实现 | +| `internal/models/query.go` | `InitData()` 空函数(6 行) | +| `internal/errors/errors.go` | 118 行业务错误码定义,**仅被下方 response 包引用** | +| `internal/response/response.go` | 144 行统一响应封装,**全模块无调用点** | +| `service/{expose,dependencies}.go` | 聚合宿主注入接口(`ExposeOptions.Engine/Config`) | +| `etc/{fts_dev,fts_prod,fts_test}.yaml` | 三份内容一致;`MinioOss`/`Local`/`FtsConfig` 三段配置 | +| `test/fts_test.go` | `//go:build integration` 集成用例,URL 为历史路径 | +| `test/oss_provider_req.http` | 手工上传用例(`/fts/v1/uploader`) | + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| GET | `/rest/fts/ping` | 健康探测,返回 `{"message":"Pong"}` | 无(匿名组,`register.go:20-22`) | `internal/logic/ping.go:9` | +| GET | `/rest/fts/config` | 返回 `FtsConfig`(`InputKey`/`MaxSize`/`Allows`) | 无(匿名组,`register.go:23`) | `internal/logic/config.go:9` | +| POST | `/rest/fts/uploader` | 上传文件到 local / minio | **JWT**(`middleware.JwtAuth(true)`,`routers/uploader.go:13`) | `internal/logic/handler.go:24` | +| HEAD | `/` | 健康检查(`infra.Health`) | 无 | `cmd/main/main.go:36` | + +**声明但未实现/占位的接口**: + +- `internal/logic/fetch.go:1-29` 整个文件被注释(且引用不存在的 `internal/svc`、`internal/types`、`exception`)→ 文件列表/详情等接口**未实现**。 +- `test/fts_test.go` 仍在调用的历史路径**全部没有注册**:`/fts/Transfer/Uploader`(`fts_test.go:26`)、`/fts/Get/Config`(`:87`)、`/fts/Transfer/Prepare`(`:105`)、`/fts/Transfer/FileList`(`:124`)、`/fts/Transfer/Details`(`:151`);`test/oss_provider_req.http:1` 用的是 `/fts/v1/uploader`。这些接口属历史残留,当前进程只注册上表 4 条路由。 + +> 路由前缀存在实现与测试的分歧,见 6.2。 + +## 4. 数据模型与表 + +### 表 `fts_record`(`internal/models/fts_record.go`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `identity` | varchar(36) | uniqueIndex | 记录标识,上传时由 `utils.UUID()`(UUID v7)生成 | +| `created_at`/`updated_at`/`deleted_at` | TIMESTAMP | - | 带软删 | +| `owner_id` | uint | Index | 来自 JWT `claims.ID` | +| `owner_identity` | varchar(36) | Index | 来自 JWT `claims.Identity`,用于生成子目录 | +| `hash` | varchar(255) | not null | 文件 sha256(仅本地/OSS 上传前计算) | +| `name` | varchar(255) | not null | **用户上传的原始文件名**(未做字符过滤) | +| `ext` | varchar(255) | not null | 文件后缀(含点,如 `.png`) | +| `size` | uint64 | default 0 | 文件字节数 | +| `handle_cmd` / `handle_args` | varchar(255) | default '' | 预留"上传后处理命令/参数",**无任何写入点** | +| `save_name` / `local_path` / `oss_path` / `result_url` | varchar(500) | default '' | 落盘文件名 / 本地绝对路径 / OSS 对象键 / 可访问 URL | +| `status` | int8 | default 0, index | 注释定义 -1…5 七种状态,**无状态流转逻辑** | + +> `internal/models/query.go:4-6` 的 `InitData()` 为空函数且无调用点。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /rest/fts/uploader"] --> B["middleware.JwtAuth(true) 校验签名与过期"] + B --> C["middleware.ParseAuth 取 claims"] + C --> D["读取表单 provider / bucket 并 ToLower"] + D --> E["c.FormFile(FtsConfig.InputKey) 解析上传体"] + E --> F["校验 fh.Size <= FtsConfig.MaxSize"] + F --> G["校验 filepath.Ext 在 FtsConfig.Allows 内"] + G --> H["sha256 计算 fileHash"] + H --> I["switch provider"] + I --> J["local: NewSubdir + Join(UploadDir, bucket, subdir) 写盘"] + I --> K["minio: PutObject(bucket, subdir/ULID+ext)"] + J --> L["DBService.Create(fts_record)"] + K --> L + L --> M["infra.Response.Success(record)"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/provider.go:25-33`(关键:`:26`) | **本地存储路径穿越**。`saveDir := filepath.Join(config.Spec.Local.UploadDir, bucket, subdirpath)`,其中 `bucket` 来自请求表单 `c.PostForm("bucket")`(`handler.go:27`),**只做了 `strings.ToLower`,没有字符集/白名单/长度校验**,也没有校验拼接结果仍位于 `UploadDir` 之下。`filepath.Join` 会对 `..` 做 Clean,因此 `bucket=../../../../etc/cron.d` 之类即可把文件写到 `UploadDir` 之外(文件名是 `utils.ULID()+ext`,扩展名受 `Allows` 约束),属任意路径写入。`record.ResultUrl`(`provider.go:71`)同样把未校验的 `bucket` 拼进返回给客户端的 URL。 | +| 中 | `internal/logic/provider.go:88-99` | MinIO 分支的 `bucket` 同样未校验(`handler.go:27` 只 `ToLower`),直接传入 `minioClient.PutObject(ctx, bucket, savePath, ...)`(`provider.go:99`)→ 可用当前凭据写入**任意桶**(跨业务桶污染/覆盖),且 `record.OssPath`/`ResultUrl`(`provider.go:106-108`)记录的攻击者可控桶名会污染数据。 | +| 中 | `internal/logic/provider.go:113-116` | `NewSubdir(identity)` 直接 `identity[0:2]` 切片。入参是 JWT 里的 `claims.Identity`(`handler.go:80`),而 `middleware.JwtAuth(true)` 只校验签名与过期时间、**不校验 `identity` 非空或长度**(`D:\work\bsm-sdk\core\middleware\jwt.go:20-63`)→ 当 token 的 `identity` 为空串或长度 <2 时,`identity[0:2]` 触发 **slice bounds out of range panic**(`LocalUpload` 与 `OssUpload` 都会走到),请求 500 且 gin 仅记录堆栈。 | +| 中 | `internal/logic/handler.go:55-60` | 文件大小限制**滞后**:`c.FormFile`(`:49`)已经把整个 multipart 体解析进内存/临时文件,`:57` 才比对 `file.Size`;`LocalUpload` 还会再次 `fh.Open()` 完整读一遍(`provider.go:48-56`)。配合 `cmd/main/main.go:23` 的 `gin.Default()` 未设置 `MaxMultipartMemory`、也没有 `http.MaxBytesReader`,攻击者可在校验生效前先打满磁盘/内存(配置上限 5GiB,`fts_dev.yaml:33`),`MaxSize` 形同虚设。 | +| 中 | `internal/logic/handler.go:63-68` | 文件类型只按 `filepath.Ext` 后缀白名单判断,**不做 MIME/魔数校验**;`Allows` 含 `.txt`(`fts_dev.yaml:36-37`)意味着可上传任意内容的文本文件,`Local.Site`(`fts_dev.yaml:28`)指向的目录若被前置静态服务直接暴露/解析,存在内容投毒与钓鱼托管风险。同时 `record.Name = fh.Filename`(`handler.go:81`)原样保存用户文件名(含控制字符/超长),无长度与字符过滤。 | +| 低 | `internal/logic/config.go:10` | `GET /rest/fts/config` 匿名返回完整 `FtsConfig`(上传字段名、大小上限、全部允许后缀),并在聚合配置中被显式放行(`pkgs/all/etc/default_dev.yaml:37`)→ 上传策略信息匿名可枚举,便于攻击者按白名单精准构造。 | +| 低 | `D:\work\bsm-sdk\core\infra\response.go:39-52` | `infra.Response.Error` 把 `err.Error()` 原样回给客户端,未做错误分级/脱敏;fts 的错误多为 MinIO、SQL、文件系统错误(如 `provider.go:83-85,101-104`)→ 内部实现细节外泄。且该方法恒以 HTTP 200 返回(`response.go:52`),状态码层面无法区分成败。 | +| 低 | `internal/logic/handler.go:43-47` | `provider` 的白名单校验被整段注释掉(原意图是只允许 `local`)。当前由 `switch provider`(`:88-96`)+ `default` 分支兜底,功能上等价,属**冗余注释**;但注释与代码并存说明该校验曾被有意放开,需确认取舍。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/routers/register.go:12` 与 `internal/routers/register_test.go:15-19` | **路由前缀与自带单测不一致**:`v1_key := path.Join("/rest", srvKey)`,`srvKey = "fts"`(`cmd/main/main.go:15`)→ 实际前缀为 `/rest/fts`(`register.go:12-14`、`uploader.go:11-14`);而 `register_test.go:15-19` 断言的是 `GET /rest/fts/v1/ping`、`GET /rest/fts/v1/config`、`POST /rest/fts/v1/uploader`。该测试**没有 build tag**,`go test ./...` 会以 `route not registered` 失败。旁证倾向于"前缀丢了 `v1` 段":`test/oss_provider_req.http:1` 写 `/fts/v1/uploader`,而聚合配置只放行了 `/rest/fts/ping`、`/rest/fts/config`(`pkgs/all/etc/default_dev.yaml:36-37`,与实现一致)。两侧必有一处需要修正,现状是**测试红、用例错**。 | +| **高** | `internal/logic/handler.go:88-104` | 本地分支先落盘、后落库:`:90` 的 `LocalUpload` 先写文件,`:101` 才 `DBService.Create(&record)`。DB 失败时文件已生成且**没有任何清理**(无 `os.Remove`、无事务)→ 孤儿文件持续堆积;反向的 `OssUpload` 也一样(对象已上传,DB 失败后对象残留)。 | +| 中 | `internal/logic/provider.go:31,43,51,58,65` 与 `internal/logic/handler.go:97-100` | **同一响应被写两次**:`LocalUpload` 内部每个失败分支都调用了 `infra.Response.Error(ctx, err)`(5 处),随后 `handler.go:97-100` 在拿到 error 后又调用一次 → 一个请求写出两份 JSON 响应体,gin 会打印 `headers were already written` 警告,客户端可能读到被截断/拼接的响应。 | +| 中 | `D:\work\bsm-sdk\core\infra\response.go:12,25-34,39-53` | 所有 fts handler 通过**包级共享变量** `var Response Reply` 输出响应,`Success`/`Error` 直接改写该全局结构体的 `Code/Message/Details/Timeseq` 再 `ctx.JSON`。并发上传时两个请求会相互覆盖字段(数据竞争,`go test -race` 可复现),响应内容可能张冠李戴。缺陷在 SDK,但 fts 是本仓库唯一使用方,需在 handler 侧规避或修 SDK。 | +| 低 | `internal/logic/provider.go:71,108` | `ResultUrl` 由字符串拼接生成(`Site + "/" + bucket + "/" + subdir + "/" + fileName`),未做 URL 转义,也未校验 `bucket` 是否含 `/`、`..`;配合 6.1 的桶名未校验问题,可产出畸形或可跳转到其它路径的下载地址。 | +| 低 | `internal/logic/provider.go:99` | MinIO 上传固定 `ContentType: "application/octet-stream"`,与按图片/视频内联展示的常见需求不符(浏览器会触发下载而不是渲染)。 | +| 低 | `internal/logic/provider.go:102,107,109` | 残留调试输出 `fmt.Println("err = ", err)`、`fmt.Println("savePath = ", savePath)`、`fmt.Println("record.ResultUrl = ", record.ResultUrl)`,会写入 stdout 被 supervisor 收集(`etc/supervisor.bsm-apps-fts.conf:8`)。 | +| 低 | `internal/logic/handler.go:85`、`internal/models/fts_record.go:36` | `record.Status = 0` 写死;`fts_record.status` 注释定义了 -1…5 共 7 种状态,`handle_cmd`/`handle_args` 也无写入点 → 状态机与"上传后处理"能力**未实现**,字段仅为预留。 | +| 低 | `internal/logic/provider.go:36,89` | 落盘/对象键用 `utils.ULID() + record.Ext`,扩展名直接取自用户文件名。风险有限(`filepath.Ext` 只返回最后一个 `.` 之后部分),但未做字符过滤。 | + +### 6.3 未完成实现 + +- `internal/logic/fetch.go:1-29`:整文件被注释,文件列表等接口未实现(引用不存在的 `internal/svc`、`internal/types`、`exception` 包)。 +- `internal/errors/errors.go`(118 行)+ `internal/response/response.go`(144 行)**整体未被引用**:全模块对 `internal/errors` 的唯一引用来自 `internal/response/response.go:11`,而后者本身无任何调用点;handler/provider 全部走 SDK 的 `infra.Response`。值得注意的是被弃用的那份实现**反而更正确**——`internal/response/response.go:44-85` 会按错误码返回真实 HTTP 状态(404/401/403/400/429/500),而 SDK 版本恒返回 200。 +- `internal/models/query.go:4-6`:`InitData()` 空函数、无调用点。 +- `internal/models/fts_record.go:48-98`:被注释的 `GetFileList`/`GetFileDetails`/`TableSql`(引用不存在的 `DBService`、`types.Record`)→ 死代码。 +- `cmd/cli/main.go:12-18`:与 fts 无关,是生成 licence 文件的构建脚本,硬编码写入 `/data/app/etc/licence.key`。 +- `test/fts_test.go`(`//go:build integration`):调用 `/fts/Transfer/*`、`/fts/Get/Config` 等**已不存在的接口**,且端口 `12214` 与当前 `Port: 16290`(`fts_dev.yaml:3`)不一致 → 已过期。 +- 未发现显式 TODO 注释。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/config/config.go:40` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`/`MinioOss`/`Local`/`FtsConfig`。其中 `Databases` 缺失时 `with.Databases` 直接 `panic("No Database Source Found !")`(`D:\work\bsm-sdk\core\with\databases.go:13-15`);`Local`/`MinioOss`/`FtsConfig` 缺失时则在 `provider.go:26`(`config.Spec.Local.UploadDir`)、`provider.go:79-81`(`config.Spec.MinioOss.*`)、`handler.go:57,128`(`config.Spec.FtsConfig.*`)空指针 panic——**都是运行期崩溃,且报错不指向配置项**。 | +| 中 | `internal/routers/register.go:20-25` 与 `uploader.go:11-14` | 匿名组与鉴权组用**同一个前缀**在两个文件分别 `engine.Group(v1_key)`,鉴权边界被拆散维护。本次 `/rest/fts` 与测试期望 `/rest/fts/v1` 的分歧正是这种写法的直接后果。 | +| 低 | `internal/impl/impl.go:13-24` | `MemorySerice`(拼写错误,应为 `MemoryService`)与 `RedisService` 初始化后本模块无任何使用点 → 死代码。 | +| 低 | `internal/logic/handler.go:32-37,39-42` | 错误码用 `errcode.NewError(400, ...)`/`errcode.NewError(501, ...)`(`handler.go:40,66`),但 `infra.Response.Error` 恒以 HTTP 200 返回 → 网关/监控无法按 HTTP 状态识别失败,客户端的 4xx 重试逻辑失效。 | +| 低 | `README.md:56-67,154-156,181-189` | README 声明的 `GET /health`、`GET /health/simple`、`GET /version`、`POST /fts/upload`、`GET /fts/download` 全部**未注册**;目录结构里列出的 `internal/health/`、`scripts/`、`build/`、`Dockerfile`、`docker-compose.yml`、`Makefile` 也都不存在 → 文档与实现严重脱节(README.md:210 还写着 MIT 许可证,与仓库实际内部许可不符)。 | +| 低 | `internal/routers/register_test.go`(34 行) | 唯一无 build tag 的单测,且当前**必然失败**(见 6.2),说明 CI 未执行 `go test ./...`(或已长期忽略)。 | +| 低 | `test/fts_test.go:52` | 表单字段名硬编码为 `"file"`,与 `FtsConfig.InputKey`(`fts_dev.yaml:32`)耦合,配置改名后用例静默失效。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| W1 | 高 | `bucket` 未校验 → 本地存储路径穿越(任意路径写入) | 服务器文件系统完整性 | +| W2 | 高 | 路由前缀 `/rest/fts` 与单测期望 `/rest/fts/v1` 矛盾,测试必失败 | 交付质量、路由契约不确定 | +| W3 | 高 | 先落盘后落库、失败不清理 | 磁盘/对象存储泄漏 | +| W4 | 中 | `NewSubdir` 对 `claims.Identity` 切片 → 空 identity 触发 panic | 接口可用性(500) | +| W5 | 中 | 大小限制晚于 `FormFile`,且无请求体上限 | 磁盘/内存耗尽(DoS) | +| W6 | 中 | MinIO `bucket` 未校验 → 可写任意桶 | 跨业务数据污染 | +| W7 | 中 | `LocalUpload` 与 handler 重复写响应 + SDK 全局 `infra.Response` 并发覆盖 | 响应错乱、数据竞争 | +| W8 | 中 | 仅后缀白名单、无 MIME 校验,`Local.Site` 目录或可托管任意文本内容 | 内容投毒 | +| W9 | 中 | 配置项未校验,缺失即运行期 panic | 部署健壮性 | +| W10 | 低 | 262 行死包(`internal/errors`+`internal/response`)、`logic/fetch.go`、注释块、`fmt.Println`、README 失真、过期集成用例 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **封死路径穿越(最高优先)**:`handler.go:26-27` 对 `bucket` 加白名单(如 `^[a-zA-Z0-9_-]{1,64}$`),或改为服务端配置的枚举映射(不接收任意字符串);在 `provider.go:26` 拼出 `saveDir` 后追加一次校验——`filepath.Clean(saveDir)` 必须以 `filepath.Clean(config.Spec.Local.UploadDir)` 为前缀,否则直接报错返回。MinIO 侧同样把 `bucket` 限制在允许列表内(`provider.go:92-99`)。 +2. **修掉 `NewSubdir` 的 panic**:`handler.go:32-37` 解析出 `claims` 后补一条 `claims.Identity == ""` 的校验并返回 401;`provider.go:113-116` 对 `len(identity) < 2` 用固定占位(如 `"00"`)兜底。 +3. **统一路由前缀**:确定 `/rest/fts` 与 `/rest/fts/v1` 哪个是目标值,然后**同时**修正 `register.go:12`(或 `register_test.go:15-19`)、`test/oss_provider_req.http:1`、`pkgs/all/etc/default_dev.yaml:36-37` 的匿名路径,避免三处各写一套。 +4. **上传体前置限流**:`cmd/main/main.go:23` 之后设置 `app.MaxMultipartMemory`;`handler.go:49` 之前用 `http.MaxBytesReader(c.Writer, c.Request.Body, config.Spec.FtsConfig.MaxSize)` 包裹请求体,再调用 `FormFile`。 +5. **响应只写一次**:删除 `provider.go:31,43,51,58,65` 内部的 `infra.Response.Error` 调用,只 `return err`,由 `handler.go:97-100` 统一输出;并把 `infra.Response` 换成 handler 内的局部响应结构(或在 SDK 侧把 `core/infra/response.go:12` 的全局变量改为按请求构造),消除并发覆盖。 +6. **落盘与落库顺序**:把 `handler.go:90` 与 `:101` 调换(先建记录再用其 `identity` 落盘),或保留现顺序但在 `Create` 失败分支 `os.Remove(record.LocalPath)` / `minioClient.RemoveObject(...)` 回滚;顺手去掉 `provider.go:63` 多余的 `os.Chmod`(`OpenFile` 已用 0644)。 +7. **文件类型加固**:在 `handler.go:63-68` 之后用 `http.DetectContentType` 读前 512 字节做魔数校验,与后缀白名单取交集;同时确认 `Local.Site` 指向的目录不参与脚本解析。 +8. **配置校验**:`internal/config/config.go:40` 的 `conf.NotNil` 补上 `Databases`,并在 `New` 中对 `MinioOss`/`Local`/`FtsConfig` 做非空判断(缺失时直接 Fatal 并打印配置项名,优于运行期 panic)。 +9. **清理死代码与文档**:删除 `internal/errors` 与 `internal/response` 两个死包(或只把 `internal/response` 的 HTTP 状态码语义用于 `handler.go` 的输出)、`internal/logic/fetch.go`、`internal/models/fts_record.go:48-98` 的注释块、`internal/impl/impl.go` 未使用的 Redis/Memory 初始化、`internal/models/query.go` 的空 `InitData`、`provider.go:102-109` 的 `fmt.Println`;修正 `README.md:56-67,154-156,181-210` 的接口清单、目录结构与许可证描述;把 `test/fts_test.go` 的 URL/端口更新为 `/rest/fts/uploader` 与 16290(或直接删除过期用例)。 +10. **补测试**:为"`bucket=../../etc` 穿越被拒"、"JWT 无 identity 时上传被拒"、"超过 `MaxSize` 的请求体被拒"、"DB 失败不留下孤儿文件"各写一条用例;并在 CI 中真正执行 `go test ./...`(当前 `register_test.go` 是红的)。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 diff --git a/docs/group.md b/docs/group.md new file mode 100644 index 0000000..cf3fe26 --- /dev/null +++ b/docs/group.md @@ -0,0 +1,244 @@ +# group(群组与成员管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/social/group`(module 路径 `bsm/full/module/social/group`) | +| 服务域 | 社交与关系 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 28 个文件 / 1011 行(含手写别名文件 `pb/blocks_compat.go` 6 行);生成代码 `pb/` 7 个文件 / 6157 行;`proto/` 3 个文件 / 465 行;`etc/` 3 个 yaml + 1 个 supervisor conf;`*_test.go` 0 个;无 `service/`、无 `test/` 目录 | +| 入口 | 仅独立入口 `cmd/main/main.go`(gRPC + grpc-gateway 单进程,`Port: 12247`);**未被** `pkgs/all`、`pkgs/ecmall` 接入;`cmd/cli/main.go` 全文 4 行、`main()` 为空 | +| 对外协议 | gRPC 2 个 service / 13 个 RPC;gateway 路径为 `POST /group.{Service}/{Method}`,但模块内**从未注册任何 gateway handler** | +| 结论摘要 | 13 个接口全部只验 JWT、全部缺群归属/角色校验;3 个核心接口用了不存在的列 `creator_id` 导致必然失败;群信息/群列表/成员列表/入群申请四类查询都是"查了不返回"或"SQL 报错";免验证入群开关恒为 false;`Basic.Disband` 是返回成功的 TODO 占位;成员计数、成员上限、群号、分页全部未落地;网关未接线(`Mux` 恒 nil)、未接入聚合入口,`InitData` 与 `GroupRes` 为死代码。 | + +## 1. 服务定位与职责 + +维护群组基本信息(`group_basic`)、群成员(`relation_group_member`)、入群申请(`relation_group_apply`)与群资源模型(`relation_group_res`,无逻辑),对外提供建群、改群、解散、群列表/搜索、群成员列表,以及入群申请与审批(PASS/REJECT)、踢人、退群、设置管理员。 + +它是群组与成员关系的**读写服务**。群内消息、群文件、群动态(`proto/const.proto:277-304` 里定义了 `GroupPostItem` 等消息但无 service 使用)、好友/关注关系、用户资料(依赖外部表 `passport_extend`)均不在本模块内实现。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("group")` → `impl.NewImpl()` → `server.New(config.Spec.Addr)` → `service.New` → `srv.Use(models.InitData)` → `srv.Start()` | +| `cmd/cli/main.go` | 调试入口,全文 4 行且 `main()` 为空 | +| `internal/config/config.go` | `SrvConfig`(Base、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChatConf、Token、Kyc)与 `conf.NotNil(Service, Cache)` 校验 | +| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 | +| `internal/server/new.go` | 创建 `grpc.Server`,注册 `Basic`、`Member` 两个 service(`protoc-gen-slc` 生成);**未创建 `ServeMux`** | +| `internal/server/{basic_server,member_server}.go` | 13 个 RPC 的 1:1 转发(生成代码) | +| `internal/logic/basic/{search,fetch,get,create,modify,disband,const}.go` | 群组搜索/列表/详情/创建/修改/解散 | +| `internal/logic/member/{fetch,do_join,join_fetch,join_do_handle,do_kick,do_quit,do_set_manager,const}.go` | 成员列表、入群申请、审批、踢人、退群、管理员设置 | +| `internal/models/{group_basic,group_member,group_apply,group_res,query}.go` | 4 张表模型 + `query.go` 中全部数据访问(含 4 段裸 SQL) | +| `pb/*.go` | 生成代码;`pb/blocks_compat.go` 为手写类型别名(`PostItem = GroupPostItem` 等) | +| `proto/{group,member,const}.proto` | 契约;公共消息放在 `proto/const.proto`(`package blocks`,与 service 的 `package group` 不同) | +| `etc/{group_dev,group_prod,group_test}.yaml` + `supervisor.bsm-social-group.conf` | 配置与进程托管;三份 yaml 内容完全相同 | + +依赖注入:本模块没有 `service/` 目录,也没有 `Expose/Dependencies` 注入点,`impl.NewImpl()` 是唯一初始化路径(`internal/impl/impl.go:19-25`)。全仓库对 `module/social/group` 的引用只有 `go.work:20` 与 `scripts/api-docgen`。 + +## 3. 接口清单 + +HTTP 路径由生成代码固定为 `/group.{服务}/{方法}`(`pb/group.pb.gw.go:472-477`、`pb/member.pb.gw.go:536-542`),全部为 `POST`。**这些 handler 在模块内从未被注册到任何 `ServeMux`**(见 6.4)。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/group.Basic/Search` | 搜索群组 | JWT(丢弃 claims) | **空实现** `logic/basic/search.go:12` | +| POST | `/group.Basic/Fetch` | 我的群组列表 | JWT | `logic/basic/fetch.go:14`(只返回总数,见 6.2) | +| POST | `/group.Basic/Get` | 群组信息 | JWT(丢弃 claims) | `logic/basic/get.go:14`(返回值被丢弃,见 6.2) | +| POST | `/group.Basic/Create` | 创建群组 | JWT | `logic/basic/create.go:17` | +| POST | `/group.Basic/Modify` | 修改群组信息 | JWT(按创建者,列名错误) | `logic/basic/modify.go:18` | +| POST | `/group.Basic/Disband` | 解散群组 | JWT(丢弃 claims) | **TODO 占位但返回成功** `logic/basic/disband.go:14` | +| POST | `/group.Member/Fetch` | 群成员列表 | JWT(丢弃 claims) | `logic/member/fetch.go:14`(只返回总数,见 6.2) | +| POST | `/group.Member/DoJoin` | 申请加群 | JWT | `logic/member/do_join.go:17` | +| POST | `/group.Member/JoinFetch` | 申请加群列表 | JWT | `logic/member/join_fetch.go:15`(SQL 不可用,见 6.2) | +| POST | `/group.Member/JoinDoHandle` | 申请加群处理 | JWT(**无角色校验**、丢弃 claims) | `logic/member/join_do_handle.go:18` | +| POST | `/group.Member/DoSetManager` | 设置/取消管理员 | JWT(按创建者,列名错误) | `logic/member/do_set_manager.go:18` | +| POST | `/group.Member/DoKick` | 踢人 | JWT(权限条件错误) | `logic/member/do_kick.go:17` | +| POST | `/group.Member/DoQuit` | 退群 | JWT(按创建者,列名错误) | `logic/member/do_quit.go:17` | + +> 鉴权方式:13 个 logic 入口全部调用 `service.ParseMetaCtx(ctx, nil)`(`bsm-sdk/core/service/meta.go:19-49`),要求有效 JWT;`opts` 均为 `nil`,即**不校验角色、不校验私有 IP**。`Search`、`Get`、`Disband`、`Member.Fetch`、`JoinDoHandle` 用 `_, err = ...` 丢弃 claims(`search.go:13`、`get.go:16`、`disband.go:16`、`member/fetch.go:15`、`join_do_handle.go:19`),后续无法做任何归属判断。 + +### 声明但未实现 / 占位接口 + +| 类别 | 接口 | 证据 | +| --- | --- | --- | +| TODO 占位但返回 `vars.OK` | `Basic.Disband` | `internal/logic/basic/disband.go:26-31`(`// TODO: add your logic code & delete this line.`) | +| 只有参数校验、无任何查询 | `Basic.Search` | `internal/logic/basic/search.go:17-21`(校验 keyword 后直接 `return &pb.GroupsReply{}, nil`) | +| 有实现但结果被丢弃 | `Basic.Get` | `internal/logic/basic/get.go:20-25`(第 25 行返回新的空 `&pb.GroupItem{}`) | +| 有实现但结果未装配 | `Basic.Fetch` | `internal/logic/basic/fetch.go:20-29`(`Groups` 字段从未赋值) | +| 有实现但结果未装配 | `Member.Fetch` | `internal/logic/member/fetch.go:24-33`(`Members` 字段从未赋值) | +| 模型存在、全模块零引用 | `GroupRes`(群文件/资源) | `internal/models/group_res.go:8-21`(全仓库无 `GroupRes` 读写调用) | +| 空初始化被注册 | `models.InitData` | `internal/models/query.go:9-12` + `cmd/main/main.go:38` | + +## 4. 数据模型与表 + +模型未注册任何自动迁移:`database.AppendMigrate` / `AutoMigrate` 在 `module/social/**` 内**无匹配**(`grep AppendMigrate module/social` 返回空),4 张表依赖外部建表脚本,仓库内没有对应 SQL。公共字段来自 SDK(`bsm-sdk/core/types/db.go`):`types.Std_IICUDS` = `id`(PK) / `identity varchar(36) uniqueIndex` / `created_at` / `updated_at` / `deleted_at`(index) / `status int8 default 0 index`;`types.Std_Passport` = `passport_id uint index` / `passport_identity varchar(36) index`。 + +### 表 `group_basic`(`internal/models/group_basic.go:8-30`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `identity` | varchar(36) | uniqueIndex | 群组唯一码(`utils.UUID()` 生成,UUIDv7 字符串) | +| `created_at`/`updated_at`/`deleted_at` | TIMESTAMP | index(deleted_at) | 软删除 | +| `status` | int8 | index,默认 0 | 状态,**全模块查询均未过滤** | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 创建者;**注意:`modify.go:35`、`do_quit.go:24`、`do_set_manager.go:25` 用的是不存在的 `creator_id`** | +| `number` | int64 | not null | 群号,`create.go` 从未生成(恒 0) | +| `avatar` / `name` / `introduce` / `notice` / `background` | varchar(255)/text | 默认 `''`,`name` not null | 群资料 | +| `member_limit` | smallint | 默认 100 | 最大成员数,0 表示不限;仅创建时写死 500,**从未参与校验** | +| `province`/`city`/`area` | int32 | 默认 0 | 地区,无逻辑写入 | +| `enable_any_join` | bool | - | 免验证入群;创建时置 true,判定逻辑恒为 false(见 6.2) | +| `enable_search_by_number` / `enable_search_by_name` | bool | - | 搜索开关,创建时未写入(恒 false) | +| `member_total` | int32 | 默认 0 | 成员数;创建时写 1,之后靠 `UpsetGroupMemberTotal` 加减 | + +### 表 `relation_group_member`(`internal/models/group_member.go:8-20`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 成员记录自身标识;`member/do_join.go:33`、`member/join_do_handle.go:39` 用 `utils.UUID()` 写入,但 **`basic/create.go:42-51` 建群时的创建者成员记录没有赋值**(见 6.2) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | 软删除;`status` 未使用 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 成员 | +| `group_id` | uint | not null | 群组主键(标签写作裸名 `group_id`,依赖 GORM 默认蛇形命名,结果与显式声明一致) | +| `group_identity` | varchar(36) | not null | 群组唯一码(**权限查询里被误用为 `identity`,见 6.2**) | +| `remark_name` | varchar(255) | 默认 `''` | 群内昵称,无任何写入点 | +| `role` | smallint | 默认 0 | 0 普通 / 1 管理员 / 2 超管 / 10 创建者(`logic/member/const.go:3-11`) | + +> 表上**没有** `(group_identity, passport_id)` 唯一约束 → 重复入群无法由数据库兜底。 + +### 表 `relation_group_apply`(`internal/models/group_apply.go:8-21`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 申请记录标识(`do_join.go:47`) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | `status`:1 通过 / -1 拒绝(`join_do_handle.go:49,55`),**查询未按状态过滤** | +| `from_id` / `from_identity` | uint / varchar(36) | not null | 申请人 | +| `group_id` / `group_identity` | uint / varchar(36) | not null | 目标群(`group_id` 来自请求 `in.Id`,未校验与 `identity` 是否对应) | +| `message` | varchar(255) | 默认 `''` | 加群留言 | + +### 表 `relation_group_res`(`internal/models/group_res.go:8-21`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 主键与唯一码 | +| `group_id` | varchar(50) | not null | 注意这里是**字符串**,与 `relation_group_member.group_id`(uint)类型不一致 | +| `name` / `introduce` / `url` / `download_num` | - | - | 群文件信息 | + +> 该表无任何读写逻辑,属未启用模型。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /group.Member/DoJoin
ParseMetaCtx 取 auth"] --> B["GetGroupEnableAnyJoin
列名 createor_id 不存在 且错误被丢弃
恒返回 false"] + B -->|"恒走此分支"| C["写 relation_group_apply
无重复校验 无 group 存在性校验"] + D["POST /group.Member/JoinDoHandle
ParseMetaCtx 丢弃 claims"] --> E{"direction"} + E -->|"PASS"| F["按 identity 查申请表"] + F --> G["Create relation_group_member
无幂等 无 MemberLimit 判断"] + G --> H["UpdateColumn status=1
不检查影响行数"] + H --> I["UpsetGroupMemberTotal +1
不检查影响行数"] + E -->|"REJECT"| J["UpdateColumn status=-1
错误未赋给 err 失败也返回成功"] + K["POST /group.Member/DoKick"] --> L["权限查询 identity=群组identity
且 passport_id 传字符串
必然报错"] + M["POST /group.Member/DoQuit"] --> N["Select creator_id
列不存在 必然报错"] + O["POST /group.Basic/Fetch"] --> P["GetMyGroup 裸 SQL
表名 group / group_member 不存在"] + Q["POST /group.Basic/Get"] --> R["First into reply 后
返回新的空 GroupItem"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/member/join_do_handle.go:19,24-52` | **入群审批无任何角色校验**。入口 `_, err = service.ParseMetaCtx(ctx, nil)` 丢弃调用者身份,PASS/REJECT 分支不判断"调用者是否该群管理员/创建者"(对比 `do_set_manager.go:30` 的创建者校验、`do_kick.go:22-30` 的意图)→ 任意登录用户提交 `apply_identity` 就能把任意申请人拉进任意群(PASS)、或拒绝任意申请(REJECT),并可借重复 PASS 膨胀 `member_total`。 | +| **高** | `internal/logic/member/do_kick.go:24` | **踢人权限判定双重错误**:`Where("identity=? and role !=0 and passport_id=?", in.GroupIdentity, auth.Identity)`。① `identity` 是成员记录自身 ULID(`member/do_join.go:33`、`member/join_do_handle.go:39` 写入;`basic/create.go:42-51` 漏写),群标识列是 `group_identity`(`group_member.go:11-12`),条件语义错误;② `passport_id` 是整型列(`bsm-sdk/core/types/db.go:52`),却传入 36 位 UUID 字符串(`bsm-sdk/core/utils/identity.go:8-10`)→ Postgres 类型错误。当前表现为接口恒失败(见 6.2);一旦按列名"修好",该条件退化为"任意管理员可踢任意人",且代码中**没有禁止踢创建者/超管**的保护。 | +| 中 | `internal/logic/member/do_set_manager.go:37,40` | 设置/取消管理员只用 `Where("identity=?", in.Identity)` 定位成员,未限定 `group_identity`,也不校验目标是否属于该群 → 修正列名后即成为跨群改角色入口;同时没有拦截"把创建者 `ROLE_CREATEOR=10` 改写为 1"。 | +| 中 | `internal/logic/member/fetch.go:15-24` + `internal/models/query.go:30-37` | 群成员列表只校验 JWT,**不校验调用者是否为该群成员**,按 `group_identity` 返回全量成员资料(identity/昵称/头像/性别/省市/签名)→ 任意登录用户可枚举任意群成员。 | +| 低 | `internal/logic/member/do_join.go:26-50` | 入群申请无频率/数量限制,且不校验 `in.Id`/`in.Identity` 是否对应真实存在的群 → 可批量灌入 `relation_group_apply` 垃圾申请(当前因 `JoinFetch` 不可用而无法被管理员看到)。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/basic/disband.go:26-31` | **解散群组是 TODO 占位却返回成功**:`// TODO: add your logic code & delete this line.` 之后直接 `return &pb.DataStatusReply{Data: vars.OK, ...}` → 调用方收到成功,群与成员数据原样保留。 | +| **高** | `internal/logic/basic/get.go:20-25` | **群信息恒为空**:`First(&reply)` 已把记录读入命名返回值,第 25 行又 `return &pb.GroupItem{}, nil` 丢弃;且"记录不存在"(`gorm.ErrRecordNotFound`)被当成 `ErrDB`。 | +| **高** | `internal/logic/basic/fetch.go:20-29` | **我的群列表恒为空**:`GetMyGroup` 的结果 `groups` 只用于 `Total: int32(len(groups))`,`Groups` 字段从未赋值 → 列表空,总数也恒为 0。 | +| **高** | `internal/logic/basic/modify.go:33-35` | **改群信息会重写群 identity**:`record.Identity = utils.UUID()` 之后 `UpdateColumns(record)`。`UpdateColumns` 对结构体**不做零值过滤**、只跳过主键,因此 `identity` 会被换成新的 UUID,未传的 `name/introduce/avatar/background/notice/enable_search_*` 一律写空/false;`relation_group_member.group_identity`、`relation_group_apply.group_identity` 立刻与新 identity 脱钩(群成员、申请全部"消失")。 | +| **高** | `internal/logic/basic/modify.go:35`、`internal/logic/member/do_quit.go:24`、`internal/logic/member/do_set_manager.go:25` | **使用不存在的列 `creator_id`**:`GroupBasic` 的创建者列是 `passport_id`(`group_basic.go:10` 内嵌 `Std_Passport`,`bsm-sdk/core/types/db.go:52-55`),模型内无 `creator_id` → `WHERE creator_id=?` / `SELECT creator_id` 在 Postgres 直接报错 → **修改群信息、退群、设置管理员三个接口恒返回 `ErrDB`**。 | +| **高** | `internal/models/query.go:39-43` + `internal/logic/member/do_join.go:26` | **"免验证入群"判定恒为 false**:`GetGroupEnableAnyJoin` 写作 `Where("createor_id=? and identity=?", passport_id, group_identity)` —— `createor_id` 列不存在,条件语义也从"群是否开放"变成"申请人是否为群主",且第 41 行**没有检查 `.Scan(&is)` 的错误**、直接返回零值 false → `DoJoin` 永远走"写申请表"分支,`GroupBasic.EnableAnyJoin`(创建时置 true)完全失效。 | +| **高** | `internal/logic/member/join_do_handle.go:24-56` | **审批无幂等、错误被吞、方向未识别即成功**:同一 `apply_identity` 可反复 PASS,每次都 `Create` 新的 `relation_group_member` 并把 `member_total` 再 +1(`:43-52`),申请表没有任何"已处理"判断;REJECT 分支第 55 行 `UpdateColumn` 的返回值未赋给 `err`,第 58 行的 `if err != nil` 检查的是旧值 → **失败也返回成功**;`direction` 既非 PASS 也非 REJECT 时直接落到函数末尾返回成功(`:56`)。 | +| **高** | `internal/logic/member/do_join.go:26-50` | **入群无任何幂等与上限校验**:不查成员表是否已存在、不查申请表中是否已有待处理申请,同一用户可生成多条成员/申请记录;`MemberLimit`/`MemberTotal` 从未参与判断(`MemberLimit` 只在创建时写死 500)。 | +| **高** | `internal/models/query.go:16-20`、`:30-37` | **裸 SQL 表名与模型不一致**:模型声明 `group_basic`(`group_basic.go:29`)与 `relation_group_member`(`group_member.go:19`),SQL 却写 `From group_member as gm Left join group on ...` 和 `From group_member as gm Left join passport_extend ...` → 表不存在;且 `group` 是 Postgres 保留字,即使存在也必须引号化。两个列表接口(`Basic.Fetch`、`Member.Fetch`)必然 SQL 报错。 | +| **高** | `internal/logic/member/join_fetch.go:24` | **`Where("group_id in ", group_ids)` 缺少占位符**(正确写法 `group_id in ?`)。该表达式不含 `?`,GORM 只能把整个 `[]uint` 当作单个参数追加(`gorm.io/gorm@v1.31.2/clause/expression.go:53-57`)→ 无法构成 IN 列表,条件无效或直接报错。 | +| **高** | `internal/logic/member/join_fetch.go:21-38` | **申请列表的 dest 类型与结果集不匹配**:`Find(&fetch)` 的目标是 `[]*pb.ApplyJoinGroupItem`(字段 `identity/group_id/from/message/created_at/status`,`proto/member.proto:74-81`),而 `relation_group_apply` 只有 `from_id/from_identity`,GORM 会拼出 `SELECT ... "from" ...` → 列不存在报错。即便查询成功,第 31 行还用 `item.From.Identity`(恒为空串,因为 `from` 列从未映射)去查 `passport_extend`,`ErrRecordNotFound` 被当成 `ErrDB`(`:32-35`)→ **只要有一条申请,整个列表就失败**。 | +| 中 | `internal/logic/member/join_fetch.go:29-36` | `card` 在 for 循环外只分配一次,`fetch[idx].From = card` 让所有元素**共用同一指针** → 即使查询成功,全部申请都会显示"最后一个被查到的人"的资料卡。 | +| 中 | `internal/models/query.go:45-47` + `do_quit.go:37-41`、`do_kick.go:36-39` | **群成员计数没有一致性保障**:`UpsetGroupMemberTotal` 不返回错误、不检查影响行数;删除成员(软删除)时 `RowsAffected=0` 也照样 `-1`(非成员调用 `DoQuit` 会让计数长期偏小甚至为负),而 PASS 分支重复审批会重复 `+1`。 | +| 中 | `internal/logic/member/fetch.go:31-33` | 群成员列表只填 `Total: int32(len(members))`,`Members` 字段从未赋值 → 成员列表恒为空。 | +| **高** | `internal/logic/basic/create.go:42-51` | **创建者成员记录漏写 `identity`**。`member` 只赋了 `GroupID/GroupIdentity/Role` 与 `PassportID/PassportIdentity`,没有 `member.Identity = utils.UUID()`(对比 `member/do_join.go:33`、`member/join_do_handle.go:39`)。而 `GroupMember.Identity` 来自 `types.Std_IICUDS`、标签是 `uniqueIndex`(`bsm-sdk/core/types/db.go:28-35`)→ 若表结构按模型建立,第一条成员记录以 `identity=''` 落库后,**任何人再建群都会因唯一键冲突失败**(`Create` 返回 `ErrDB`);若线上表没有该索引,则这些成员记录的 `identity` 全是空串,`DoKick` 里按 `identity` 定位成员的逻辑永远无法正确命中。 | +| 中 | `internal/logic/basic/create.go:36-55` | **建群无事务**:群记录写入成功而成员记录失败时,群已存在且 `MemberTotal=1` 却没有任何成员;`Number`(群号)与 `EnableSearchByNumber/Name` 从未从请求写入(恒 0/false),`in.MemberLimit` 被忽略(第 28 行写死 `DefaultMemberLimit`)。 | +| 中 | 全模块 | **没有任何分页与上限**:`Basic.Fetch` 拉全部群、`Member.Fetch` 拉全部成员、`JoinFetch` 拉全部申请,`proto/const.proto:10-14` 的 `FetchRequest.page_no/page_size` 从未被任何 logic 使用 → 大群/多申请一次性全量返回。 | +| 低 | `internal/logic/basic/fetch.go:28` | `Total: int32(len(groups))` 与真实总数脱钩(`len` 恒 0),且未做 `int`→`int32` 溢出保护。 | +| 低 | `internal/logic/basic/create.go:59` | 时间戳用 `UnixNano`,其余接口统一 `UnixMilli`(如 `modify.go:43`)→ 同一字段单位不一致。 | +| 低 | `internal/logic/member/do_join.go:23,58` | 返回的 `Data` 是第 23 行新生成的 UUID,既没落库也不代表任何记录,调用方拿到的"标识"无效。 | + +### 6.3 未完成实现 + +| 类别 | 内容 | 证据 | +| --- | --- | --- | +| TODO 占位(且返回成功) | `Basic.Disband` | `internal/logic/basic/disband.go:26-31` | +| 空实现(无 TODO 标注) | `Basic.Search` | `internal/logic/basic/search.go:17-21` | +| 空实现 | `models.InitData`(只有 `return nil`),却被 `srv.Use` 注册 | `internal/models/query.go:9-12` + `cmd/main/main.go:38` | +| 模型存在、零逻辑 | `GroupRes` 群文件表 | `internal/models/group_res.go:8-21` | +| gateway 未接线 | `Server.Mux` 声明后从未赋值(`internal/server/new.go:20-25` 只建 gRPC),`pb.RegisterBasicHandlerServer` / `pb.RegisterMemberHandlerServer` 在仓库内**无调用点** | `internal/server/new.go:20-33`、`cmd/main/main.go:33`;`grep RegisterBasicHandlerServer module/social/group` 仅命中生成文件 | +| 未接入聚合入口 | `pkgs/all/internal/service/service.go:18-32`(15 项)与 `pkgs/ecmall/internal/service/service.go:18-32`(14 项)均无 `social/*`;本模块无 `service/` 目录,没有 `Expose/Dependencies` | `pkgs/all/internal/service/service.go:18-33` | +| 无建表手段 | `database.AppendMigrate`/`AutoMigrate` 在 `module/social/**` 无匹配 → 4 张表依赖外部 SQL,仓库内无脚本 | `grep AppendMigrate module/social` 无结果 | +| CLI 空实现 | `cmd/cli/main.go` 全文 4 行,`main()` 为空 | `cmd/cli/main.go:1-4` | +| README 空壳 | 全文只有标题 | `README.md:1-2` | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/server/new.go:20-25` + `cmd/main/main.go:33` | `Server.Mux`(`new.go:16`)声明后从未赋值,`GatewayMux: s.Mux` 传给 SDK 的是**类型非空、值为 nil** 的 `*runtime.ServeMux`;`service.Start` 用它直接 `http.ListenAndServe`(`bsm-sdk/core/service/service.go:106-111,120-129`),请求到达时 `runtime.ServeMux.ServeHTTP` 会读取自身字段(`grpc-gateway/v2@v2.30.0/runtime/mux.go:407-437`)→ **nil 接收者 panic**(net/http 按连接 recover,客户端表现为连接中断)→ `Gateway.Enable: true` 得到的 HTTP 侧完全不可用。 | +| 中 | `internal/config/config.go:56` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`,而 `with.Databases` 在 `cfg == nil` 时 `panic("No Database Source Found !")`(`bsm-sdk/core/with/databases.go:12-15`)→ 配置漏配即启动 panic。当前 `etc/group_dev.yaml:4-7` 有 `Databases` 段,可正常初始化。 | +| 中 | `etc/group_dev.yaml`、`etc/group_prod.yaml`、`etc/group_test.yaml` | 三份配置**逐行相同**(`Service: group`、`Port: 12247`、同一个 `127.0.0.1` + `dbname=bsm_dev` 连接串、相同 `Gateway.Port: 12425`)→ dev/prod/test 无法区分;数据库口令以明文形式入库(`password=CHANGE_ME`)。 | +| 中 | `etc/group_dev.yaml:13-16` + `internal/impl/impl.go:24` | `MicroService.Anonymous` 写的是 `passport.ping.hello`,而本服务只有 `Basic`/`Member` 两个 service、不存在 `ping` 方法(`internal/server/new.go:28-29`)→ 匿名清单无效;配置中**没有 `Etcd` 段**,`with.Etcd(nil)` 直接返回 nil(`bsm-sdk/core/with/etcd.go:14-17`),一旦把 `MicroService.Enable` 改为 true,`service.Start` 会因 `EtcdClient == nil` 执行 `os.Exit(1)`(`bsm-sdk/core/service/service.go:66-70`)。 | +| 低 | `internal/impl/impl.go:21-22` | `MemorySerice`、`RedisService` 初始化后全模块零使用(死代码);`EtcdService` 只出现在被跳过的注册路径。 | +| 低 | `internal/logic/basic/const.go:13-15`、`internal/logic/member/const.go:3-15` | 两个包重复定义同一组 `ROLE_*`、`DefaultMemberLimit`、`DefaultEnableAnyJoin`;`basic` 包的 `filed` 变量没有任何使用点(`member` 包的同名变量用于 `join_fetch.go:31`)。 | +| 低 | `internal/logic/basic/get.go:20` | 把 `**pb.GroupItem`(`&reply`)交给 GORM 当 dest,依赖 SDK 对双重指针的隐式分配(`gorm.io/gorm@v1.31.2/callbacks.go:121-133`),语义正确但极不直观,也正是第 25 行改写返回值后不易察觉的原因。 | +| 低 | 全模块 | 0 个 `*_test.go`;`test/` 目录不存在,无任何请求样例;README 只有标题。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| G1 | 高 | 入群审批无角色校验,任意登录用户可拉人进群/拒绝申请 | 越权、群成员被污染 | +| G2 | 高 | `Disband` 为 TODO 占位却返回成功 | 功能不可用、误导调用方 | +| G3 | 高 | `Basic.Get`/`Basic.Fetch` 丢弃查询结果 | 功能不可用 | +| G4 | 高 | `Modify` 重写群 `identity` + `UpdateColumns` 零值覆盖 | 关联数据断裂、群资料被清空 | +| G5 | 高 | `creator_id` 列不存在 → Modify/DoQuit/DoSetManager 恒失败 | 功能不可用 | +| G6 | 高 | `GetGroupEnableAnyJoin` 列名错+语义错+吞错误 | 免验证入群永久失效 | +| G7 | 高 | `JoinDoHandle` 无幂等、错误被吞、未知 direction 返回成功 | 重复成员、计数膨胀、假成功 | +| G8 | 高 | 入群无幂等与成员上限校验 | 数据污染、绕过群容量 | +| G9 | 高 | 裸 SQL 表名(`group`/`group_member`)与模型不一致 | 两个列表接口必然报错 | +| G10 | 高 | `join_fetch` IN 缺占位符 + dest 结构不匹配 + `From` 指针复用 | 申请列表不可用 | +| G11 | 高 | `DoKick` 权限条件列名/类型双错(修正后无角色保护) | 功能不可用、权限模型缺失 | +| G12 | 中 | 成员计数无一致性保障(不检查影响行数) | 群成员数长期失真 | +| G13 | 中 | 无任何分页与上限 | 资源滥用、超大响应 | +| G14 | 中 | 建群无事务,群号/搜索开关/成员上限未落库 | 半成品数据 | +| G15 | 中 | gateway 未接线、`Mux` 为 nil、无 `service.Expose` | HTTP 不可用、无法被聚合 | +| G16 | 中 | `Databases` 未校验、三份配置同内容、缺 `Etcd` 段 | 部署脆弱、环境不分 | +| G17 | 低 | 死代码(`GroupRes`、`InitData`、`filed`、Redis/Memory)、重复常量、无测试、README 空壳 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **补权限判定**(G1/G11):`JoinDoHandle`、`DoKick`、`DoSetManager` 先取回 claims(不要写成 `_`),再按 `group_identity + passport_id` 查一条 `relation_group_member` 拿到 `role`,据此判断"是否管理员/创建者",并禁止对 `role >= 操作者 role` 的成员执行踢人/改角色;`DoQuit` 保留"创建者不能退群"。 +2. **修列名与 SQL**(G3/G5/G6/G9/G10):`creator_id`/`createor_id` 统一改为 `passport_id`;`GetMyGroup`/`GetGroupMember` 的 `group`/`group_member` 改为 `group_basic`/`relation_group_member`;`GetGroupEnableAnyJoin` 改为 `Where("identity=?", group_identity).Select("enable_any_join")` 并检查错误;`join_fetch.go:24` 改为 `Where("group_id in ?", group_ids)`。 +3. **修返回值装配**(G3):`Get` 直接 `return reply, nil`(`ErrRecordNotFound` 单独返回"群不存在"错误码);`Basic.Fetch`/`Member.Fetch` 把查到的切片填进 `Groups`/`Members` 并同步 `Total`。 +4. **修 `Modify`**(G4):删除 `record.Identity = utils.UUID()`;改用 `Updates(map[string]any{...})` 或 `Select("name","introduce",...)` 只更新请求中实际提供的字段;`Where` 改为 `identity=? and passport_id=?`。 +5. **补幂等与上限**(G7/G8/G12):PASS 前查申请 `status` 与成员表是否已存在,已处理直接返回;入群前用 `MemberTotal < MemberLimit`(0 视为不限)拦截;成员增删后按 `Delete/Create` 的 `RowsAffected` 决定是否 `±1`,并把 `UpsetGroupMemberTotal` 改为返回 `error` 由调用方检查。 +6. **修申请列表**(G10):改为先 `Find(&[]models.GroupApply{})` 再组装 pb;`card` 在循环内 `new`;申请人资料查不到时跳过该条而不是整单失败;如只展示待处理,补 `status = 0` 条件。 +7. **补事务与字段落库**(G14):建群的两条 `Create` 放进 `impl.DBService.Transaction`;把 `Number`(群号生成规则)、`EnableSearchByNumber/Name`、`MemberLimit` 从请求写入。 +8. **补分页**(G13):为 `Member.Fetch`/`JoinFetch` 接上 `FetchRequest` 的 `page_no/page_size`(并设默认值与上限),`Basic.Fetch` 按产品需要决定是否分页。 +9. **接线与配置**(G15/G16):`internal/server/new.go` 中 `Mux: gwRuntime.NewServeMux()` 并调用 `pb.RegisterBasicHandlerServer`/`pb.RegisterMemberHandlerServer`;或在 `service/` 下补与 wallet 同构的 `Expose` 以便接入 `pkgs/all`;`config.New` 补 `conf.NotNil(Spec.Databases)`;三份 yaml 区分环境与口令来源,`Anonymous` 改为真实存在的方法或删除,若要启用微服务注册需补 `Etcd` 段。 +10. **清理与测试**(G17):删除 `basic/const.go` 未使用的 `filed`、`impl.go` 中未使用的 Redis/Memory 初始化、`models/query.go:9-12` 的空 `InitData`(或从 `cmd/main/main.go:38` 移除注册);为 `DoJoin`/`JoinDoHandle`/`DoQuit`/`Modify` 各补一条正例 + 一条边界用例(当前 0 测试)。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。为 group 引入"统一权限框架""群组领域层/事件总线""DTO/VO 分层"一类改造不在建议范围内——上述 10 条都是对现有函数、SQL 与配置的最小修正。 diff --git a/docs/initial.md b/docs/initial.md new file mode 100644 index 0000000..68b9bb4 --- /dev/null +++ b/docs/initial.md @@ -0,0 +1,222 @@ +# initial(客户端初始化与基础字典)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/initial` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件 27 个、1066 行(含 `test/grpc/main.go` 226 行非 `_test.go` 的 main 程序、`internal/models/query.go` 1 行空文件);`proto` 3 个(`check.proto` 67 行、`data.proto` 78 行、`const.proto` 313 行);`pb/` 生成文件按 `check`/`data`/`const` 各 3~4 个 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程);聚合入口 `pkgs/all`(`pkgs/all/internal/service/initial.go:9-20`) | +| 对外协议 | gRPC + REST Gateway,REST 路径 `/initial.Check/{Hello,Config,Updates}`、`/initial.Data/{Country,Areas,Datas}` | +| 结论摘要 | 6 个只读接口共 1066 行代码,无写接口。**版本更新判断用字符串不等比较**(`check/updates.go:39`),客户端版本比服务端新时会"升级"到旧版本(强制降级),且无任何语义版本/白名单(平台/架构/最低版本)约束;**`check/config_cache.go:21` 把请求参数 `os` 直接拼接进 `ORDER BY`,构成 SQL 注入**;`data/areas_cache.go:33-35` 查询了模型上根本不存在的 `enabled`/`show_town`/`sort_order` 三列,`Areas` 接口在仓库给定的表结构下必然查库失败;所有 6 个接口在模块 yaml 中被登记为匿名(`etc/initial_dev.yaml:15-21`),而聚合部署白名单(`pkgs/all/etc/default_dev.yaml:24-43`)未放行其中任何一条,且模块 yaml 的匿名条目写法与框架实际匹配格式不一致,两边都落空。 | + +## 1. 服务定位与职责 + +面向客户端启动阶段的基础数据服务,提供三类能力: + +1. **配置下发**(`Check.Config`):按 `app + os` 返回配置键值列表,支持 `os = ALL` 的全平台兜底。 +2. **版本更新检查**(`Check.Updates`):按 `app + os + arch` 取"最新"一条版本记录,与客户端上报版本比对,返回更新包信息。 +3. **基础字典**(`Data.Country` / `Data.Areas` / `Data.Datas`):国家列表、行政区划(国家→省→市→区县,可选乡镇)、系统标签字典。 + +不承担字典的增删改,不负责安装包分发(只返回 `files` 字段),`Check.Hello` 为健康检查。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口。`ServiceKey = "Initial"`(`:15`,大写);`config.New` → `impl.NewImpl` → `server.New(nil)`(`:25`)→ `service.New(...)` + `srv.Start()`(`:27-43`) | +| `cmd/cli/main.go` | 11 行占位,仅 `log.Println("Hello Cli")`(`:8-9`) | +| `internal/config/config.go` | `SrvConfig`(`Base`/`Databases`/`MicroService`/`Rpc`/`Gateway`/`APM`/`Etcd`);`New` 做端口/IP 校验、`conf.NotNil(Spec.Service, Spec.Cache)`(`:36`) | +| `internal/impl/impl.go` | 初始化 `MemorySerice`/`RedisService`/`DBService`/`EtcdService`(`:20-29`);`MemorySerice` 拼写错误(`:16,22`) | +| `internal/logic/check/hello.go` | `Hello` 健康检查,只回显 `code`(`:12-32`),无依赖探活 | +| `internal/logic/check/config.go` | `Config`:缓存取配置 + 内存去重 + 转 pb(`:13-55`) | +| `internal/logic/check/config_cache.go` | `GetConfigByCache`:Redis → DB(`app + (os OR ALL)`)→ 回写缓存(`:10-29`) | +| `internal/logic/check/updates.go` | `Updates`:取最新一条 `initial_apps` 记录并比较版本(`:14-55`) | +| `internal/logic/data/country.go` / `country_cache.go` | `Country` 查询 + 缓存(`enabled = true`,`sort_order ASC, name ASC`) | +| `internal/logic/data/areas.go` / `areas_cache.go` | `Areas` 查询 + 缓存,默认国家 1(中国)(`areas.go:15-17`) | +| `internal/logic/data/datas.go` / `datas_cache.go` | `Datas` 查询 + 缓存,并在结果前拼接一条硬编码"全部"项(`datas.go:23-31`) | +| `internal/models/*.go` | 5 个表模型 + `database.AppendMigrate` 注册;`query.go:1` 只有 `package models`(空文件) | +| `internal/server/{new,check_server,data_server}.go` | 注册 `Check`/`Data` 到 gRPC;独立运行时开 `reflection`(`new.go:36-38`) | +| `internal/excode/ex.go` | 5 个"参数错误"错误码,**全模块无引用**(见 6.3) | +| `internal/routers` | **不存在**(走 gRPC + HTTP Gateway) | +| `service/{expose,dependencies}.go` | 聚合宿主注入点:覆盖 Redis/Etcd/DB/Cache(`dependencies.go:19-32`) | +| `proto/{check,data,const}.proto` | 前两个定义本模块 6 个 rpc;`const.proto` 为本模块无关的共享消息 | +| `etc/{initial_dev,initial_prod,initial_test}.yaml` | **三份内容一致**(Port 12101 / Gateway 12102 / 同一 `127.0.0.1` 库与 Redis) | +| `test/grpc/main.go` | `package main` 的手工调用脚本(非 `_test.go`),默认打 `api.apinb.com:10020`(`:18`) | +| `test/http/*.http` | 2 个手工请求样例 | + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/initial.Check/Hello` | 健康检查,回显 `code` | 无(模块 yaml 登记匿名 `etc/initial_dev.yaml:16`) | `internal/logic/check/hello.go:12` ← `internal/server/check_server.go:19` | +| POST | `/initial.Check/Config` | 按 `app + os` 下发配置 | 无(`etc/initial_dev.yaml:17`) | `internal/logic/check/config.go:13` ← `check_server.go:24` | +| POST | `/initial.Check/Updates` | 版本更新检查 | 无(`etc/initial_dev.yaml:18`) | `internal/logic/check/updates.go:14` ← `check_server.go:29` | +| POST | `/initial.Data/Country` | 启用国家列表 | 无(`etc/initial_dev.yaml:19`) | `internal/logic/data/country.go:11` ← `data_server.go:18` | +| POST | `/initial.Data/Areas` | 行政区划(默认中国、默认市级) | 无(`etc/initial_dev.yaml:20`) | `internal/logic/data/areas.go:13` ← `data_server.go:23` | +| POST | `/initial.Data/Datas` | 系统标签字典(含硬编码"全部"项) | 无(`etc/initial_dev.yaml:21`) | `internal/logic/data/datas.go:12` ← `data_server.go:28` | +| POST | gRPC `/initial.Check/*`、`/initial.Data/*` | 同上(同一 handler) | 同上 | `internal/server/new.go:33-34` | + +**声明但未实现/占位的接口**:无。`proto/check.proto:8-20` 声明 3 个 rpc、`proto/data.proto:7-16` 声明 3 个 rpc,与 `internal/server/check_server.go:19-31`、`internal/server/data_server.go:18-30` 逐一对应;`pb/*_grpc.pb.go` 的 server 接口无多余方法。 + +> 鉴权补充(两处问题叠加): +> 1. **模块 yaml 的匿名清单不会生效**:`etc/initial_dev.yaml:14` 为 `MicroService.Enable: false`,而匿名清单只在 `Enable == true` 时才写入 etcd(`D:\work\bsm-sdk\core\service\service.go:66-90`,`register.SetAnonymous` 在 `:86`)。 +> 2. **独立部署时模块网关自身不带任何鉴权**:`Start` 把 `GatewayMux` 直接交给 `http.ListenAndServe`(`service.go:106-111`、`:120-129`),没有包 JWT 中间件;JWT 校验由聚合入口 `pkgs/all/internal/server/authorization.go:42-66` 提供。因此独立部署下这 6 个接口对任何网络可达方**完全开放**(含下面 6.1 的 SQL 注入)。 +> 3. **聚合部署时这 6 个接口都需要 JWT,与模块 yaml 的匿名声明矛盾**:`pkgs/all/etc/default_dev.yaml:24-43` 的 `Authorization.Anonymous` 不含任何 `/initial.*`。 +> 4. **即便把 `Enable` 改为 `true`,清单写法也对不上**:框架发现的方法名是 `服务名.方法名`(`service.go:149-158`,如 `Check.Config`、`Data.Areas`),HTTP 网关的规范化路径是 `/initial.Check/Config`(`authorization.go:105-112`),而配置里写的是带前缀的 `initial.Check.Config`(`etc/initial_dev.yaml:17`)——归一化后为 `/initial.Check.Config`,与上述两种格式都不相等,永远不会命中匿名判断。 + +## 4. 数据模型与表 + +### 表 `initial_apps`(`internal/models/initial_apps.go:11-20`,GORM 自动迁移) + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| `id`/`created_at`/`updated_at`/`deleted_at` | uint / timestamp | PK / 软删 | 来自 `types.Std_IICUDS` | +| `version` | varchar(255) | default '' | 版本号,**字符串**(`:13`) | +| `app` / `os` / `arch` | varchar(255) | default '' | 应用名 / 操作系统 / 架构 | +| `summary` | text | default '' | 更新说明 | +| `files` | text | default '' | 更新文件与 hash | +| `pubdate` | timestamp | — | 发布时间(`:19`) | + +> 无 `enabled`/`status` 字段,也无 `(app, os, arch)` 唯一索引 → 同一三元组可堆积任意多条记录,靠 `ORDER BY pubdate DESC, id DESC LIMIT 1` 取"最新"(`updates.go:23-25`)。 + +### 表 `initial_config`(`internal/models/initial_config.go:9-16`) + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `app` / `os` | varchar(255) | `os = 'ALL'` 表示全平台(`:12`) | +| `key` / `value` | **text** | 配置键值(`:13-14`),`key` 为 text 且**无索引** | +| `version` | int64 | 版本号(`:15`) | + +### 表 `initial_country`(`internal/models/initial_country.go:6-21`) + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `iso2` / `iso3` | varchar(255) | 均声明 `uniqueIndex`(`:8-9`) | +| `num_code` / `phone_code` | varchar(255) | ISO 数字码 / 国际区号 | +| `currency` / `currency_symbol` / `region` / `name` / `local_name` | varchar(24|255) | 均 `not null` | +| `timezones` / `translations` | text(无 tag) | 时区 / 翻译 | +| `enabled` | bool | default true(`:19`),查询条件列 | +| `sort_order` | int32 | default 0(`:20`),排序列 | + +### 表 `initial_areas`(`internal/models/initial_areas.go:7-18`) + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `id` / `country_id` / `country_code` / `pid` / `deep` | uint / varchar / int32 | 层级关系 | +| `name` / `pinyin_prefix` / `pinyin` / `ext_id` / `ext_name` | varchar | 名称与拼音 | + +> **注意:该模型没有 `enabled`、`sort_order`、`show_town` 三个字段**,而 `areas_cache.go:33-35` 的查询却使用了它们,详见 6.2 第 1 条。 + +### 表 `initial_datas`(`internal/models/initial_datas.go:10-17`) + +| 字段 | 类型 | 说明 | +| --- | --- | --- | +| `data_type` | varchar(255) | 按类型分组,有索引 | +| `key` | varchar(255) | `uniqueIndex`(`:13`) | +| `title` / `remark` / `icon` | varchar | 展示字段 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["客户端请求 /initial.Check/Updates"] --> B["校验 app/os/arch/version 非空,否则 ErrInvalidArgument"] + B --> C["SELECT * FROM initial_apps WHERE app=? AND os=? AND arch=? ORDER BY pubdate DESC, id DESC LIMIT 1"] + C --> D{"记录是否存在"} + D -->|"不存在"| E["返回 Identity=0, Version=0(视为无更新)"] + D -->|"查询出错"| F["返回 errcode.ErrDB"] + D -->|"存在"| G{"in.Version != data.Version(字符串不等)"} + G -->|"不相等"| H["返回新版本 identity/version/summary/files/pubdate"] + G -->|"相等"| I["返回 identity/version,不带 summary/files"] + J["客户端请求 /initial.Data/Areas"] --> K["入参为空则默认 CountryId=1(中国)"] + K --> L["Redis Get BuildKey(areas, 条件, show_town)"] + L -->|"命中"| M["直接返回"] + L -->|"未命中"| N["SELECT * FROM initial_areas WHERE enabled=true AND country_id=? AND show_town=? ORDER BY sort_order ASC, name ASC"] + N --> O["回写 Redis(忽略 DB 错误)并返回"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 高 | `internal/logic/check/config_cache.go:21` | **SQL 注入**。`Order("CASE WHEN os = '" + os + "' THEN 0 ELSE 1 END, id DESC")` 把请求参数 `os`(`in.Os`,来自 `check/config.go:15-17` 的必填校验,**只判空、无字符过滤**)**直接字符串拼接**进 `ORDER BY` 子句。GORM 的 `Order(string)` 按原始 SQL 下发,不经占位符转义,因此 `os` 传入形如 `1' THEN 0 ELSE 1 END) UNION SELECT ... --` 的载荷即可越权读取/破坏数据。同一条查询的 `Where` 用的是 `?` 占位符(`config_cache.go:20`),唯独 `Order` 走了拼接,属明显疏漏。 | +| 高 | `etc/initial_dev.yaml:14-21`、`etc/initial_prod.yaml:14-21`、`etc/initial_test.yaml:14-21` | 6 个接口全部登记为匿名。叠加 6.1 上一行的 SQL 注入,攻击面为"未鉴权的任意请求"。另见第 3 节鉴权补充:该清单在 `Enable: false` 下不会发布,且条目格式(`initial.Check.Config`)与框架实际匹配格式(`Check.Config` / `/initial.Check/Config`)不一致。 | +| 中 | `pkgs/all/etc/default_dev.yaml:24-43` | 聚合部署白名单未放行任何 `/initial.*` 路径 → 与模块 yaml 的匿名声明直接矛盾,表现为"本地/单模块可达,聚合后 401"。 | +| 中 | `internal/logic/check/config.go:15-17` | 入参仅做非空校验,`app`/`os` 无长度上限、无字符白名单,随后被用作缓存键(`config_cache.go:13`)与 SQL 条件;`os` 还进入 `Order` 拼接(见上)。 | +| 低 | `internal/logic/data/areas_cache.go:22-23` | `country_id` 作为查询条件**未校验其存在性与归属**,任意整数都直接落到 `WHERE country_id = ?`;`excode.ErrInvalidCountry`(`internal/excode/ex.go:11`)定义了却未使用。 | +| 低 | `internal/logic/check/hello.go:14-21` | `Hello` 把请求方传入的 `code` 原样回显进 `Details`(`"Hello " + in.Code`),无长度限制;虽为健康检查,仍是反射型输出的最小隐患。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 高 | `internal/logic/data/areas_cache.go:33-35` | 查询使用了 `enabled`、`show_town`、`sort_order` 三列,但 `models.InitialAreas`(`initial_areas.go:7-18`)**没有这三个字段**,`database.AppendMigrate`(`initial_areas.go:20-22`)也不会创建它们。按仓库内证据,该 SQL 在任何库上都会因"列不存在"报错;而错误在第 37 行被 `RedisService.Set` 的返回值覆盖,于是**接口不报错、而是把空结果写进缓存并返回空列表 30 分钟**(`vars.DefaultTTL = 30m`,`D:\work\bsm-sdk\core\vars\cache.go:16`)。若线上表结构由仓库外脚本额外加了这三列(`README.md:279` 提到的 `scripts/initial_areas_202509081640.sql` 在仓库中**不存在**),则可运行——但仓库内无此依据,【信息不足:数据库实际列结构未随仓库提供】。 | +| 高 | `internal/logic/check/updates.go:39` | **版本比较用字符串不等**:`if in.Version != data.Version`。没有语义版本解析、没有"仅当服务端更新时才提示升级"的判断。后果:①客户端版本高于表内最新记录(如客户端 2.0.0、表内 1.9.0)时,接口仍返回 `1.9.0` 并带 `files`,等于**向已升级的客户端下发旧安装包(强制降级)**;②任何书写差异(`v1.0.0` vs `1.0.0`、`1.0.0` vs `1.0.00`)都会触发一次"更新";③`updates.go:23-25` 只按 `pubdate DESC, id DESC` 取一条,`pubdate` 相同时按 `id` 倒序,若运营误插入一条 `pubdate` 为未来的记录,会长期占据"最新"。 | +| 中 | `internal/logic/check/config.go:34-40` | 去重逻辑与排序意图相反。SQL 用 `ORDER BY CASE WHEN os = '' THEN 0 ELSE 1 END, id DESC`(`config_cache.go:21`)让"专有 OS 优先、且 id 大者优先";但去重条件 `if existing, exists := configMap[item.Key]; !exists \|\| item.Os != "ALL"` 会让**后遍历到的非 ALL 记录覆盖先前记录**,而遍历顺序正是 SQL 的顺序(大 id → 小 id),于是同一 `key` 存在多条同 OS 记录时,最终保留的往往是 **id 最小**的那条,与"取最新"相反。`else if existing.Os == "ALL" && item.Os != "ALL"` 分支与首条件重复,只在 `existing.Os == "ALL"` 时也已被首条件命中,属冗余。 | +| 中 | `internal/logic/data/country_cache.go:19-24`、`areas_cache.go:33-38` | **DB 错误被吞掉**。两者的 `Find(&x).Error` 结果都在下一行被 `RedisService.Set(...)` 的返回值覆盖,函数最终返回的是 Set 的错误;查询失败(表不存在、连接超时)时接口不报错,而是把 `nil` 切片缓存 30 分钟,形成"空数据缓存穿透式污染"。`datas_cache.go:22-25` 是唯一做了错误判断的实现(`err != gorm.ErrRecordNotFound` 则返回 `ErrDB`),三处风格不一致。 | +| 中 | `internal/logic/data/datas_cache.go:22` | `Order("data_type ASC, id ASC")` 的 `Find` **无任何过滤条件**(连 `enabled` 都没有),全表返回且无 `LIMIT`;`initial_datas` 也没有启用/停用字段可用。字典表一旦膨胀,每次缓存失效都会全量扫表并把全量结果写入 Redis。 | +| 中 | `internal/logic/data/areas_cache.go:22-23`、`areas.go:15-17` | `Areas` 无分页、无层级/数量上限:`show_town = true` 时会把国家下全部乡镇一次性返回(`Where("show_town = ?", in.ShowTown)`),响应体无界。默认 `CountryId=1`(中国)也是硬编码常量,若字典中 id=1 不是中国则语义错位。 | +| 低 | `internal/logic/check/updates.go:28-34` | 未找到记录时返回 `Identity: "0", Version: "0"` 并带 `nil` error,与"查库失败"(`:35` 返回 `ErrDB`)语义不同,但客户端难以区分"无该应用"与"该应用无更新";`Identity`/`Version` 被赋固定字符串 `"0"`,不是空串,容易与真实版本号混淆。 | +| 低 | `internal/logic/check/updates.go:20-25` | 查询未使用 `ctx`(`:14` 的入参 `ctx` 全程未使用),上游超时/取消不会传导到 DB,慢查询无超时控制。`country.go:11`、`areas.go:13`、`datas.go:12`、`config.go:13`、`hello.go:12` 同样是 `ctx` 未使用。 | +| 低 | `internal/logic/data/datas.go:23-31` | 业务字典里硬编码了中文常量"全部"/"default"/"all"(`DataType: "default"`, `Key: "all"`),既与库内数据的 `data_type` 取值可能冲突(`initial_datas.key` 还有 `uniqueIndex`,见 `initial_datas.go:13`),也让多语言/多端差异化无法实现。 | +| 低 | `internal/logic/check/config_cache.go:27-28` | 缓存设置失败时 `return configs, err` 把 Redis 错误当成整体失败返回;上层 `config.go:27-30` 随即转成 `ErrDB`("数据库错误"),误导排障方向。 | +| 低 | `internal/logic/data/areas_cache.go:26` | 缓存键由**原始 SQL 片段**拼成:`BuildKey("areas", "country_id = ?", val, in.ShowTown)` → 形如 `bsm:areas:country_id = ?::`(`BuildKey` 实现见 `D:\work\bsm-sdk\core\cache\redis\cache.go:19-26`,直接 `fmt.Sprintf(":%v")`)。键里带空格与 `?`,可读性差;一旦哪天改 `key` 的写法,历史缓存键即全部失效。 | + +### 6.3 未完成实现 + +- **`internal/excode/ex.go` 5 个错误码全部未使用**:`ErrInvalidApp`/`ErrInvalidOS`/`ErrInvalidArch`/`ErrInvalidVersion`/`ErrInvalidCountry`(`:7-11`)在本模块无任何引用点(全模块检索仅命中该文件自身),实际实现用的是 SDK 的 `errcode.ErrInvalidArgument`/`ErrDB`(如 `check/config.go:16,29`)。整个 `internal/excode` 包是死代码。 +- **`internal/models/query.go` 是 1 行空文件**(只有 `package models`,无任何声明)。 +- **`internal/config/config.go:19` 的 `Rpc` 字段全模块无使用点**;`etc/initial_*.yaml:31-34` 的 `Rpc` 段整体被注释 → 死配置。 +- **`internal/impl/impl.go:16,22,24` 的 `MemorySerice`/`RedisService`**:`MemorySerice` 仅被 `service/dependencies.go:30` 赋值,本模块内无任何读取;`RedisService` 有实际使用(各 `*_cache.go`)。即内存缓存层是初始化了但不用的预留。 +- **`Check.Hello` 未做任何依赖探活**:`check/hello.go:23-24` 自身注释写着"可以在这里添加更多的健康检查逻辑,例如检查数据库连接、Redis连接等",当前恒返回 `Code: 0` → 健康检查无法反映真实可用性。 +- **`proto/const.proto:1-313`** 定义了 `OrderSummaryItem`、`FeedPostItem`、`GroupPostItem`、`RelationItem`、`MarketLoginReply`、`CmsCategoryItem` 等大量与本模块无关的消息,本模块未引用任何一条 → 死定义,且被 `pb/const.pb.go` 编译进本模块。 +- **`test/grpc/main.go`(`package main`)** 是手工调用脚本而非测试:不在 `_test.go` 文件中,`go test` 不会执行;默认地址硬编码 `api.apinb.com:10020`(`:18`),且调用成功后只打印条数,无断言。 +- **`cmd/cli/main.go:1-10`** 仅打印两行日志。 +- 全模块**无任何 `*_test.go`**,`internal/logic/**` 与 `internal/models/**` 零测试覆盖。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/config/config.go:36` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`。`with.Databases`(`D:\work\bsm-sdk\core\with\databases.go:13-15`)在 `Source` 为空时直接 `panic("No Database Source Found !")`,因此缺 `Databases` 段时 `impl.NewImpl()`(`impl.go:26`)在启动阶段 panic,且报错不含配置项名。 | +| 中 | `etc/initial_prod.yaml:7,10`(及 `:1-41` 全篇) | **生产配置是开发配置的副本**:`Databases.Source` 指向 `host=127.0.0.1 ... dbname=rst_dev`(`:7`),`Cache` 指向 `redis://null:...@127.0.0.1:6379/`(`:10`),与 `initial_test.yaml`、`initial_dev.yaml` 三份完全一致。生产若直接使用该文件,会连本机库/本机 Redis。 | +| 中 | `pkgs/all/internal/service/initial.go:9-20` | 聚合入口调用 `moduleService.Expose` 时**未传 `Config`**,而 `internal/service/expose.go:12-16` 的 `ExposeOptions` 也确实没有该字段(与同仓 `sender` 的 `expose.go:13-20` 不一致)。因此聚合部署下 initial 的配置完全取自 `pkgs/all` 的共享 DB/Redis;本模块 `etc/*.yaml` 在聚合场景下是**不被读取的**,容易被误认为生效。 | +| 低 | `internal/impl/impl.go:16,22` | 变量名拼写错误 `MemorySerice`(应为 `MemoryService`)。同 workspace 的 `pkgs/all/internal/impl` 用的是正确拼写(见 `pkgs/all/internal/service/initial.go:15` 的 `impl.MemoryService`),跨模块阅读易误判为两个变量。 | +| 低 | `internal/config/config.go:20`、`initial_prod.yaml:14` | `SrvConfig.Rpc` 字段与 yaml 段位均无使用方(见 6.3)。 | +| 低 | `README.md:322-329` | README 的缓存策略表写着"配置数据 10 分钟 / 国家数据 24 小时 / 地区数据 12 小时 / 系统数据 6 小时",但四处实现统一使用 `vars.DefaultTTL`(`config_cache.go:27`、`country_cache.go:23`、`areas_cache.go:37`、`datas_cache.go:27`),实际值是 **30 分钟**(`D:\work\bsm-sdk\core\vars\cache.go:16`)。文档与实现不一致。 | +| 低 | `README.md:180-187` | README 把 6 个接口标为 `POST /initial.Check/Hello` 等,与 `authorization.go:105-112` 的规范化路径一致,这部分是对的;但 `README.md:342,346` 声明的 `/health`、`/metrics` 端点在本模块无任何注册 → 不实描述。 | +| 低 | `README.md:76-101` | README 的目录树宣称存在 `cache/`、`swagger/`、`scripts/`、`Dockerfile`、`docker-compose.yml`、`Makefile`,这些在本模块均**不存在**(实际目录只有 `cmd/internal/pb/proto/etc/test/service` 与三个 md)。 | +| 低 | `etc/supervisor.bsm-apps-initial.conf:6` | 进程以 `user=root` 运行。 | +| 低 | `internal/logic/data/areas_cache.go:33` | `Where("enabled = ?", true)` 用原始 SQL 字符串表达布尔条件(而非 `Where(&models.InitialAreas{Enabled: true})` 这类结构化条件),正是这类写法让 6.2 第 1 条的"列不存在"缺陷在编译期无法被发现。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| I1 | 高 | `config_cache.go:21` 用字符串拼接把 `os` 注入 `ORDER BY` | 未授权读取/篡改任意表、库结构泄露 | +| I2 | 高 | `updates.go:39` 用字符串不等判断版本,未判断"服务端是否更新" | 客户端降级到旧版本、版本书写差异导致误报更新 | +| I3 | 高 | `areas_cache.go:33-35` 查询模型上不存在的 3 列,且 DB 错误被 `Set` 覆盖 | `Areas` 接口必然返回空列表并缓存 30 分钟,数据不可用且难排查 | +| I4 | 高 | 6 个接口全部匿名(模块 yaml),独立部署网关无鉴权中间件 | 未鉴权访问全部初始化数据面,放大 I1 | +| I5 | 中 | 模块匿名清单与聚合白名单互相矛盾,且条目格式与框架匹配规则不一致 | 上线 401 / 误判防护已生效 | +| I6 | 中 | `config.go:34-40` 去重与 `ORDER BY` 意图相反,同 OS 多条时保留 id 最小者 | 下发的配置值可能不是最新配置 | +| I7 | 中 | `country_cache.go`/`areas_cache.go` 吞掉 DB 错误并缓存空结果 | 空数据被缓存 30 分钟,故障期数据面静默降级 | +| I8 | 中 | `datas_cache.go:22` 全表无过滤、无分页;`Areas` 无条数上限 | 大表全量扫描 + 响应体膨胀 | +| I9 | 中 | `config.go:36` 未校验 `Databases`,缺失即启动 panic;`initial_prod.yaml` 仍是本机地址 | 部署可用性、生产误连本机库/Redis | +| I10 | 低 | 死代码/死配置(`internal/excode`、`models/query.go`、`Rpc` 段、`const.proto`、`cmd/cli`、`MemorySerice`)+ 零 `_test.go` | 可维护性、无法回归验证 | + +## 8. 修复建议(务实项) + +1. **修 SQL 注入(最高优先)**:把 `internal/logic/check/config_cache.go:21` 的 `Order` 从拼接改为白名单映射,例如按 `os` 是否等于请求值在 Go 侧决定排序,或改用 `clause.Expr`+占位符;同时给 `os`/`app` 加长度与字符白名单(只允许 `[A-Za-z0-9_.-]`,≤32)。校验位置就在 `internal/logic/check/config.go:15-17` 现有的参数校验处,无需新增层。 +2. **修版本比较**:在 `internal/logic/check/updates.go:39` 之前解析版本(仓库已有 `github.com/coreos/go-semver` 作为间接依赖,见 `go.mod:26`;也可直接按 `.` 分段比较,避免新增直接依赖),仅当 `服务端版本 > 客户端版本` 时返回更新;并对 `in.Version` 做非空+格式校验。同时为 `initial_apps` 的 `(app, os, arch, version)` 加唯一索引,避免同版本重复记录。 +3. **修 `Areas` 列不匹配**:在 `internal/models/initial_areas.go:7-18` 补上 `Enabled bool`、`SortOrder int32`、`ShowTown bool`(并在仓库内补上对应的建表/迁移 SQL,`README.md:279` 引用的 `scripts/initial_areas_202509081640.sql` 应随仓库提供);若这三列本就该由外部脚本维护,则在 `areas_cache.go` 改为按模型字段结构化查询并显式注明外部依赖。 +4. **恢复错误处理**:`country_cache.go:19-24` 与 `areas_cache.go:33-38` 参照 `datas_cache.go:22-25` 的写法,先判断 `Find` 的 `err` 再回写缓存,避免把空结果写入 Redis。 +5. **鉴权口径对齐(二选一,必须选)**:若这 6 个接口应当匿名,把 `/initial.Check/Hello`、`/initial.Check/Config`、`/initial.Check/Updates`、`/initial.Data/Country`、`/initial.Data/Areas`、`/initial.Data/Datas` 按 `authorization.go:105-112` 的规范化格式加入 `pkgs/all/etc/default_dev.yaml:24-43`(并同步生产配置);若应当鉴权,则从 `etc/initial_{dev,test,prod}.yaml:15-21` 删除匿名条目。注意模块 yaml 的 `initial.Check.Config` 写法与框架匹配规则不符,改 `Enable` 也不会生效。 +6. **生产配置**:`etc/initial_prod.yaml:4-10` 改成真实生产库/Redis(或明确该文件在聚合部署下不生效,避免误用)。 +7. **配置校验**:`internal/config/config.go:36` 的 `conf.NotNil` 补上 `Databases`;或在 `impl.NewImpl` 前显式判空并打印缺失项名。 +8. **查询边界**:`datas_cache.go:22` 增加明确的条件与 `LIMIT`;`Areas` 增加返回条数上限(如 5000)或按 `deep` 分层返回。 +9. **去重逻辑**:`internal/logic/check/config.go:34-40` 改为显式优先级比较(先比 `Os == 请求os`,再比 `id` 更大),删掉冗余的 `else if` 分支。 +10. **清理与补测**:删除 `internal/excode`(或真正在参数校验处使用其中错误码)、`internal/models/query.go`、`proto/const.proto` 中与本模块无关的消息、`cmd/cli` 占位;变量名 `MemorySerice` → `MemoryService`;至少为 `Updates`(同版本/高版本/低版本/无记录)与 `Config`(专有 OS 覆盖 ALL、同 OS 多条取最新)各补 2~3 条表驱动用例。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 diff --git a/docs/logs.md b/docs/logs.md new file mode 100644 index 0000000..4d269f1 --- /dev/null +++ b/docs/logs.md @@ -0,0 +1,135 @@ +# logs(业务操作日志)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/logs` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 12 个(全部手写,无 `proto`/生成代码) | +| 入口 | `cmd/main`(Gin 单进程)、聚合入口 `pkgs/all`、`pkgs/ecmall` | +| 对外协议 | 仅原生 REST,前缀 `/rest/logs` | +| 结论摘要 | 结构简单、职责清晰;但核心接口存在**过滤逻辑写反**、**取参键名不一致**、**分页参数未生效**、**类型断言可 panic**,且全部路由挂匿名组、无鉴权。 | + +## 1. 服务定位与职责 + +集中保存与查询业务操作记录,供后台追踪操作行为和排查问题。数据模型包含服务名、操作人、IP、数据类型、级别、内容等字段。它是业务日志的**存储与查询**服务,不负责进程日志采集、指标监控或链路追踪。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:读配置 → 初始化 impl → 建 Gin → 注册路由 → 监听 `Spec.Port` | +| `cmd/cli/main.go` | 命令行辅助入口 | +| `internal/config/config.go` | 配置结构(Base、Databases、Rpc、APM、Etcd)与校验 | +| `internal/impl/impl.go` | 初始化 Redis、DB、Etcd 实例 | +| `internal/routers/register.go` | REST 路由注册(全部挂在匿名组) | +| `internal/logic/hello/ping.go` | 健康探测 | +| `internal/logic/log/{create,fetch,total}.go` | 创建、分页查询、按级别统计 | +| `internal/models/log_data.go` | `LogData` 模型 + 自动迁移注册 | +| `service/{expose,dependencies}.go` | 聚合宿主注入接口 | + +依赖注入:`service.Dependencies` 支持外部传入 Redis/Etcd/DB/Cache,非 nil 时覆盖 `internal/impl` 对象,供 `pkgs/all` 复用共享连接。 + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| GET | `/rest/logs/ping` | 健康探测,返回 `{"message":"Pong"}` | 无 | `logic/hello/ping.go` | +| POST | `/rest/logs/create` | 批量写入日志记录 | 无 | `logic/log/create.go` | +| POST | `/rest/logs/fetch` | 按条件分页查询日志 | 无 | `logic/log/fetch.go` | +| POST | `/rest/logs/total` | 按 level 分组统计数量 | 无 | `logic/log/total.go` | +| HEAD | `/` | 健康检查(独立入口注册) | 无 | `cmd/main/main.go` | + +> 该模块没有 `proto` 定义,接口契约以逻辑层实际读取的 JSON 字段为准。 + +## 4. 数据模型 + +### 表 `log_data`(经 GORM 自动迁移创建) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `created_at` | TIMESTAMP | - | 创建时间 | +| `service` | varchar(255) | 默认 `def` | 服务名称 | +| `op_id` | uint | - | 操作人 ID | +| `op_name` | varchar(255) | - | 操作人姓名 | +| `ip` | varchar(255) | 默认 `0.0.0.0` | 操作 IP | +| `data_type` | varchar(255) | - | 数据类型 | +| `level` | uint | 默认 1 | 日志级别(1 信息 / 2 警告 / 3 错误) | +| `content` | text | - | 日志内容 | +| `hmac` | varchar | - | 预留校验字段,写入逻辑未计算 | +| `encry` | - | `gorm:"-"` | 非持久化字段 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /rest/logs/create"] --> B["ClientIP 过滤"] + B --> C["BindJSON 到 []LogData"] + C --> D["DBService.Create 批量写入"] + E["POST /rest/logs/fetch"] --> F["BindJSON 到 map"] + F --> G["按 op_name/service/ip/level 过滤"] + G --> H["Count + Limit/Offset 分页"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/log/create.go:21` | **IP 过滤逻辑与注释相反**。注释为"禁止公网网段提交",但代码在 IP 属于 `127.0.0.1`/`localhost`/`10.`/`172.`/`192.` **内网段时返回错误**,等于只拦内网、放行公网,与设计意图完全颠倒。 | +| **高** | `internal/routers/register.go:21-27` | 全部业务路由注册在**匿名组**,`JwtAuth` 中间件被注释掉。`create`/`fetch`/`total` 可被任意匿名调用,日志可被伪造写入、全量读取。 | +| 中 | `internal/logic/log/fetch.go:36,39,42` | 查询关键词未做长度/字符限制即拼接进 `LIKE`;虽使用参数占位符未直接注入 SQL,但 `%kw%` 全表扫描与超长入参缺少约束。 | +| 低 | `internal/logic/log/create.go:22,30,35,41` | 用标准库 `log.Printf` 打印请求 IP、解析错误与 DB 错误,可能将内部信息写入进程日志。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/log/fetch.go:42` | **取参键名不一致**:判断用 `request["op_ip"]`,取值用 `request["ip"].(string)`。实际传入 `op_ip` 时取到 `nil`,`nil.(string)` 触发 **panic**,服务 500。该过滤条件永远生效不了或直接崩溃。 | +| **高** | `internal/logic/log/fetch.go:33-53` | **分页参数未生效**:`size`/`page` 声明后从未从 `request` 赋值,判断 `page<=0`/`size<=0` 恒成立,永远固定第 1 页、每页 50 条。调用方传 `page`/`size` 无效。 | +| **高** | `internal/logic/log/fetch.go:45` | `request["level"].(int)`:JSON 数字反序列化为 `float64`,直接断言 `int` 会 **panic**。只要带上 `level` 字段即崩溃。 | +| 中 | `internal/logic/log/fetch.go:29-31` | 返回体变量名为 `data []any`,未指定 `LogData` 类型,GORM `Find` 按 `any` 填充,字段结构依赖 GORM 猜测,易与表结构脱节。 | +| 中 | `internal/logic/log/total.go:24` | `tx.Where(...)` 未回写 `tx`,在 GORM v2 下当前能生效但依赖内部行为,一旦链式调用顺序变化即静默丢失条件;同类写法在 `fetch.go` 同样存在。 | +| 中 | `internal/logic/log/total.go:28` | `Select("level","count(level)").Group("level").First(&result)`:`First` 会附加 `ORDER BY id LIMIT 1`,与 `GROUP BY` 组合语义不明确,多分组时只返回一条。 | +| 低 | `internal/models/log_data.go:19-20` | `Hmac` 字段存在于表中,但 `create.go` 未计算/校验任何 HMAC;`Encry` 为非持久化字段。二者目前无实际用途。 | + +### 6.3 未完成实现 + +- 未发现显式 TODO/占位。`Hmac` 完整性校验字段有模型无逻辑,属"设计预留未落地"。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/impl/impl.go:32` | `DBService = with.Databases(config.Spec.Databases, opts)`;而 `config.go:27` 的 `conf.NotNil` 只校验 `Service`/`Cache`,**未校验 `Databases`**。配置漏配时 DB 可能为 nil,首个查询即空指针崩溃。 | +| 低 | `internal/impl/impl.go:22,35` | `RedisService`、`EtcdService` 被初始化但全模块无任何使用点(死代码)。`Dependencies.Cache` 也被声明却未在 `applyDependencies` 中应用。 | +| 低 | `internal/routers/register.go:20` | 保留调试用 `fmt.Println(v1_key)`。 | +| 低 | 全模块 | **无任何单元测试**(`*_test.go` 缺失)。`fetch.go` 的两处 panic 若有测试即可暴露。 | +| 低 | `internal/logic/log/fetch.go:19` | 请求体用 `map[string]any` 承载,缺失字段/类型错误只能运行时发现,无误用反馈。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| L1 | 高 | 路由无鉴权,日志可伪造/泄露 | 数据可信性、信息安全 | +| L2 | 高 | `fetch` 取参键名不一致 → panic | 接口可用性 | +| L3 | 高 | `level` 类型断言 → panic | 接口可用性 | +| L4 | 高 | 分页参数完全失效 | 功能不可用 + 全表返回风险 | +| L5 | 高 | `create` IP 过滤逻辑写反 | 防护失效 | +| L6 | 中 | `Databases` 未做非空校验 | 启动后运行期崩溃 | +| L7 | 中 | GORM 链式未回写、`First`+`Group` 语义问题 | 查询结果不确定 | +| L8 | 低 | 死代码、调试输出、无测试 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **鉴权**:`create`/`fetch`/`total` 至少接入入口层统一鉴权(聚合入口已有 JWT 能力,直接启用即可),不要留在匿名组。 +2. **修正 IP 过滤**:明确"允许来源"与"禁止来源",按白名单/内网判定写正;`172.`/`192.` 需收敛为 `172.16-31.`、`192.168.`,并考虑网关代理下的 `X-Forwarded-For`。 +3. **`fetch` 修复**:统一键名为 `op_ip`;`level` 用 `mapstructure`/`json.Number` 或结构体接收,避免 `.(int)` 断言;把 `page`/`size` 真正从请求读出并设上限。 +4. **`total` 修复**:确认是要"按 level 分组返回全部"还是"单一统计值",按结果语义改用 `Find`/`Scan`,并回写 `tx`。 +5. **配置校验**:`conf.NotNil` 补上 `Databases`;确需可选时在初始化后显式判空并给出清晰错误。 +6. **清理**:删除调试 `Println` 与该模块未使用的 Redis/Etcd 初始化(或按规划保留并注明)。 +7. **补测试**:为 `create`/`fetch`/`total` 各写一条正例 + 一条边界用例,覆盖本次发现的三处 panic。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。日志审计中"统一请求校验框架""引入 DTO 层"一类改造不在此列。 diff --git a/docs/mall.md b/docs/mall.md new file mode 100644 index 0000000..fd04e17 --- /dev/null +++ b/docs/mall.md @@ -0,0 +1,362 @@ +# mall(店铺与商品经营)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/ec/mall` | +| 服务域 | 电商-店铺与商品经营 | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 123 个(`pb/` 生成代码 22 个,手写 101 个,手写约 5014 行;含生成代码共 21486 行);`proto/` 8 个 1026 行;`internal/logic/` 67 个文件(覆盖 7 个服务域) | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、`cmd/cli`(占位)、聚合入口 `pkgs/all` 经 `service.Expose` 复用共享 gRPC/Gateway | +| 对外协议 | gRPC + HTTP Gateway,路径 `/mall./`,全部为 POST | +| 结论摘要 | 接口数量大、分层一致,但**越权面很宽**:13 处写操作与 3 处读操作只按 `id/identity` 定位、从不携带 token 中的 `store_identity`;`Staff.Fetch` 鉴权被注释、手机验证码登录从不校验验证码、店铺支付/邮件配置可被任意已登录用户读改。另有 12 个明文占位接口(运费模板 5 个 + 禁运区域 5 个 + 入驻 + 小程序码 + 按规格查询)、`item_fetch` 分类过滤恒空、`store/search` 列名错误、`spec_create` 把 `stock_type` 当库存写入等确定性缺陷。密码已统一为 bcrypt,仅注释残留 MD5 字样。 | + +## 1. 服务定位与职责 + +电商后台的经营域服务,围绕"店铺"组织全部资源:店铺基础配置与支付/邮件配置、店铺员工与其登录、商品(含规格、图片、评论、分类)、分类树、广告位、公告、运费模板与禁运区域。租户隔离通过 JWT `owner` 中的 `store_id`/`store_identity` 表达(`bsm-sdk/core/types/encipher.go:8`、签发见 `internal/logic/staff/login.go:88`)。 + +不负责:订单与购物车(`module/ec/order`)、地址库(`module/ec/address`)、供应商主数据(`module/ec/market`,本模块通过跨库表 `market_supply` 直读)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New` → `impl.NewImpl` → `server.New(nil)` → `service.New` → `srv.Start()` | +| `cmd/cli/main.go` | 命令行占位入口,仅 `log.Println("Hello World!")`(`cmd/cli/main.go:6`) | +| `internal/config/config.go` | 配置结构(Base/Databases/MicroService/Rpc/Gateway/Apm/Etcd)与端口、日志、密钥初始化 | +| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 | +| `internal/password/password.go` | bcrypt 封装:`Hash`/`Verify`(唯一有测试的包) | +| `internal/excode/ex.go` | 店铺域错误码 `ErrStoreNotFound`/`ErrStoreDisabled` | +| `internal/models/**` | 16 张表的 GORM 模型 + 少量模型层 SQL 工具函数 | +| `internal/logic/{staff,store,product,category,freight,ads,notice}/**` | 业务逻辑,67 个文件,按服务域分包 | +| `internal/server/**` | protoc-gen-slc 生成的 gRPC 服务端桩(7 个 server + `new.go` 注册) | +| `pb/**` | protoc 生成的 pb / grpc / gateway 代码(22 个文件,本次只看接口签名) | +| `service/expose.go`、`service/dependencies.go` | 聚合宿主注入:注册 7 组 gRPC + Gateway handler,并允许外部覆盖 Redis/Etcd/DB/Cache | +| `proto/*.proto` | 8 个契约文件(const 为公共消息块) | +| `etc/mall_{dev,test,prod}.yaml` | 三套配置,均为 postgres + `Port: 12420`/`Gateway.Port: 12419` | +| `test/readme.md` | 仅一行 "restful test",无实际用例 | + +依赖注入:`service.Dependencies`(`service/dependencies.go:12-17`)在 `pkgs/all` 中由共享连接覆盖 `impl` 全局量;`Expose` 同时把 `internal/server` 的 7 个服务注册到宿主的 gRPC Server 与 Gateway Mux(`service/expose.go:23-43`)。 + +## 3. 接口清单 + +共 7 个服务、60 个 RPC,全部映射为 `POST /mall./`(`pb/*.pb.gw.go` 的 `MustPattern`,如 `pb/staff.pb.gw.go:536-542`、"审核" 列取自各 logic 文件实际调用的 `service.ParseMetaCtx` 参数)。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| Login | `/mall.Staff/Login` | 员工登录(账密 / 手机验证码) | 无需 | `logic/staff/login.go:23` | +| Fetch | `/mall.Staff/Fetch` | 员工列表 | **无(鉴权被注释)** | `logic/staff/fetch.go:15`(注释见 `:17-20`) | +| Create | `/mall.Staff/Create` | 创建员工 | Mall_Admin | `logic/staff/create.go:20` | +| Delete | `/mall.Staff/Delete` | 删除员工 | Mall_Admin | `logic/staff/delete.go:16` | +| GetProfile | `/mall.Staff/GetProfile` | 员工资料 | Mall_Admin | `logic/staff/get_profile.go:16` | +| SetProfile | `/mall.Staff/SetProfile` | 改自己资料 | Mall_Admin | `logic/staff/set_profile.go:16` | +| SetPassword | `/mall.Staff/SetPassword` | 改自己密码 | Mall_Admin | `logic/staff/set_password.go:18` | +| ApplyJoin | `/mall.Store/ApplyJoin` | 申请入驻 | Mall_Admin | `logic/store/apply_join.go:12` **占位** | +| Licensing | `/mall.Store/Licensing` | 店铺许可授权 | **无** | `logic/store/licensing.go:15` **半占位** | +| GetSetting | `/mall.Store/GetSetting` | 读店铺基础配置 | **无** | `logic/store/get_setting.go:14` | +| SetSetting | `/mall.Store/SetSetting` | 写店铺基础配置 | Mall_Admin(不校验店铺归属) | `logic/store/set_setting.go:15` | +| SetPayment | `/mall.Store/SetPayment` | 写支付配置 | token(任意角色) | `logic/store/set_payment.go:16` | +| SetEmail | `/mall.Store/SetEmail` | 写邮件配置 | token(任意角色) | `logic/store/set_email.go:16` | +| GetPayment | `/mall.Store/GetPayment` | 读支付配置 | token(任意角色) | `logic/store/get_payment.go:14` | +| GetEmail | `/mall.Store/GetEmail` | 读邮件配置 | token(任意角色) | `logic/store/get_email.go:14` | +| MiniCode | `/mall.Store/MiniCode` | 小程序推荐码 | Mall_Admin | `logic/store/mini_code.go:11` **占位(返回 nil)** | +| Search | `/mall.Store/Search` | 店铺搜索 | Mall_Admin | `logic/store/search.go:16` | +| ItemFetch | `/mall.Product/ItemFetch` | 商品列表 | 无 | `logic/product/item_fetch.go:15` | +| ItemDetail | `/mall.Product/ItemDetail` | 商品详情 | 无 | `logic/product/item_detail.go:14` | +| ItemDetailBySerial | `/mall.Product/ItemDetailBySerial` | 按序号批量取商品 | 无 | `logic/product/item_detail_by_serial.go:16` | +| ItemDetailBySpec | `/mall.Product/ItemDetailBySpec` | 按规格编号取商品 | 无 | `logic/product/item_detail_by_spec.go:11` **占位(返回 nil,nil)** | +| ItemCreate | `/mall.Product/ItemCreate` | 新建/更新商品 | Mall_Admin | `logic/product/item_create.go:18` | +| ItemDelete | `/mall.Product/ItemDelete` | 删除商品 | Mall_Admin(不校验店铺归属) | `logic/product/item_delete.go:16` | +| ItemModify | `/mall.Product/ItemModify` | 上下架/禁用 | Mall_Admin(不校验店铺归属) | `logic/product/item_modify.go:16` | +| ItemBatchOp | `/mall.Product/ItemBatchOp` | 批量改状态 | Mall_Admin(不校验店铺归属) | `logic/product/item_batch_op.go:16` | +| SpecFetch | `/mall.Product/SpecFetch` | 规格列表 | Mall_Admin(不校验店铺归属) | `logic/product/spec_fetch.go:15` | +| SpecCreate | `/mall.Product/SpecCreate` | 新建规格 | Mall_Admin | `logic/product/spec_create.go:17` | +| SpecModify | `/mall.Product/SpecModify` | 改规格 | Mall_Admin(不校验店铺归属) | `logic/product/spec_modify.go:15` | +| SpecDelete | `/mall.Product/SpecDelete` | 删规格 | Mall_Admin(不校验店铺归属) | `logic/product/spec_delete.go:16` | +| SpecDetail | `/mall.Product/SpecDetail` | 规格详情 | 无 | `logic/product/spec_detail.go:14` | +| PhotoCreate | `/mall.Product/PhotoCreate` | 加图片 | Mall_Admin | `logic/product/photo_create.go:17` | +| PhotoDelete | `/mall.Product/PhotoDelete` | 删图片 | Mall_Admin(不校验店铺归属) | `logic/product/photo_delete.go:16` | +| PhotoList | `/mall.Product/PhotoList` | 图片列表 | 无 | `logic/product/photo_list.go:14` | +| CommentFetch | `/mall.Product/CommentFetch` | 评论列表 | 无 | `logic/product/comment_fetch.go:15` | +| CommentCreate | `/mall.Product/CommentCreate` | 发评论/回复 | `is_comment=true` 时任意 token,否则 Mall_Admin | `logic/product/comment_create.go:18-24` | +| CommentRe | `/mall.Product/CommentRe` | 商家回复 | Mall_Admin | `logic/product/comment_re.go:17` | +| CommentDelete | `/mall.Product/CommentDelete` | 删评论 | Mall_Admin(不校验店铺归属) | `logic/product/comment_delete.go:16` | +| CommentModify | `/mall.Product/CommentModify` | 改评论 | Mall_Admin(不校验店铺归属) | `logic/product/comment_modify.go:15` | +| Fetch | `/mall.Category/Fetch` | 分类树 | 无 | `logic/category/fetch.go:13` | +| Create | `/mall.Category/Create` | 建分类 | Mall_Admin | `logic/category/create.go:16` | +| Delete | `/mall.Category/Delete` | 删分类 | Mall_Admin(不校验店铺归属) | `logic/category/delete.go:16` | +| Modify | `/mall.Category/Modify` | 改分类 | Mall_Admin(不校验店铺归属) | `logic/category/modify.go:15` | +| Fetch | `/mall.Freight/Fetch` | 运费模板列表 | Mall_Admin | `logic/freight/fetch.go:11` **占位** | +| Create | `/mall.Freight/Create` | 建运费模板 | Mall_Admin | `logic/freight/create.go:13` **占位** | +| Modify | `/mall.Freight/Modify` | 改运费模板 | Mall_Admin | `logic/freight/modify.go:13` **占位** | +| Delete | `/mall.Freight/Delete` | 删运费模板 | token(任意角色) | `logic/freight/delete.go:13` **占位** | +| Detail | `/mall.Freight/Detail` | 运费模板详情 | Mall_Admin | `logic/freight/detail.go:12` **占位** | +| DenyRegionFetch | `/mall.Freight/DenyRegionFetch` | 禁运区域列表 | Mall_Admin | `logic/freight/deny_region_fetch.go:11` **占位** | +| DenyRegionCreate | `/mall.Freight/DenyRegionCreate` | 建禁运区域 | Mall_Admin | `logic/freight/deny_region_create.go:13` **占位** | +| DenyRegionDelete | `/mall.Freight/DenyRegionDelete` | 删禁运区域 | token(任意角色) | `logic/freight/deny_region_delete.go:12` **占位** | +| DenyRegionModify | `/mall.Freight/DenyRegionModify` | 改禁运区域 | Mall_Admin | `logic/freight/deny_region_modify.go:12` **占位** | +| ByPos | `/mall.Ads/ByPos` | 按广告位取广告 | 无 | `logic/ads/by_pos.go:13` | +| Fetch | `/mall.Ads/Fetch` | 广告列表 | 无 | `logic/ads/fetch.go:14` | +| Create | `/mall.Ads/Create` | 建广告 | Mall_Admin | `logic/ads/create.go:18` | +| Modify | `/mall.Ads/Modify` | 改广告 | Mall_Admin(不校验店铺归属) | `logic/ads/modify.go:17` | +| Delete | `/mall.Ads/Delete` | 删广告 | Mall_Admin(不校验店铺归属) | `logic/ads/delete.go:16` | +| Fetch | `/mall.Notice/Fetch` | 公告列表 | 无 | `logic/notice/fetch.go:14` | +| Create | `/mall.Notice/Create` | 建公告 | Mall_Admin | `logic/notice/create.go:18` | +| Modify | `/mall.Notice/Modify` | 改公告 | Mall_Admin(不校验店铺归属) | `logic/notice/modify.go:17` | +| Delete | `/mall.Notice/Delete` | 删公告 | Mall_Admin(不校验店铺归属) | `logic/notice/delete.go:16` | + +> 契约以 proto 声明的类型为准(`proto/*.proto`),实现层的取参方式与 proto 一致;但 `pb/` 生成代码只读了接口签名,未逐行审计。 + +### 声明但未实现 / 占位的接口(已核实) + +| 接口 | 位置 | 现状 | +| --- | --- | --- | +| `Store.ApplyJoin` | `logic/store/apply_join.go:19-27` | 校验后仅剩两行 `// TODO`,直接返回 `Code:0 "OK"`,不写 `mall_apply`、不建店铺与管理员 | +| `Store.Licensing` | `logic/store/licensing.go:25,30` | 只按 `subdomain` 查店铺返回 identity;请求中的 `license_type`/`license` 未参与校验(`// TODO: valid code`),`// TODO: add your logic code` 仍保留 | +| `Store.MiniCode` | `logic/store/mini_code.go:18-22` | `// TODO` 后直接 `return`,`MiniCodeReply` 为 nil,调用方拿到空消息 | +| `Product.ItemDetailBySpec` | `logic/product/item_detail_by_spec.go:16-20` | `// TODO` 后 `return` 空 `ListItem` + nil error | +| `Freight.{Fetch,Create,Modify,Delete,Detail}` | `logic/freight/{fetch.go:26,create.go:24,modify.go:24,delete.go:25,detail.go:24}` | 5 个方法只做入参非空检查后返回固定 `Code:0 "OK"`(Fetch/Detail 直接裸 `return` 空 reply),`mall_freight`/`mall_freight_attr` 表无任何读写 | +| `Freight.{DenyRegionFetch,DenyRegionCreate,DenyRegionDelete,DenyRegionModify}` | `logic/freight/deny_region_{fetch.go:25,create.go:24,delete.go:24,modify.go:19-21}.go` | 同上,5 个方法全为空实现,`mall_freight_deny` 表无任何读写 | +| `Freight.Remove`、`Freight.DenyRegionRemove` | `logic/freight/remove.go:16`、`logic/freight/deny_region_remove.go:13` | **反向问题**:这两个方法写了真实删库逻辑,但 `pb.FreightServer` 接口只有 `DenyRegionDelete`/`DenyRegionModify`(`pb/freight_grpc.pb.go` 的 `FreightServer`),`internal/server/freight_server.go` 也只转调 `freight.Delete`/`freight.DenyRegionDelete` → 二者是不可达的死代码 | +| `models.InitData` | `internal/models/query.go:36` | 默认店铺 + `root` 账号的初始化函数全仓无调用点,入口不会执行 | + +其余残留 `// TODO: add your logic code & delete this line.` 但功能已实现的文件:`logic/product/item_modify.go:28`、`logic/product/spec_delete.go:33`、`logic/product/photo_delete.go:34`、`logic/notice/modify.go:27`、`logic/notice/fetch.go:45`、`logic/staff/fetch.go:49`。 + +## 4. 数据模型与表 + +16 张表全部经 GORM `AutoMigrate` 注册(模型 `init()` 调 `database.AppendMigrate`)。 + +| 表名 | 模型文件 | 主键 / 唯一键 | 说明 | +| --- | --- | --- | --- | +| `mall_store` | `internal/models/mall_store.go:15` | `id` PK;`identity` 唯一索引(`Std_IICUDS`) | 店铺;`pay_configs`/`mail_configs` 为 text | +| `mall_staff` | `internal/models/mall_staff.go:19` | `id` PK;`identity` 唯一索引;`phone` 唯一索引;`account` 普通索引 | 员工;`password` 存 bcrypt 串 | +| `mall_apply` | `internal/models/mall_apply.go:16` | `id` PK(`gorm.Model`) | 入驻申请;**无 identity 列,且全仓无任何读写代码** | +| `mall_product` | `internal/models/mall_product.go:17` | `id` PK;`identity` 唯一索引;`serial_id` 索引 | 商品主体 | +| `mall_product_spec` | `internal/models/mall_product_spec.go:11` | `id` PK;`identity` 唯一索引 | 规格;价格单位为分 | +| `product_spec` | `internal/models/mall_product_link_spec.go:9` | `id` PK;`identity` 唯一索引 | 商品-规格关联表(`spec_id`) | +| `product_category` | `internal/models/mall_product_link_category.go:9` | `id` PK;`identity` 唯一索引 | 商品-分类关联表 | +| `mall_product_photos` | `internal/models/mall_product_photos.go:17` | `id` PK;`identity` 唯一索引;`product_id` 索引 | 商品相册 | +| `mall_product_comment` | `internal/models/mall_product_comment.go:17` | `id` PK;`identity` 唯一索引 | 评论,自关联 `List`(`comment_identity`) | +| `mall_product_attr` | `internal/models/mall_product_attr.go:8` | `id` PK | 商品属性;**全仓无读写代码** | +| `mall_category` | `internal/models/mall_category.go:10` | `id` PK;`identity` 唯一索引 | 分类树(自关联 `Categories`) | +| `mall_ads` | `internal/models/mall_ads.go:8` | `id` PK;`identity` 唯一索引 | 广告 | +| `mall_notice` | `internal/models/mall_notice.go:8` | `id` PK;`identity` 唯一索引 | 公告 | +| `mall_freight` | `internal/models/mall_freight.go:10` | `id` PK;`identity` 唯一索引 | 运费模板;**无读写代码** | +| `mall_freight_attr` | `internal/models/mall_freight_attr.go:9` | `id` PK;`identity` 唯一索引 | 计费规则;**无读写代码** | +| `mall_freight_deny` | `internal/models/mall_freight_deny.go:10` | `id` PK;`identity` 唯一索引 | 禁运区域;**无读写代码** | + +### 表 `mall_store`(`internal/models/mall_store.go:15`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键(`Std_IICUDS`) | +| `identity` | varchar(36) | uniqueIndex | 店铺唯一标识(UUID) | +| `created_at`/`updated_at`/`deleted_at` | TIMESTAMP | `deleted_at` 索引 | 软删除 | +| `status` | int8 | default 0,索引 | 1 正常 / -1 禁止 | +| `logo`/`title` | varchar(255) | title `not null` | 店铺标识与名称 | +| `intro`/`template`/`keywords` | varchar(255) | default `''` | 简介、模板、SEO 关键词 | +| `subdomain` | varchar(255) | `not null` | 子域名,`Licensing`/`Search` 依赖 | +| `configs` | text | default `''` | 基础配置(JSON 字符串) | +| `pay_configs` | text | default `''` | **支付配置(含支付凭据)** | +| `mail_configs` | text | default `''` | **邮件服务器配置(含 SMTP 凭据)** | +| `approve` | int8 | default 0 | 0 待审 / 1 通过 / 2 失败 | + +### 表 `mall_staff`(`internal/models/mall_staff.go:19`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id`/`identity`/`created_at`/`updated_at`/`deleted_at`/`status` | 同 `Std_IICUDS` | `identity` 唯一索引 | — | +| `store_id` | uint | PK 成员(`Std_Store`) | 所属店铺 ID | +| `store_identity` | varchar(36) | 索引 | 所属店铺标识 | +| `name` | varchar(255) | 索引,default `''` | 姓名 | +| `account` | varchar(255) | **普通索引(非唯一)**,default `''` | 登录账号;跨店可重名 | +| `password` | varchar(255) | default `''` | bcrypt 哈希(`internal/password/password.go`) | +| `phone` | varchar(20) | uniqueIndex,default `''` | 手机号(验证码登录用) | +| `email`/`avatar`/`profile` | varchar(255) | default `''` | 邮箱、头像、简介 | +| `role` | varchar(36) | 索引,default `''` | `Mall_Admin` / `staff` | +| `salt` | varchar(255) | default `''` | 遗留盐字段,`SetPassword` 会显式清空(`logic/staff/set_password.go:32`) | +| `last_login_at` | TIMESTAMP | — | 最后登录时间 | + +### 表 `mall_product`(`internal/models/mall_product.go:17`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id`/`identity`/时间/`status` | 同 `Std_IICUDS` | `identity` 唯一索引 | `status`:1 上架 / 2 待上架 / 3 禁用 / 4 草稿 / 5 未上架 | +| `store_id`/`store_identity` | uint / varchar(36) | 索引用 | 所属店铺(冗余存储) | +| `serial_id` | varchar(36) | Index | 产品序号(`gas_by_kg`/`gas_by_bottle` 作特殊标识) | +| `title` | varchar(255) | `not null` | 商品标题 | +| `sub_title`/`measuring_name`/`brand`/`standard_number`/`putaway_time`/`notes`/`args`/`category_paths` | varchar(255) | default | 副标题、计量单位、品牌、标准号、上架时间、备注、参数 JSON、分类路径 | +| `sell_max`/`sell_min`/`wastage`/`stock_type`/`stock` | int32 | default | 限购、损耗率、库存计数方式、库存 | +| `supply_id`/`template_id`/`li_template_id`/`recent_number`/`limitation`/`sale_total` | int64 | default | 供应商、运费模板、限区模板、近 30 天销量、区域限制、销售总数 | +| `sales_price`/`cost_price`/`partner_per`/`star` | int64 | default | **售价/成本价/分成/评分(`sales_price` 本模块从不写入)** | +| `recommend`/`types`/`gas_types`/`comment_total`/`view_total`/`image_total`/`sort` | int8/int32 | default | 推荐位、商品类型、燃气类型、计数、排序 | +| `content` | text | default `''` | 商品详情 | +| 关联 | — | `foreignKey:ProductIdentity;references:Identity` | `Images`、`Specs`、`Categories`(`mall_product.go:58-60`) | + +### 表 `mall_product_spec`(`internal/models/mall_product_spec.go:11`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK(`gorm.Model`) | 自增主键 | +| `identity` | varchar(36) | 唯一索引 | 规格唯一标识(UUID) | +| `product_identity` 经 `product_spec` 关联 | — | `product_spec.spec_id` | 商品归属靠关联表表达 | +| `title` | varchar(255) | `not null` | 规格名称 | +| `stock_type` | int32 | default 2 | 1 设置库存限制 / 2 不限制 | +| `stock` | int64 | default 0 | 库存数量(订单侧扣减目标,`module/ec/order/internal/logic/summary/submit.go:209`) | +| `price` | int64 | default 0 | 销售价(分) | +| `direct_sales` | int64 | default 0 | 直营价(分) | +| `purchasing_price` | int64 | — | **进货价(分),被匿名接口返回** | +| `wholesale` | int64 | — | **代理价(分),被匿名接口返回** | +| `tp_type`/`ds_tp_type` | int32 | default 2 | 阶梯价开关 | +| `img`/`spec_no`/`keyword`/`serial_number` | varchar | default `''` | 预览图、规格编号、关键词、产品编号 | +| `store_identity` | varchar(36) | — | 店铺标识(由请求传入,见 6.1) | + +> 无身份维度的跨库读取:`logic/product/item_detail.go:31` 直接查 `market_supply`(market 模块的表);`logic/product/item_fetch.go:51-63` 直接 join `product_category`/`mall_category`/`product_spec`/`mall_product_spec`。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /mall.Staff/Login"] --> B["LoginGenre"] + B --> C{"1 账密"} + B --> D{"2 手机验证码"} + C --> E["按 account 查 mall_staff(无店铺维度)"] + E --> F["password.Verify(bcrypt)"] + D --> G["按 phone 查 mall_staff"] + G --> H["只校验手机号格式,从不校验 verify_code"] + F --> I["查店铺 status=1"] + H --> I + I --> J["encipher.GenerateTokenAes(Owner=Std_Store)"] + J --> K["更新 last_login_at 并返回 token"] +``` + +```mermaid +flowchart TD + A["POST /mall.Product/ItemCreate"] --> B["ParseMetaCtx 需 Mall_Admin"] + B --> C["checkParam(status=4 草稿可跳过)"] + C --> D{"identity 为空"} + D -->|是| E["itemToModel + 从 owner 取 store_id/store_identity"] + D -->|否| F["itemToModel(不回填 store 字段)"] + E --> G["CreateProduct:事务内写主表/规格/图片/分类"] + F --> H["ModifyProduct:先按 identity 查原记录,再事务内先删后建关联"] + G --> I["返回 IdentityStatusReply"] + H --> I + J["POST /mall.Product/ItemFetch"] --> K["第一次分页查询(结果被丢弃)"] + K --> L["按 category_id/keyword/价格区间/sort 组条件"] + L --> M["第二次分页查询 + Preload Images/Specs.Spec/Categories"] + M --> N["ModelsToProductItems 拼装返回"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/staff/login.go:45-58` | **手机验证码登录从不校验验证码**。`login_genre=2` 分支只检查 `phone`/`verify_code` 非空与手机号正则(`:46`),随后按手机号查库(`:50`)并签发 token(`:88`),校验验证码的位置只留 `// TODO: 添加验证码校验逻辑`(`:58`)。任意已注册手机号 + 任意非空 `verify_code` 即可取得该员工 JWT。 | +| **高** | `internal/logic/staff/fetch.go:17-20` | **鉴权代码被注释**:`Fetch` 的 `ParseMetaCtx` 整段注释掉,`/mall.Staff/Fetch` 匿名可用。入参只有 `store_identity`(`:28`),返回员工 `account`/`phone`/`email`/`role`/`status`(`:62-73`)——只需知道店铺标识即可批量拉取员工手机号与邮箱。 | +| **高** | `internal/logic/store/{get_payment.go:16,26,set_payment.go:17,26,get_email.go:15,25,set_email.go:17,26}` | 四处都用 `service.ParseMetaCtx(ctx, nil)`,只验 token 不验角色,且定位条件全部取自请求:读接口 `Where("id=? or identity=?")`、写接口 `Where("identity = ?")`(`set_payment.go:26`、`set_email.go:26`)。任意已登录账号(含 `role=staff`)可读/改**任意店铺**的 `pay_configs`/`mail_configs`。 | +| **高** | `internal/logic/store/get_setting.go:14-23` | `GetSetting` **完全没有鉴权调用**,入参 `id`/`identity` 任一非空即返回该店铺基础配置(含 `configs`)。 | +| **高** | `internal/logic/store/set_setting.go:17,34` | 解析出的 `auth` 被丢弃(`_`),更新条件用请求里的 `id/identity`,任意 `Mall_Admin` 可改他人店铺的名称、子域名、模板、`configs`。 | +| **高** | `internal/logic/product/{item_modify.go:29,item_batch_op.go:27,item_delete.go:28,spec_modify.go:34,spec_delete.go:28,photo_delete.go:28}`、`internal/logic/category/{delete.go:28,modify.go:30}`、`internal/logic/ads/delete.go:28`、`internal/logic/notice/delete.go:28`、`internal/logic/freight/remove.go:28`、`internal/logic/staff/delete.go:28` | **13 处写操作一律只按 `id`/`identity` 定位对象,从不带上 token 中的 `store_identity`**。同角色(`Mall_Admin`)可跨店铺修改/删除商品、规格、图片、评论、分类、广告、公告、运费模板、员工。反例参考:`internal/models/mall_product.go:74 ForbiddenProduct` 是唯一带 `Where("store_identity = ?")` 的批量更新实现,但没有任何调用点。 | +| **高** | `internal/logic/product/spec_detail.go:14-20`、`internal/logic/product/item_detail.go:14-24` | 两个接口**无鉴权**,返回体经 `internal/logic/product/ref.go:143-144`(`PurchasingPrice`/`Wholesale`)与 `:54`(`DirectSales`)带出进货价与代理价;`SpecDetail` 还直接透传 `PurchasingPrice`(`spec_detail.go:38`)。 | +| **高** | `internal/logic/product/spec_fetch.go:17,22,27` | 仅要求 `Mall_Admin` 与入参 `store_identity`,**不校验该店铺是否属于调用方**,一次请求即可批量拉走他店所有规格的价格、库存、进货价、代理价(无分页上限)。 | +| **高** | `internal/logic/store/licensing.go:15-38` | `Licensing` 只用 `domain` 查 `subdomain` 后返回店铺 `identity`(`:41-49`),请求中的许可码 `in.License` 从未参与校验(`:25` `// TODO: valid code`)。知道子域名即可换取店铺标识。 | +| 中 | `internal/logic/product/spec_create.go:82` | `StoreIdentity: in.StoreIdentity` 直接采信请求参数,规格所属店铺可被伪造;`item_create.go:104-109` 只在新建分支用 token 里的店铺字段,更新分支不回填。 | +| 中 | `internal/logic/product/item_detail.go:29-36` | 商品详情根据 `supply_id` 直查跨库表 `market_supply` 并回填 `SupplyName`,两个服务共享同一份库/表结构,无编译期约束。 | +| 中 | `internal/logic/staff/set_profile.go:31` | 业务查询上残留 `impl.DBService.Debug()`,会为该条 SQL 打开 GORM 调试日志(含参数)。 | +| 低 | `internal/logic/store/search.go:16-21` | 店铺搜索要求 `Mall_Admin`(`:18`),但结果集是跨店铺的店铺列表(`ModelToReply`,`:50-73`),角色语义不清。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/product/item_fetch.go:28,50-54` | `idList` 声明为空切片(`:28`)后从未把 `in.CategoryId` 填入,过滤条件变成 `product_category.category_id in ?`(空集合,GORM 生成 `IN (NULL)`)→ **按分类筛选时结果恒为空**。 | +| **高** | `internal/logic/product/item_fetch.go:60-64` | 价格区间:条件是 `if min != 0 || max != 0`,取值却固定 `between min and max`;只传 `min_price` 时 `max=0`,区间为 `[min,0]` 恒空。同时 join 规格表未去重,同一商品命中多条规格会重复出现在结果中。 | +| **高** | `internal/logic/product/item_fetch.go:42-46` 与 `:77-83` | 同一组分页条件被**执行了两次**(第一次 `Count`+`Find` 到 `product`,第二次整体覆盖),第一次查询的结果完全作废,DB 开销翻倍。 | +| **高** | `internal/logic/store/search.go:39` | `Where("keyword = ?", in.GetKeyword())`,而 `mall_store` 的列名是 `keywords`(`internal/models/mall_store.go:25`,`keyword` 是 `mall_product` 的列)→ 店铺搜索必然报 `Unknown column`,接口不可用。 | +| **高** | `internal/logic/product/spec_create.go:96-98` | `if in.StockType != 2 { specification.Stock = int64(in.StockType) }`:把"库存设置开关"(1 设置限制 / 2 不限制)当库存数量写入,客户端传 `stock_type=1` 时库存被覆盖为 1;真正的 `in.Stock` 已在 `:85` 赋过值,随后被这一行改写。 | +| **高** | `internal/logic/product/spec_create.go:37-40`、`internal/logic/product/photo_create.go:36-38` | `cnt, err := tx.RowsAffected, tx.Error; if cnt == 0 \|\| err != nil { printer.Error(err.Error()) }`:`cnt==0` 而 `err==nil` 时对 nil 调用 `err.Error()` → **panic**,服务 500。 | +| **高** | `internal/logic/product/item_detail_by_serial.go:22` | `Preload("Spec")`/`Preload("Category")` 与模型声明的关联名 `Specs`/`Categories`(`internal/models/mall_product.go:59-60`)不一致。GORM 无法解析未声明的关联(unsupported relation),规格与分类不会加载且查询直接报错 → 该接口目前无法正常返回数据(对比同模块 `item_detail.go:20` 用的是正确名 `Specs.Spec`)。 | +| 中 | `internal/logic/product/comment_modify.go:22,31` | 校验要求 `comment` 与 `nick_name` 非空(`:22`),但 `Select("score","reply","product_identity","store_identity","spec_identity")` 把这两列排除在更新范围外 → 评论正文和昵称永远改不了。 | +| 中 | `internal/logic/product/comment_re.go:24,31` | 要求 `comment_identity` 非空(`:24`),写入时却用 `CommentIdentity: in.Identity`(`:31`)→ 被回复评论标识写成了回复自身。 | +| 中 | `internal/logic/product/comment_delete.go:28` | `Where("id=? or identity=? or comment_identity=?", in.GetId(), in.GetIdentity(), in.GetIdentity())`,第三个条件把同一个 identity 当作 `comment_identity`,一次删除会连带删掉"以它为被回复对象"的所有回复。 | +| 中 | `internal/logic/ads/modify.go:35-38`、`internal/logic/notice/modify.go:32-34` | 两处都在结构体里设置 `Status`,但 `Select` 白名单不含 `status`(ads 为 `title,pos_key,content,type,to_url`,notice 为 `title,author,content`)→ 广告/公告的启用与禁用实际不生效。 | +| 中 | `internal/logic/store/get_setting.go:26-38` | 返回体未设置 `Status`(proto `StoreBasic.status` 恒为 0,`proto/store.proto:86`),前端无法区分店铺是否停用。 | +| 中 | `internal/logic/staff/login.go:71,79-81` | 查询已带 `status = 1`,随后的 `if storeData.Status == 0` 永不成立(死判断);`login.go:32` 按 `account` 查而不带店铺维度,`mall_staff.account` 只是普通索引(`internal/models/mall_staff.go:23`)而非唯一键 → 跨店同名账号时取到任意一条记录,可能落到错误店铺。 | +| 中 | `internal/logic/product/item_create.go:38-44`、`internal/models/mall_product.go:153` | 更新走 `ModifyProduct`,其 `tx.Where("identity = ?", product.Identity).Updates(product)` 传的是结构体,GORM 跳过零值字段 → 已填字段无法清空(如把 `sell_max` 改回 0、把 `notes` 清空)。 | +| 中 | `internal/logic/category/fetch.go:19-24`、`internal/models/query.go:75` | `types`(`all`/`first`)只影响是否递归子集,`GetCategoryList` 恒以 `parent_id = 0` 过滤顶层,两个取值语义无差别;`types=first` 的层级语义未落地。 | +| 中 | `internal/logic/ads/by_pos.go:20` | `Find(&result)` 的目标是 `[]*pb.AdsItem`(protobuf 结构体),直接让 GORM 把 `mall_ads` 行扫进 pb 类型,字段映射依赖命名约定而非显式列;同模块其它查询都先落 `models.MallAds` 再 `ref()` 转换(`ads/fetch.go:39-49`)。 | +| 低 | `internal/logic/product/item_modify.go:24` | `if in.GetIdentity() == "" \|\| in.GetStatus() == 0` 拒绝 `status=0`,但 proto 未定义 0,实际等价于"必须传 status";上传未上架时无法回退到 0。 | +| 低 | 多处 | 时间戳单位混用:`item_create.go:55` 用 `UnixMilli()`,`ads/create.go:51`、`category/modify.go:40`、`freight/create.go:29`、`freight/deny_region_*` 用 `Unix()`(秒),`item_delete.go:37`、`photo_delete.go:39` 用 `UnixNano()`。 | +| 低 | `internal/logic/store/get_payment.go:26`、`internal/logic/store/get_email.go:25` | 用 `Find` 而非 `First`:查不到店铺时返回空 `Configs` 而不报错,调用方无法区分"无配置"与"店铺不存在"。 | + +### 6.3 未完成实现 + +完整占位清单见第 3 节末尾表格。归纳: + +- **运费与禁运区域整体未实现**:`Freight` 服务 9 个 RPC 中 9 个为空实现(`Create`/`Modify`/`Delete`/`Detail`/`Fetch`/`DenyRegion*`),`mall_freight`、`mall_freight_attr`、`mall_freight_deny` 三张表建好了但无任何读写代码。前端调用会拿到 `Code:0 "OK"` 的假成功。 +- **入驻与授权**:`Store.ApplyJoin` 返回假成功(`logic/store/apply_join.go:19-27`),`mall_apply` 表全仓无读写;`Store.Licensing` 的许可码校验缺失(`logic/store/licensing.go:25`)。 +- **小程序码**:`Store.MiniCode` 为空(`logic/store/mini_code.go:18-22`),返回 nil reply。 +- **按规格查询**:`Product.ItemDetailBySpec` 为空(`logic/product/item_detail_by_spec.go:16-20`)。 +- **批量操作**:`Product.ItemBatchOp` 已实现,但只用 `Where("identity in ?")`(`logic/product/item_batch_op.go:27`),已有的 `ForbiddenProduct`(带店铺约束,`internal/models/mall_product.go:74`)未被接线。 +- **反向未接线**:`freight/remove.go:16`、`freight/deny_region_remove.go:13` 实现完整但不在 proto 服务接口中(`pb/freight_grpc.pb.go` 的 `FreightServer` 只有 `DenyRegionFetch/Create/Delete/Modify`),`internal/server/freight_server.go` 未转调 → 死代码。 +- **初始化未接线**:`internal/models/query.go:36 InitData`(建默认店铺 + `root/123456` 管理员)无调用点。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/config/config.go:38` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`,而 `internal/impl/impl.go:26` 直接用 `config.Spec.Databases`;漏配时 DB 为 nil,首个查询即空指针。 | +| 中 | `internal/server/new.go:26-30` + `cmd/main/main.go:35` | `Server` 结构体有 `Mux` 字段(`new.go:16`)但 `New()` 从未初始化,`main.go:35` 把它作为 `GatewayMux` 传给 SDK;`Server.Mux` 为 nil 时 SDK 的 `http.ListenAndServe(addr, mux)`(`bsm-sdk/core/service/service.go:126`)实际挂的是 `http.DefaultServeMux`,`service/expose.go:23-43` 注册的 8 组 HTTP 路由在独立进程里不会生效(全部 404)。 | +| 中 | `internal/logic/store/search.go:34`、`internal/logic/ads/fetch.go:34`、`internal/logic/notice/fetch.go:35`、`internal/logic/product/comment_fetch.go:38`、`internal/logic/staff/fetch.go:37` | 分页一律写成 `if pageSize < 50 { pageSize = 50 }`,**只有下限没有上限**;`item_fetch.go:37` 只在 `<= 0` 时兜底 10。客户端传 `page_size=1000000` 即触发全表扫描与超大响应。 | +| 中 | `internal/logic/store/{get_setting.go:21,set_setting.go:35,get_payment.go:27,get_email.go:26}` | 错误上报用 Go 内置 `print(err.Error())`(写 stderr、无级别、无结构),与同模块其它位置的 `printer.Error` 混用。 | +| 中 | `internal/models/mall_product.go:74,80,206,249`、`internal/models/mall_product_spec.go:41,48,58`、`internal/models/mall_product_photos.go:38,48`、`internal/models/mall_product_comment.go:46,52,58,64` | 13 个模型层函数全仓无调用者(`ForbiddenProduct`、`ProductDetail`、`DelProduct`、`GetProductList`、`DetailSpec`、`SpecList`、`DelSpec`、`PhotoList`、`DelPhoto`、`DelComment`、`CreateComment`、`ReComment`、`ModifyComment`)。其中 `DelProduct`(事务级联删评论/图片/规格/分类)比实际使用的单表删除(`item_delete.go:28`)更完整,却没有接线。 | +| 中 | `internal/logic/product/item_detail.go:35` | `reply.SupplyName = supply["name"].(string)` 无保护类型断言,且 `supply` 来自跨库表 `market_supply`;该列为 NULL 或类型变化即 panic。 | +| 低 | `internal/impl/impl.go:22` + `service/dependencies.go:30` | `MemorySerice` 拼写错误(应为 `MemoryService`),且除依赖注入赋值外全模块无使用点。 | +| 低 | 全模块 | 仅 1 个测试文件 `internal/password/password_test.go`(bcrypt 正反例);`test/readme.md` 仅一行 "restful test";67 个 logic 文件 0 测试。 | +| 低 | `cmd/cli/main.go:6` | CLI 入口只打印 "Hello World!"。 | +| 低 | `README.md:39,219-229,375-389` 与 `etc/mall_prod.yaml:2,5-7,24` | README 要求 MySQL 8.0+、端口写 `12101`/`12102`,实际配置是 `Driver: postgres`、`Port: 12420`、`Gateway.Port: 12419`;README 的"缓存策略表/连接池/读写分离"在代码中不存在(`impl.go:24-28` 只建了连接,无缓存读写)。 | +| 低 | `etc/mall_dev.yaml:19-20`(prod/test 同) | `Anonymous: - mall.ping.hello`,但 8 个 proto 文件与全部 gw 路由中都不存在 `ping` 方法,是死配置。 | +| 低 | `internal/logic/staff/login.go:40` | 注释写"使用MD5加盐验证",实际调用 `password.Verify`(bcrypt,`:41`)。**全仓已无 MD5 实现**(`md5` 仅此一处注释命中);但历史以 MD5 入库的账号 `bcrypt.CompareHashAndPassword` 必然失败,等于无法登录,且 `mall_staff.salt` 字段(`mall_staff.go:30`)无迁移处理。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| M1 | 高 | 手机验证码登录不校验验证码 | 任意账号接管 | +| M2 | 高 | 支付/邮件配置可被任意已登录用户读改(4 个接口 + 无鉴权的 GetSetting) | 店铺资金与邮件凭据泄露、被篡改 | +| M3 | 高 | 12 处写操作无店铺归属校验(商品/规格/图片/分类/广告/公告/运费/员工),评论的改删另见 M13 | 跨店数据被改删 | +| M4 | 高 | `Staff.Fetch` 鉴权被注释 | 员工手机号/邮箱批量泄露 | +| M5 | 高 | 进货价/代理价经无鉴权的 `ItemDetail`、`SpecDetail` 与无归属校验的 `SpecFetch` 外泄 | 商业成本信息泄露 | +| M6 | 高 | `Licensing` 不校验许可码 | 店铺标识可被枚举获取 | +| M7 | 高 | `item_fetch` 分类过滤恒空、价格区间单边失效、重复查询 | 商品列表功能不可用 + DB 压力翻倍 | +| M8 | 高 | `store/search.go:39` 列名错误 | 店铺搜索完全不可用 | +| M9 | 高 | `spec_create.go:96-98` 库存被写为 `stock_type` | 库存数据错误、影响下单扣减 | +| M10 | 高 | `spec_create.go:38`、`photo_create.go:37` nil error 调 `Error()` | 服务 panic | +| M11 | 高 | `item_detail_by_serial.go:22` Preload 关联名不存在 | 接口不可用 | +| M12 | 高 | 12 个接口空实现/假成功(运费 9 个 + 入驻 + 小程序码 + 按规格查询) | 业务功能缺失且无错误反馈 | +| M13 | 中 | 评论修改/回复/删除的目标字段写错或被 `Select` 排除 | 评论功能行为异常、误删 | +| M14 | 中 | 广告/公告状态更新被 `Select` 白名单排除 | 启停失效 | +| M15 | 中 | 登录跨店同名账号、店铺状态死判断 | 登录归属不确定 | +| M16 | 中 | GORM 分页无上限、`print` 上报、死代码、无测试 | 可维护性与抗压能力 | +| M17 | 中 | `Databases` 未校验、`Server.Mux` 未初始化 | 启动后运行期崩溃 / 独立进程 HTTP 全 404 | +| M18 | 低 | README 与配置不符、`Anonymous` 死配置、MD5 注释残留 | 认知成本与迁移风险 | + +## 8. 修复建议(务实项) + +1. **补验证码校验**(M1):`login.go:58` 的 TODO 必须落地——按 `phone` 取出服务端存的一次性验证码,比对 `in.VerifyCode` 及其有效期,成功后立即删除;在此之前建议直接对该分支返回 `ErrUnimplemented`,不要签发 token。 +2. **恢复 `Staff.Fetch` 鉴权并收敛返回字段**(M4):取消 `fetch.go:17-20` 的注释,改为 `&service.ParseOptions{RoleValue: "Mall_Admin"}`,并把 `store_identity` 从入参改为取 token 中的 `owner`,`ModelToReply` 去掉 `phone`/`email`(或按需脱敏)。 +3. **给所有写操作补店铺归属约束**(M3):统一在 `Where` 后追加 `store_identity = ?`(值取自 `auth.Owner`)。已有现成范例:`internal/models/mall_product.go:75` 的 `ForbiddenProduct`,把 `item_batch_op.go:27`、`item_modify.go:29`、`item_delete.go:28`、`spec_*`、`photo_delete.go:28`、分类/广告/公告/员工的对应 `Delete`/`Modify`/`Updates` 都对齐这个写法;`SpecCreate` 的 `StoreIdentity`(`spec_create.go:82`)改为从 token 取。 +4. **收口配置类接口**(M2):`get_payment`/`set_payment`/`get_email`/`set_email` 把 `ParseMetaCtx(ctx, nil)` 改成 `RoleValue: "Mall_Admin"`,并把 `identity` 换成 token 中的 `store_identity`(或校验两者相等后拒绝不匹配);`GetSetting` 补上与 `SetSetting` 相同的鉴权;返回 `pay_configs`/`mail_configs` 时对凭据字段做掩码。 +5. **`item_fetch` 三处修复**(M7):`item_fetch.go:28` 改为 `idList = in.GetCategoryId()` 并删掉 `:42-46` 的第一次查询;价格过滤拆成"只传 min / 只传 max / 都传"三分支;join 规格后加 `Group("mall_product.id")` 或改用 `DISTINCT`。 +6. **`store/search.go:39` 改列名**:`Where("keywords like ?", "%"+in.GetKeyword()+"%")`(或按业务决定不加过滤),并补 `page_size` 上限。 +7. **`spec_create.go` 修正**:删除 `:96-98` 这行,库存只认 `in.Stock`;把 `:38-40` 与 `photo_create.go:37-39` 的 `err.Error()` 改成先判 `err != nil` 再打印(或直接 `return errcode.ErrDB`)。 +8. **`item_detail_by_serial.go:22` 改关联名**:`Preload("Specs.Spec")`、`Preload("Categories.Category")`,与 `item_detail.go:20` 保持一致。 +9. **`comment_modify.go:31`**:把 `comment`、`nickname` 加进 `Select` 白名单;`comment_re.go:31` 改为 `CommentIdentity: in.GetCommentIdentity()`;`comment_delete.go:28` 去掉第三个 `comment_identity=?` 条件或改用独立入参。 +10. **`ads/modify.go:38`、`notice/modify.go:34`**:把 `status` 加进 `Select`,或改用 `map[string]any` 显式指定要更新的列。 +11. **占位接口二选一**:`Freight` 的 9 个方法与 `ApplyJoin`/`MiniCode`/`ItemDetailBySpec` 要么按 proto 实现,要么暂时返回 `errcode.ErrUnimplemented`,不要返回 `Code:0 "OK"`;同时把 `freight/remove.go`、`freight/deny_region_remove.go` 接到 `pb.FreightServer` 上(或删除,避免两份实现并存)。 +12. **配置与启动**:`config.go:38` 的 `conf.NotNil` 补上 `Databases`;`internal/server/new.go` 初始化 `Mux: gwRuntime.NewServeMux()`,否则独立进程的 HTTP 入口无任何路由。 +13. **分页上限**:在 `search.go:34`、`ads/fetch.go:34`、`notice/fetch.go:35`、`comment_fetch.go:38`、`staff/fetch.go:37`、`item_fetch.go:37` 统一补 `if pageSize > 200 { pageSize = 200 }`。 +14. **清理与测试**:删除 `set_profile.go:31` 的 `.Debug()`、`get_setting.go` 等 4 处 `print(err.Error())` 与 6 处 TODO 残留;为 `login`(含验证码分支)、`item_fetch`、`spec_create`、`item_create` 各补 1 条正例 + 1 条边界用例;README 的驱动与端口按 `etc/*.yaml` 更新。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。仓储里"为 models 层加统一查询构造器""引入 DTO/VO 转换层""把跨库表访问抽象为 RPC 客户端"一类改造不在此列。 diff --git a/docs/market.md b/docs/market.md new file mode 100644 index 0000000..9e8513d --- /dev/null +++ b/docs/market.md @@ -0,0 +1,233 @@ +# market(代理商与供应商管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/ec/market` | +| 服务域 | 电商域(EC)——渠道侧(代理商 / 供应商)与渠道经营数据 | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 49 个:`pb/` 生成代码 10 个 7125 行;非 `pb/` 39 个 1569 行(其中 protoc-gen-slc 生成骨架 4 个 185 行、测试 1 个 13 行、手写类型别名 1 个 3 行)→ 业务手写约 1368 行。`proto/` 4 个 510 行;`etc/` 4 个 123 行;`sdk/typescript/` 5 个 2465 行;`test/*.http` 10 个 81 行;`README.md` 3 行 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、`cmd/cli`(脚手架占位)、聚合入口 `pkgs/all` 经 `service.Expose` 复用共享 gRPC/Gateway | +| 对外协议 | gRPC + HTTP Gateway,路径 `/market./`,全部 POST | +| 结论摘要 | **审核流程与账号生命周期完全未被保护**:`Approve` 全函数没有任何鉴权调用,`Agency.Create`/`Agency.Delete`/`Supply.Create` 的鉴权被整段注释;`Get`/`Modify`/`SetPassword` 用请求里的 `identity` 覆盖 token 里的 `identity`,可读改任意代理商资料;登录签发的 AES 密文与所有受保护接口使用的 JWT 校验**互不兼容**(合法登录后无法调用任何接口,且可触发 SDK 空指针)、`Data` 服务 5 个方法全部是返回 `nil` 的占位、分页存在 `Offset` 早于归一化与 `LIMIT 0` 两处确定性缺陷、列表返回的 `count` 是本页条数。密码已统一 bcrypt(无 MD5 代码),但 `salt` 列残留且无历史哈希兼容分支。 | + +## 1. 服务定位与职责 + +渠道(代理商 / 供应商)主数据与渠道经营数据的对外服务,由三个 gRPC service 组成(`internal/server/new.go:33-35`): + +- `Agency`(`proto/agency.proto:7-35`):代理商(省代公司)登录、建号、增删改查、改密、待审核列表、审核; +- `Supply`(`proto/supply.proto:7-24`):供应商建号、增删改查; +- `Data`(`proto/data.proto:7-22`):代理商视角的概况/会员/订单统计(**当前全部为占位**)。 + +它是渠道账号与资质的**管理中心**:账号体系是自建的一套(`market_agency` / `market_supply` 两张表各自存 `account`/`password`,与 base/passport 的会员体系无关);审核(`approve` 0 待审 / 2 通过 / -2 拒绝)是登录前置条件(`login.go:44`)。它不负责佣金结算、不负责订单归属计算,也没有与订单/会员表的联动(`data` 服务为空实现,见 6.3)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New` → `impl.NewImpl` → `server.New(nil)` → `service.New` → `srv.Start` | +| `cmd/cli/main.go` | 脚手架占位,仅 `log.Println("Hello World!")`(`cmd/cli/main.go:5-7`) | +| `internal/config/config.go` | 配置结构(Base、Databases、MicroService、Rpc、Gateway、Apm、Etcd)与校验 | +| `internal/impl/impl.go` / `with.go` | Redis / DB / Etcd 初始化(`RedisCache`/`Etcd`/`DBService`) | +| `internal/models/market_agency.go` | `MarketAgency` 模型(代理商) | +| `internal/models/market_supply.go` | `MarketSupply` 模型(供应商) | +| `internal/models/impl.go` | GORM 连接与 `AutoMigrate`(两张表) | +| `internal/models/query.go` | `NormalStatus`/`DisabledStatus` 常量 + 写入 token 的 `Std_Market` 结构 | +| `internal/password/password.go` | bcrypt `Hash`/`Verify`(12 行,唯一有单元测试的包) | +| `internal/server/{new,agency_server,supply_server,data_server}.go` | 生成骨架:注册 3 个 gRPC service、按 service 转发到 logic | +| `internal/logic/agency/*.go`(10 个) | 登录/建号/查/列表/改/删/改密/待审/审核/字段映射 | +| `internal/logic/supply/*.go`(6 个) | 供应商建号/查/列表/改/删/字段映射 | +| `internal/logic/data/*.go`(5 个) | 概况/会员列表/会员详情/订单列表/订单详情(**全部占位**) | +| `service/expose.go` / `dependencies.go` | 聚合宿主注入(`applyDependencies` + 3 个 Handler 注册) | +| `proto/*.proto` | 4 个契约文件(`const.proto` 为跨模块复制的 blocks 定义) | +| `test/agency/*.http`、`test/supply/fetch.http` | 10 个手工请求脚本(非自动化测试) | + +依赖注入:`service.Dependencies`(`service/dependencies.go:12-17`)非 nil 时覆盖 `internal/impl` 包级变量,聚合实例见 `pkgs/all/internal/service/market.go:10-19`;同样地,`Dependencies.Cache` 被声明但 `applyDependencies`(`:19-29`)忽略。 + +## 3. 接口清单 + +全部接口注册在 grpc-gateway 的 POST 路由上(Agency:`pb/agency.pb.gw.go:287,307,327,347,367,387,407,427,447`;Supply:`pb/supply.pb.gw.go:408-412`;Data:`pb/data.pb.gw.go:410` 等): + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/market.Agency/Login` | 代理商登录,签发 token | **无**(仅校验入参非空) | `internal/logic/agency/login.go:19` | +| POST | `/market.Agency/Create` | 新增代理商 | **无(鉴权被注释掉)** | `internal/logic/agency/create.go:19`(`:20-23` 注释) | +| POST | `/market.Agency/Get` | 取代理商 | token + **identity 可被请求覆盖** | `internal/logic/agency/get.go:16`(`:25-27`) | +| POST | `/market.Agency/Fetch` | 代理商列表 | token(不校验角色/归属) | `internal/logic/agency/fetch.go:16` | +| POST | `/market.Agency/Modify` | 改代理商 | token + **identity 可被请求覆盖** | `internal/logic/agency/modify.go:16`(`:25-27`) | +| POST | `/market.Agency/Delete` | 删代理商 | **无(鉴权被注释掉)** | `internal/logic/agency/delete.go:16`(`:17-21` 注释) | +| POST | `/market.Agency/SetPassword` | 改密码 | token + identity 可覆盖 + 需旧密码 | `internal/logic/agency/set_password.go:19`(`:25-27,37`) | +| POST | `/market.Agency/Pending` | 待审核列表 | token(不校验角色) | `internal/logic/agency/pending.go:16` | +| POST | `/market.Agency/Approve` | 审核(通过/拒绝) | **无(全函数无任何鉴权调用)** | `internal/logic/agency/approve.go:15` | +| POST | `/market.Supply/Create` | 新增供应商 | **无(鉴权被注释掉)** | `internal/logic/supply/create.go:18`(`:19-23` 注释) | +| POST | `/market.Supply/Get` | 取供应商 | token(按请求 identity 查) | `internal/logic/supply/get.go:15` | +| POST | `/market.Supply/Fetch` | 供应商列表 | token(不校验角色) | `internal/logic/supply/fetch.go:16` | +| POST | `/market.Supply/Modify` | 改供应商 | token(按请求 identity 改) | `internal/logic/supply/modify.go:17` | +| POST | `/market.Supply/Delete` | 删供应商 | token(按请求 id/identity 删) | `internal/logic/supply/delete.go:17` | +| POST | `/market.Data/Overview` | 概况统计 | token,**实现为空** | `internal/logic/data/overview.go:11` | +| POST | `/market.Data/MemberFetch` | 会员列表 | token,**实现为空** | `internal/logic/data/member_fetch.go:11` | +| POST | `/market.Data/MemberDetails` | 会员详情 | token,**实现为空** | `internal/logic/data/member_details.go:12` | +| POST | `/market.Data/OrderFetch` | 订单列表 | token,**实现为空** | `internal/logic/data/order_fetch.go:11` | +| POST | `/market.Data/OrderDetails` | 订单详情 | token,**实现为空** | `internal/logic/data/order_details.go:12` | + +补充说明: + +- **声明但未实现/占位(已核实)**:`Data` 服务的 5 个方法**全部是骨架**——`overview.go:18-22`、`member_fetch.go:26-28`、`member_details.go:24-26`、`order_fetch.go:26-28`、`order_details.go:24-26` 都是"校验 token(+ 归一化分页参数)→ `// TODO: add your logic code & delete this line.` → `return`",命名返回值 `reply` 从未赋值,实际返回 `(nil, nil)`。其中 `overview.go:18` 还额外留着 `// TODO: valid code`。**不是"疑似",是确认的空实现**,`proto/data.proto:24-39` 里的 `OverviewReply`(4 个计数 + 2 组按月报表)与 `DataReply`/`KeyVal` 永远不会被填充。 +- 上表"无"的鉴权,指的是 `internal/logic` 层没有任何校验(`ParseMetaCtx` 被注释或根本不存在)。聚合入口 `pkgs/all` 额外安装了 gRPC 拦截器与 HTTP 中间件(`pkgs/all/internal/server/authorization.go:42-53,55-66`),按 `Authorization.Anonymous` 白名单放行、其余要求一个合法 JWT —— 因此聚合部署下"无鉴权"退化为"**任意合法 JWT 均可**"(不校验角色、不校验归属);独立进程部署(本模块 `cmd/main`,`internal/server/new.go:23` 的 `grpc.NewServer()` 无拦截器)下则是彻底匿名可用。`Authorization.Anonymous` 白名单文件不在仓库内(`pkgs/all/etc/` 不存在)→【信息不足】无法确认聚合部署下哪些方法被列为匿名。 + +## 4. 数据模型与表 + +### 表 `market_agency`(`internal/models/market_agency.go:11-37`,GORM 自动迁移 `internal/models/impl.go:15-18,39`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `identity` | varchar(36) | uniqueIndex | 唯一标识,写入 `utils.ULID()`(`create.go:32`) | +| `created_at` / `updated_at` | TIMESTAMP | - | 时间戳 | +| `deleted_at` | TIMESTAMP | index | GORM 软删除列 | +| `status` | int8 | default 0, index | -1 禁用(`models/query.go:7`)/ 1 正常(`create.go:32` 硬编码) | +| `name` | varchar(255) | **not null** | 联系人名称 | +| `avatar` | varchar(255) | default '' | 头像 | +| `account` | varchar(255) | default '',**无唯一索引** | 登录账号(登录按此字段查,`login.go:26`) | +| `phone` | varchar(20) | **not null** | 手机号(无格式约束) | +| `password` | varchar(255) | **无 not null** | bcrypt 哈希(`create.go:27`) | +| `salt` | varchar(255) | default '' | 历史盐字段:无读取点,`set_password.go:47` 还在显式写 `""` | +| `email` | varchar(255) | default '' | 邮箱 | +| `country` / `area` | varchar(255) | default '' | 国家 / 地区 | +| `org_name` / `org_photo` | varchar(255) | default '' | 机构名称 / 照片 | +| `id_name` / `id_before` / `id_after` | varchar(255) | default '' | 证件姓名 / 正面照 / 反面照 | +| `remark` | varchar(255) | default '' | 备注 | +| `commission_rate` | int32 | default 0 | 佣金比例(**无范围约束**,可写负数或超大值) | +| `agency_type` | int32 | default 0 | 1 代理商 / 2 安装商(无校验) | +| `approve` | int8 | default 0 | 0 待审 / 2 通过 / -2 拒绝(`login.go:44` 强制 `==2` 才能登录) | +| `last_login_at` | TIMESTAMP | - | 无 `gorm` tag,按 GORM 命名推断为 `last_login_at`;登录时用 `Select("last_login_at")` 单独更新(`login.go:60`) | + +### 表 `market_supply`(`internal/models/market_supply.go:10-32`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` / `created_at` / `updated_at` / `deleted_at` / `status` | 同上 | 同 `market_agency` | 结构一致 | +| `name` | varchar(255) | not null | 联系人名称 | +| `avatar` / `account` | varchar | default '' | 头像 / 账号(`Create` 中 `Avatar` 被注释掉,见 `supply/create.go:34`,永远写不进去) | +| `phone` | varchar(20) | not null | 手机号 | +| `password` | varchar(255) | **not null** | bcrypt 哈希 | +| `salt` | varchar(255) | default '' | 同上,无使用点 | +| `org_name` / `org_photo` / `id_name` / `id_before` / `id_after` / `remark` | varchar(255) | default '' | 机构与证件信息 | +| `commission_rate` | int32 | default 0 | 佣金比例(无范围约束) | +| `approve` | int8 | default 0 | 0 / 1 审核中 / 2 / -2(注释比代理商表多一档"1 为审核中",但**没有任何代码写 1**) | + +两张表的共同缺口:`account` 无唯一索引(可重复注册同名账号,见 6.2);`status`、`approve`、`commission_rate`、`agency_type` 均无 check 约束;`password` 长度未按 bcrypt 输出(60 字符)收紧到 60。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /market.Agency/Login"] --> B["按 account 查 market_agency 取第一条"] + B --> C["password.Verify 走 bcrypt"] + C --> D["校验 status 不等于 -1 且 approve 等于 2"] + D --> E["encipher.GenerateTokenAes 签发 AES-CBC 密文"] + E --> F["Select last_login_at 更新并返回 token 与账号信息"] + G["后续任意受保护接口"] --> H["ParseMetaCtx 转 token.ParseJwt 期望 HS256 JWT"] + H --> I["AES 密文非三段 JWT,校验必然失败,且 SDK 在解析失败后访问 nil token 会 panic"] + J["POST /market.Agency/Create"] --> K["鉴权被注释,bcrypt 建号 Status=1 approve=0"] + L["POST /market.Agency/Approve"] --> M["全函数无鉴权,按 identity 直接改 approve=2 或 -2"] + K --> N["approve 必须为 2 才允许登录"] + M --> N + O["POST /market.Agency/Get 或 Modify 或 SetPassword"] --> P["identity 用请求值覆盖 token 值"] + P --> Q["按覆盖后的 identity 读或改任意代理商记录"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/agency/approve.go:15-46` | **审核接口完全无鉴权**。整个函数没有任何 `service.ParseMetaCtx` 调用(对比同目录 `get.go:20`、`fetch.go:22`、`modify.go:20` 都调用了;`Login` 不调用是合理的,因为它就是登录入口),只校验 `identity` 非空(`:17-19`)。任何人只要能把请求送到(独立部署时直连 gRPC 端口即可),就能把任意代理商 `approve` 改成 2(通过,`:29`)或 -2(拒绝,`:34`)。审核是登录的唯一前置条件(`login.go:44`),此接口失守等于渠道准入体系失守。 | +| **高** | `internal/logic/agency/create.go:20-23`、`internal/logic/supply/create.go:19-23` | **建号鉴权被整段注释**:两处的 `// parse authorization meta.` 后跟 `// _, err = service.ParseMetaCtx(ctx, nil)` 及其错误分支都被注释掉,函数直接进入参数校验。配合 `create.go:32` 硬编码 `Status: 1`,任何人都能创建可用的代理商/供应商账号;再配合上一条,任何人都能自己把自己审核通过。 | +| **高** | `internal/logic/agency/delete.go:17-21,26` | **删除接口鉴权同样被注释**,且删除条件为 `Where("id = ? or identity = ?", in.GetId(), in.GetIdentity())`(`:26`),两个条件任一命中即删。匿名调用即可软删除任意代理商(`deleted_at` 置位后该账号无法登录,等同封号),也支持传 `id` 批量打击。 | +| **高** | `internal/logic/agency/get.go:25-27`、`modify.go:25-27`、`set_password.go:25-27` | **请求参数覆盖 token 身份**:三处都是 `identity = auth.Identity; if in.GetIdentity() != "" { identity = in.GetIdentity() }`,随后的查询/更新(`get.go:29`、`modify.go:45`、`set_password.go:33`)全部使用被覆盖后的 `identity`。任意已登录账号传一个他人的 `identity` 即可:`Get` 读出对方 `id_name`/证件照/`phone`/`email`(`get.go:33-52` 全字段返回)、`Modify` 改掉对方 `account`(登录名)、`phone`、`email`、`commission_rate`、`org_name`(`modify.go:28-45`)。`SetPassword` 因需旧密码(`:37`)危害较小,但仍可对任意目标发起撞库式改密。 | +| **高** | `internal/logic/agency/fetch.go:16-52`、`pending.go:16-47` | **列表接口只验"token 有效"、不验角色、不按归属过滤**:`fetch.go:22`、`pending.go:17` 都是 `service.ParseMetaCtx(ctx, nil)`,无任何 `Where` 限定调用方。任何持合法 JWT 的调用方(含商城会员等其他业务域角色;独立进程部署下连 JWT 都不需要)都能一次拉走**全部代理商**(含待审核)的 `phone`/`email`/`id_name`/证件照 URL,且 `fetch.go:30-32` 只把 `page_size < 10` 归一到 50、**没有上限**,传 `page_size=1000000` 即全量导出。 | +| **高** | `internal/logic/agency/login.go:53` + SDK `crypto/token/jwt.go:64-73`、`golang-jwt/jwt/v5@v5.3.1/parser.go:138-140` | **登录签发的令牌无法被任何接口接受,且会触发空指针 panic**。`Login` 用 `encipher.GenerateTokenAes` 生成的是 **AES-CBC 密文的 base64**(SDK `crypto/encipher/encipher.go:29-54`),而 `service.ParseMetaCtx` 走的是 **HS256 JWT 解析**(SDK `service/meta.go:31` → `token.ParseJwt`)。base64 密文里没有点号,`jwt.ParseWithClaims` 在第一段就返回 `nil, ErrTokenMalformed`(parser.go:139-140),而 SDK 随后执行 `if claims, ok := token.Claims.(*Claims); ...`(jwt.go:68),**在 nil 上取字段 → 空指针 panic**;模块 `internal/server/new.go:23` 的 `grpc.NewServer()` 未装 recover 拦截器,goroutine panic 会终止进程。两个后果:① 合法登录的代理商调用任何受保护接口都失败(聚合入口的 `authorization.validate` 同样只认 JWT,`pkgs/all/internal/server/authorization.go:73-85`);② 任何人向 gRPC 端口发一个 `Authorization: x` 就能让进程崩溃。 | +| 中 | `internal/logic/agency/create.go:50,52` | `fmt.Println("mktModel = ", mktModel)` 把**包含 bcrypt 密码哈希的完整模型**打印到 stdout(`:52` 还打印 DB 错误)。生产由 supervisor 托管,stdout 会落到 `stdout_logfile`(`etc/supervisor.bsm-ec-market.conf:8`),等于把口令哈希写进日志文件。 | +| 中 | `internal/logic/agency/login.go:22-46` | 登录接口无失败次数限制、无锁定、无验证码校验:`proto/agency.proto:41` 声明的 `verify` 字段从未被读取(`:22` 只判 `Account`/`Password` 非空),且 `ErrAccountNotFound`(`:29`)与 `ErrPassword`(`:35`)分开返回,可用于账号枚举 + 无限撞库。 | +| 中 | `internal/logic/agency/login.go:60` | 业务查询上残留 `impl.DBService.Debug()`,会为该条 UPDATE 打开 GORM 调试输出(含参数),与 `internal/models/impl.go:83` 的全局 Debug 叠加。 | +| 中 | `internal/logic/agency/get.go:29-32`、`modify.go:46-47`、`supply/get.go:23-26`、`supply/modify.go:39-40` | 查不到记录时直接 `return nil, err` 透传 **GORM 原始错误**(而非 `errcode`),把表名/驱动错误信息经 API 返回外部;`log.Println("获取代理商数据失败:", err)` 的中文日志也与 SDK `printer` 风格不一致。 | +| 低 | `internal/password/password.go:5-12` | **密码哈希已无 MD5 遗留(代码层)**:`Hash`/`Verify` 统一 `bcrypt.GenerateFromPassword`(DefaultCost),调用点只有 `create.go:27`、`supply/create.go:27`、`set_password.go:41`、`login.go:34`,全仓库无 md5 引用;`internal/password/password_test.go:5-13` 覆盖了正确/错误口令两个分支。遗留形态是**数据侧**:`market_agency.salt`(`models/market_agency.go:18`)与 `market_supply.salt`(`models/market_supply.go:17`)两列仍存在,`set_password.go:47` 还在显式写 `"salt": ""`,且登录/改密**没有任何"历史 MD5 或加盐哈希"的兼容或迁移分支**——若库中存量值是旧哈希,`bcrypt.CompareHashAndPassword` 必然返回 false,用户侧无自助修复路径。【信息不足】本次未连接数据库,无法确认线上两张表的 `password` 列是否仍有 32 位 MD5(或加盐 MD5)存量值。 | +| 低 | `etc/market_dev.yaml:8` | dev 配置写了 `Databases.Debug: true`,但 `conf.DBConf` 只有 `Driver`/`Source`(SDK `conf/types.go:18-21`),该键被 YAML 解析器静默忽略;真正生效的是 `internal/models/impl.go:83` 的无条件 `db.Debug()`。配置看起来可控、实际不可控。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/agency/fetch.go:19,27-32,43` | **`Offset` 在分页参数归一化之前就算好了**。`:19` 用请求原始值计算 `Offset = (page_no-1)*page_size`,`:27-32` 才把 `page_no<1` 归一为 1、`page_size<10` 归一为 50,`:43` 用**归一后的 `Limit` + 归一前的 `Offset`**。两个必现后果:① 只传 `page_no`(`page_size` 缺省 0)时 `Offset` 恒为 0 → 无论要第几页都返回第一页数据;② 传 `page_no=3,page_size=5` 时 `Offset=10` 而 `LIMIT 50` → 返回的是"第 11 条起的 50 条",页窗口整体错位(第 2、3 页内容重叠)。`supply/fetch.go:19,27-32,37` 是同一份代码的复制,缺陷相同。 | +| **高** | `internal/logic/agency/pending.go:31,38` | **不传 `page_size` 时待审核列表恒为空**。归一化用的是 `if in.GetPageSize() < 0 { in.PageSize = 50 }`(`:31`),而 `page_size` 缺省值是 0(不满足 `< 0`)→ 0 被原样传给 `Limit(0)`(`:38`);GORM 对 `Limit(0)` 生成 `LIMIT 0`(`gorm.io/gorm/clause/limit.go:16-18` 的条件是 `*Limit >= 0`),查询结果必为空。对照 `fetch.go:30` 用的是 `< 10`,两处阈值不一致。`test/agency/pending.http:5` 恰好传了 `page_size`,所以手工验证时看不出问题。 | +| **高** | `internal/logic/agency/fetch.go:43,50`、`pending.go:38,45`、`supply/fetch.go:37,43` | **`count` 返回的是本页条数而非总数**。三处都执行了 `Count(&cnt)` 却把 `cnt` 丢弃,返回体写的是 `Count: int64(len(data))`(=这一页的行数)。前端据此算总页数必然错误;`cnt` 变量在三处都是"算了不用"的死赋值。 | +| 中 | `internal/logic/agency/approve.go:27-38,40-44` | `switch in.GetApprove()` 只处理 1(→`approve=2`,`:29`)和 2(→`approve=-2`,`:34`),**其它值(含缺省 0、或任意数字)什么都不做却返回 `Code:0, Message:"OK"`**,调用方会误判审核成功。函数还不校验当前状态(可对已通过/-2 的记录反复改判)、不记录审核人与审核时间(无审计字段),返回体也没有 `Timeseq`(对比 `delete.go:35`)。 | +| 中 | `internal/logic/agency/ref.go:16-35` | 列表字段映射**漏掉 `Approve`**(`proto/agency.proto:65` 定义了该字段):`/market.Agency/Fetch` 与 `/Pending` 返回的 `approve` 恒为空,前端拿不到审核状态,`Pending` 的结果也无法体现"待审 vs 已拒绝"。`supply/ref.go:14-29` 同样漏了 `Approve` 与 `CommissionRate`(`supply/get.go:28-41` 也漏了这两个字段)。 | +| 中 | `internal/logic/agency/modify.go:28-45,53` | `Updates(&mktModel)` 传结构体 → GORM 跳过零值:`commission_rate` 改不回 0、`remark`/`email` 等无法清空;`Account`(登录名)可被任意改;`in.Password`(`proto/agency.proto:52`)被静默忽略。同时 `:53` 的 `Identity: mktModel.Identity` 取的是**刚构造的入参结构体**(`:28-44` 从未赋值该字段)→ 响应里的 `identity` **恒为空字符串**,调用方拿不到被改对象标识。`supply/modify.go:38-46` 同一问题(返回的 `Identity` 也是空)。 | +| 中 | `internal/logic/agency/create.go:24-49`、`supply/create.go:24-44` | **不校验 `account` 唯一性**,而 `account` 列没有唯一索引(`models/market_agency.go:15`、`models/market_supply.go:14`)→ 可创建任意多个同 `account` 账号;登录用 `Where("account=?").First(...)`(`login.go:26`)取无序的第一条,登录到哪个账号不确定(可能恰是刚被审核拒绝的那条,表现为"密码错误/账号被禁用")。`create.go:25` 只校验 name/account/password 非空,`phone`(列为 not null 但允许空串)等未校验。 | +| 中 | `internal/logic/agency/create.go:31-49` | 创建时忽略请求里的 `status` 与 `approve`,硬编码 `Status: 1`、`approve` 取零值 0。`approve=0` 符合"待审核"语义,但 `proto/agency.proto:61` 声明的 `status` 被静默丢弃,接口契约与行为不符。 | +| 中 | `internal/logic/agency/fetch.go:35`、`pending.go:36` | `tx.Where(...)` 未回写到 `tx`,而同函数的 `:38/:41`(Fetch)、`:34`(Pending)都写了,同一函数内两种写法并存。当前依赖 GORM "clone=0 时共享 Statement" 的内部行为仍能生效,属隐患而非现网故障,但版本升级或链式顺序调整后会静默丢条件。 | +| 低 | `internal/logic/supply/get.go:23` | 若请求 `identity` 为空则生成 `Where("identity=''")`,直接返回 GORM `ErrRecordNotFound` 原始错误;`supply.Modify` 有非空校验(`modify.go:22-24`),`supply.Get` 没有,同目录口径不一致。 | +| 低 | `internal/models/query.go:4-7` | `NormalStatus` 常量无任何引用(只有 `DisabledStatus` 在 `login.go:39` 使用)。 | + +### 6.3 未完成实现 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/data/overview.go:18-22` | `Overview`(概况)保留 `// TODO: valid code` 与 `// TODO: add your logic code & delete this line.`,直接 `return` 返回 `(nil, nil)`。`proto/data.proto:24-39` 的 `total_approve`/`total_month`/`total_all`/`total_staff` 与 `report_month`/`report_staff` 永远为空。 | +| **高** | `internal/logic/data/member_fetch.go:26-28`、`member_details.go:24-26`、`order_fetch.go:26-28`、`order_details.go:24-26` | 会员列表、会员详情、订单列表、订单详情 4 个接口同样是"只做鉴权(+分页参数归一)+ TODO + `return`"的骨架,`reply` 从未赋值。**`market.Data` 服务的 5 个方法零实现**,接口已对外注册(`internal/server/data_server.go:19-41`、`service/expose.go:26-28`),对调用方表现为"永远返回空"。 | +| 中 | `internal/logic/data/member_fetch.go:19-24`、`order_fetch.go:19-24` | 占位实现里归一化出的 `PageNo`/`PageSize` 无任何消费者(没有查询),且用 `< 10 → 50` 而 `pending.go:31` 用 `< 0 → 50`,同一模块三种分页口径;占位接口同样没有 `page_size` 上限。 | +| 中 | `proto/supply.proto:29-46` + `internal/logic/supply/create.go:27,35-36` | 供应商表有 `account`/`password`/`last_login_at` 字段、`Create` 也在写 bcrypt 哈希,但**整个 `Supply` 服务没有任何登录接口**(`proto/supply.proto:7-24` 只有 Create/Get/Fetch/Modify/Delete),`supply/create.go:34` 还把 `Avatar` 的赋值注释掉了(字段永远为空)。供应商账号目前只能建、不能用。 | +| 低 | `internal/logic/agency/create.go:19-23`、`delete.go:16-21`、`supply/create.go:17-23` | 三处保留被注释掉的 `// parse authorization meta.` 段落,属"注释后忘记打开"的遗留,不是有意匿名设计(同目录其它文件都调用了 `ParseMetaCtx`)。 | +| 低 | `cmd/cli/main.go:5-7` | 仍是 `log.Println("Hello World!")` 脚手架。 | +| 低 | `README.md:1-3` | 只有 `# market` 和一行 `market agency`,无启动方式、账号体系与审核流程说明。 | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/models/impl.go:75-85`(尤其 `:83`) | **生产环境强制打开 SQL 调试日志**。`NewPostgres` 无条件 `db = db.Debug()`,三个 yaml 的 `Driver` 都是 `postgres`(`etc/market_dev.yaml:5`、`market_prod.yaml:5`、`market_test.yaml:5`)→ 全部 SQL 与参数打印,包含代理商 `password` 哈希、`id_name`、证件照 URL、`phone`、`email`。 | +| 中 | `internal/models/impl.go:39` | 每次启动执行 `AutoMigrate(&MarketAgency{}, &MarketSupply{})`(生产同样执行);模型字段变更会直接改线上表结构,无版本化、无回滚、发布前无法评审 DDL。 | +| 中 | `internal/server/new.go:16,26-30` + `cmd/main/main.go:32` | **独立进程下 HTTP 网关不可用**。`Server.Mux` 字段声明后**从未赋值**(全仓库检索 `.Mux =` / `Mux:` 无命中),`cmd/main` 把它(nil)当 `GatewayMux` 传给 SDK,SDK `service.Start` 用它执行 `http.ListenAndServe(httpAddr, s.Opts.GatewayMux)`(SDK `service/service.go:106-108`)→ 非空接口持有 nil 指针,`(*ServeMux).ServeHTTP` 首次访问 `s.unescapingMode`(grpc-gateway `runtime/mux.go:407-420`)即空指针 panic,被 `net/http` recover 后断开连接。只有聚合入口 `pkgs/all` 自建 ServeMux(`pkgs/all/internal/server/server.go:38`)才正常。同一缺陷存在于全部模块的生成骨架,属生成器级问题。 | +| 低 | `internal/impl/impl.go:9`、`internal/impl/with.go:19-21` | `RedisCache`、`Etcd` 初始化后无任何使用点(死代码);`service/dependencies.go:16` 的 `Cache` 字段被 `applyDependencies`(`:19-29`)忽略。 | +| 低 | 全模块 | 只有 `internal/password/password_test.go:5-13` 一个单元测试;`internal/logic/**` 无测试、`internal/models` 无测试。本次发现的分页错位、`LIMIT 0`、`count` 语义、审核空转(switch 无 default 分支)等问题都无回归保护。 | +| 低 | `internal/logic/agency/{login.go:53,create.go:50}` | 使用标准库 `log`/`fmt.Println`/`fmt.Errorf` 与 SDK `printer` 混用(`fetch.go:44`、`delete.go:27` 用 `printer.Error`),日志出口与格式不统一;`fmt.Println` 的调试输出被直接留在生产路径上。 | +| 低 | `etc/market_dev.yaml:20`、`etc/market_prod.yaml:19`、`etc/market_test.yaml:19` | `MicroService.Anonymous` 三个环境不一致(dev 是 `market.ping.hello`,prod/test 是 `mall.ping.hello`),且本模块**没有任何 ping 方法**(`proto/*.proto` 只有 Agency/Supply/Data 三个服务)→ 该白名单条目恒为无效配置;`SecretKey: CHANGE_ME`、`password=CHANGE_ME` 也是占位值。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| M1 | 高 | `Approve` 无任何鉴权;`Agency.Create`/`Agency.Delete`/`Supply.Create` 鉴权被注释(`approve.go:15-46`、`create.go:20-23`、`delete.go:17-21`、`supply/create.go:19-23`) | 渠道准入体系可被绕过、账号可被自助创建与删除 | +| M2 | 高 | `Get`/`Modify`/`SetPassword` 用请求 `identity` 覆盖 token `identity`(`get.go:25-27`、`modify.go:25-27`、`set_password.go:25-27`) | 任意代理商 PII(证件照/手机号/邮箱)可读、账号资料可改 | +| M3 | 高 | `Data` 服务 5 个方法全为零实现却已注册对外(`logic/data/*.go:18-28`) | 功能不可用、API 契约撒谎 | +| M4 | 高 | 登录签发 AES 密文 vs 接口校验 HS256 JWT 互斥(`login.go:53`、SDK `crypto/token/jwt.go:64-73`) | 登录后无法调用任何受保护接口 | +| M5 | 高 | 非 JWT 的 `Authorization` 头触发 SDK `ParseJwt` 空指针,模块无 recover 拦截器(`internal/server/new.go:23`) | 服务可用性(进程崩溃,任意人可触发) | +| M6 | 高 | 分页 `Offset` 早于参数归一化;`Pending` 用 `< 0` 归一导致 `LIMIT 0`(`fetch.go:19,27-32,43`、`pending.go:31,38`、`supply/fetch.go:19,27-32,37`) | 列表接口返回错页或恒空 | +| M7 | 高 | `Count` 被丢弃、返回本页条数(`fetch.go:43,50`、`pending.go:38,45`、`supply/fetch.go:37,43`) | 前端分页计算错误 | +| M8 | 中 | 列表接口仅验 token、无角色/归属过滤、`page_size` 无上限(`fetch.go:22,30-32`、`pending.go:17`) | 批量 PII 泄露(含证件照) | +| M9 | 中 | 登录无锁定、账号枚举、`verify` 未校验(`login.go:22-46`) | 撞库、账号枚举 | +| M10 | 中 | 建号日志打印含密码哈希的完整模型(`create.go:50,52`) | 凭据泄漏至日志文件 | +| M11 | 中 | 审核空转返回成功、不校验当前状态、无审核留痕(`approve.go:27-44`) | 审核结果不可信、无法追责 | +| M12 | 中 | `account` 无唯一索引且不查重(`models/market_agency.go:15`、`create.go:24-49`、`login.go:26`) | 登录串号、账号抢占 | +| M13 | 中 | 列表映射漏 `Approve`、`Modify` 返回空 `identity`(`ref.go:16-35`、`modify.go:53`、`supply/modify.go:46`) | 前端拿不到审核状态与变更对象标识 | +| M14 | 中 | 生产强制 SQL Debug + 启动 `AutoMigrate`(`internal/models/impl.go:39,83`) | 敏感信息落盘、DDL 不可控 | +| M15 | 中 | 独立进程 HTTP 网关因 `Mux` 未赋值不可用(`internal/server/new.go:16`、`cmd/main/main.go:32`) | 可用性(生成器级、全模块共有) | +| M16 | 中 | 供应商有密码字段却无登录接口、`Avatar` 赋值被注释(`proto/supply.proto:7-24`、`supply/create.go:34`) | 功能不可用 | +| M17 | 低 | `salt` 列残留 + 无历史哈希兼容分支;死代码、无测试、`cmd/cli`/README 占位 | 存量账号登录(【信息不足】)、可维护性 | + +## 8. 修复建议(务实项) + +1. **补齐四处鉴权(M1,最高优先)**:`approve.go` 开头补 `auth, err := service.ParseMetaCtx(ctx, nil)`(它还需要一个"审核人"概念,可先用返回值做非空校验);`create.go:20-23`、`delete.go:17-21`、`supply/create.go:19-23` 把注释掉的段落恢复。这样至少保证"必须持合法令牌",与同目录其它接口一致。 +2. **禁止 identity 覆盖(M2)**:`get.go:25-27`、`modify.go:25-27`、`set_password.go:25-27` 统一改为只使用 `auth.Identity`;若确实需要"管理员代改",就显式判定 `auth` 的某个角色扩展字段(`claims.Extend`,SDK `service/meta.go:51-60`),未带该角色时直接 `errcode.ErrPermissionDenied`。 +3. **列表接口收口(M8/M13)**:`fetch.go:22`、`pending.go:17` 的 `ParseMetaCtx(ctx, nil)` 改为传入角色要求;`ref.go:16-35` 补上 `Approve`(`supply/ref.go` 补 `Approve`/`CommissionRate`);`fetch.go:30-32` 增加上界(例如 `if in.GetPageSize() > 200 { in.PageSize = 200 }`)。 +4. **分页三处修复(M6/M7)**:`fetch.go`、`pending.go`、`supply/fetch.go` 都调整为"先归一化 `page_no`/`page_size`,再计算 `Offset`";`pending.go:31` 的 `< 0` 改成与其它两处一致的 `< 10`;三处返回体把 `Count` 改成 `cnt`(已算好的总数)。 +5. **审核语义修正(M11)**:`approve.go:27-38` 的 `switch` 加 `default: return nil, errcode.ErrInvalidArgument`,并校验当前 `data.Approve == 0`(只允许对待审记录操作);如需留痕,用现有的 `remark` 或直接依赖 `updated_at`。 +6. **账号唯一性(M12)**:迁移里给 `market_agency.account`、`market_supply.account` 加唯一索引,并在 `create.go`/`supply/create.go` 建号前先 `Where("account = ?").First` 判重后返回明确的 `errcode`(避免仅靠索引报 500)。 +7. **登录链路(M4/M9)**:`login.go:53` 改用与校验侧一致的签发方式——参照 base/passport 的写法 `token.New(env.Runtime.JwtSecretKey).GenerateJwt(...)`(`module/base/passport/internal/logic/common/token.go:10`),不要用 `encipher.GenerateTokenAes`;同时落地 `verify` 字段的校验(`proto/agency.proto:41` 已声明为必填),并对连续失败次数做限制。若暂时不做验证码,至少对同账号失败次数计数(Redis 已有初始化代码 `internal/impl/with.go:24-31`,可直接使用)。 +8. **降 panic 面(M5)**:本模块侧给 `internal/server/new.go:23` 的 `grpc.NewServer()` 装带 `recover` 的 unary 拦截器;根因(SDK `crypto/token/jwt.go:68` 在 `err != nil` 时仍访问 `token.Claims`)建议对 SDK 提缺陷单——聚合入口的同类实现是安全的(`pkgs/all/internal/server/authorization.go:80` 用 `if err != nil || !tokenValue.Valid` 短路),可直接照抄这个判断顺序。 +9. **日志与凭据(M10/M14)**:删除 `create.go:50,52` 的 `fmt.Println`;把 `internal/models/impl.go:83` 的无条件 `db.Debug()` 改为按配置开启(`internal/impl/with.go:41` 传入真实 `*types.SqlOptions`,或把 `Debug` 补进 `conf.DBConf`),生产不要打印含口令哈希的 SQL。 +10. **收尾清理(M15/M16/M17)**:`internal/server/new.go:26-30` 补 `Mux: gwRuntime.NewServeMux()`(需改生成模板 `protoc-gen-slc`,否则独立进程的 HTTP 网关一直不可用);`supply/create.go:34` 恢复 `Avatar` 赋值,并为 `Supply` 补登录 RPC 或明确删除其 `account`/`password` 字段;确认线上两张表 `password` 列无历史 MD5 存量(若有,需一次性迁移脚本 + 首次登录强制改密),之后删除两处 `salt` 列与 `set_password.go:47` 的 `"salt": ""` 写入;补一条集成测试覆盖"匿名/低权令牌不能 Approve、不能 Delete、不能读他人 Get/Single、列表 count 等于总数"。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。"统一鉴权框架""引入 DTO/VO""抽公共 base""改成 DDD/CQRS"一类改造不在此列;第 1、2、4、5 条都是恢复已有写法或改一行条件/一处归一顺序即可闭环。 diff --git a/docs/mgt.md b/docs/mgt.md new file mode 100644 index 0000000..ef9d51b --- /dev/null +++ b/docs/mgt.md @@ -0,0 +1,156 @@ +# mgt(后台组织与权限管理)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/mgt` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件约 80 个(`cmd/`、`internal/` 全量、`service/`、`types/`、`middleware/`、`libs/`);**无 proto 定义(纯 Gin REST),无生成代码** | +| 入口 | `cmd/main`(Gin 单进程)、聚合入口 `pkgs/all`、`pkgs/ecmall` | +| 对外协议 | 仅原生 REST,基础前缀 `/rest/mgt`(README 所述 `/mgt/v1/...` 与实际不符) | +| 结论摘要 | 鉴权中间件**已正确挂载**于全部管理路由(JWT + 超级管理员角色双重校验,模块内无绕过);SQL 全部参数化。但 `Login` 的 `AppId/AppKey` 路径以**明文相等比对密码列**(哈希即凭证);dev 配置启用 `InitRootUser` 注入 `root/123456` 弱默认账号;列表查询**无分页上限**;配置文件注释与实现行为矛盾。 | + +## 1. 服务定位与职责 + +后台 RBAC 权限管理系统:用户、角色、权限(菜单/按钮)、应用、部门的管理与关联。负责管理员账号生命周期、登录认证、Token 刷新、短信重置密码、以及权限/角色/部门树的维护。它是**管理面(admin console)**服务,所有写操作需超级管理员权限。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 进程入口:配置→impl→Gin→session→中间件→`routers.Register`→监听 | +| `internal/config/config.go` | 配置结构(`Databases/SecretKey/InitRootUser` 等);设置 JWT 密钥来源 | +| `internal/impl/{impl,with}.go` | 初始化 Redis/DB/Etcd/内存缓存;DB 连接(mysql/postgres)+ 自动迁移 | +| `internal/middleware/rbac.go` | `RequireAdmin` / `IsSuperAdmin` / `EnsureSelfOrAdmin` 管理员鉴定 | +| `internal/libs/validator.go` | 参数校验(手机号、邮箱自定义规则) | +| `internal/routers/register.go` | REST 路由注册(匿名组 + 鉴权组 + 管理员组) | +| `internal/logic/hello/ping.go` | 健康检查、Session 调试端点 | +| `internal/logic/pub/{login,refresh,forget}.go` | 公开接口:登录、刷新、短信重置 | +| `internal/logic/user/*` | 用户 CRUD、角色/权限分配、查询 | +| `internal/logic/role/*`、`permission/*`、`application/*`、`department/*` | 各资源 CRUD 与关联 | +| `internal/logic/tool/tool.go` | 分页参数处理 | +| `internal/models/*` | 16+ 模型 + `impl.go`(迁移与 root 初始化) + `init_db.go`(root 数据) + `init_menu.go`(菜单) | +| `internal/types/{vars,req,resp}.go` | 常量、请求/响应结构 | +| `service/{expose,dependencies}.go` | 聚合宿主注入(共用 Engine 与连接) | + +> 与 `logs`/`passport` 不同,mgt **没有 proto/gRPC**,是纯 Gin 服务;聚合入口 `service.Expose` 直接复用同一 `Engine` 注册路由,鉴权接线一致。 + +## 3. 接口清单 + +> 鉴权分三层:`anonymous`(无)、`auth`(`middleware.JwtAuth`)、`admin`(`mgtmw.RequireAdmin`)。所有管理写操作均挂在 `admin` 组。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| GET | `/rest/mgt/ping` | 健康检查 | 无 | `logic/hello/ping.go` | +| GET | `/rest/mgt/session` | Session 调试 | 无 | `logic/hello/ping.go` | +| POST | `/rest/mgt/login` | 登录(账号密码 / AppId+AppKey) | 无 | `logic/pub/login.go` | +| POST | `/rest/mgt/refresh` | 刷新 Token | auth | `logic/pub/refresh.go` | +| POST | `/rest/mgt/reset` | 短信验证码重置密码 | 无 | `logic/pub/forget.go` | +| POST | `/rest/mgt/user/role` `/app` `/pmn` `/pmn_tree` | 当前用户角色/应用/权限查询 | auth | `logic/user/*` | +| POST | `/rest/mgt/user/create` `/del` `/detail` `/modify` `/fetch` `/list` `/set_role` `/del_role` `/set_pmn` `/modify_pmn` `/del_pmn` | 用户管理(全部写操作) | admin | `logic/user/*` | +| POST | `/rest/mgt/app/*` | 应用管理 | admin | `logic/application/*` | +| POST | `/rest/mgt/role/*` | 角色管理 | admin | `logic/role/*` | +| POST | `/rest/mgt/pmn/*` | 权限管理 | admin | `logic/permission/*` | +| POST | `/rest/mgt/dpt/*` | 部门管理 | admin | `logic/department/*` | + +> 对照发现:README 描述的路径前缀 `/mgt/v1/...` 与实际注册前缀 `/rest/mgt/...` 不一致;`ForgetPwdByPwd`(旧密码改密)为**空 stub 且未注册路由**。 + +## 4. 数据模型与表 + +| 表名 | 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | --- | +| `mgt_user` | `id` | - | PK | 用户 | +| | `account` | varchar(255) | uniqueIndex | 账号(含 `root`) | +| | `phone` | varchar(255) | - | 手机号 | +| | `password` | varchar(255) | - | 密码(bcrypt / 兼容 MD5 十六进制) | +| | `salt` | varchar(255) | - | 密码盐 | +| | `last_login_at` | time | - | 最后登录 | +| `mgt_role` | `id/name/description` | - | - | 角色(含 `超级管理员`) | +| `mgt_permission` | `id/code/title/menu_path/type/app_id/parent_id` | - | - | 权限/菜单 | +| `mgt_application` | `id/workspace/title` | - | workspace 唯一 | 应用 | +| `mgt_department` | `id/name/parent_id/leader_id` | - | - | 部门 | +| 关联表 | `mgt_link_user_role` `mgt_link_user_pmn` `mgt_link_user_app` `mgt_link_user_dpt` `mgt_link_role_pmn` `mgt_link_role_app` `mgt_link_dpt_role` `mgt_link_dpt_pmn` | - | 复合唯一 | 多对多关联 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /rest/mgt/login"] --> B["按 account 或 AppId 查 mgt_user"] + B --> C{"AppKey 路径?"} + C -->|否| D["checkPwdAndStatus: bcrypt / MD5 比对"] + C -->|是| E["checkAppKeyAndStatus: 明文相等比对密码列"] + D --> F["签发 JWT"] + E --> F + G["POST /rest/mgt/user/create"] --> H["RequireAdmin 校验超级管理员"] + H --> I["bcrypt 哈希 + 事务建用户/关联"] + J["POST /rest/mgt/reset"] --> K["校验短信验证码(Redis)"] + K --> L["bcrypt 重置密码"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/pub/login.go:147-159` | **`checkAppKeyAndStatus` 以明文相等比对密码列**:`if userPwd != pwd` 直接比较存储的 `password` 字段与传入 `AppKey`。对 MD5 兼容老用户(密码列存 `md5(pwd+salt)` 十六进制),攻击者只要拿到密码哈希,以 `app_id=账号`、`app_key=哈希` 即可登录,**哈希本身成为可用凭证(pass-the-hash)**,加密存储失去意义。 | +| **高** | `etc/mgt_dev.yaml:16` + `internal/models/impl.go:69-70` + `internal/models/init_db.go:16` | **弱默认管理员账号**:dev 配置 `InitRootUser: true`,启动时 `InitRootUserData()` 创建 `root` 账号、口令硬编码 `123456`。非生产环境一旦启用即注入后门级默认凭据;代码不强制改密。 | +| 中 | `internal/config/config.go:19,24-29` | **配置语义矛盾**:注释"未配置时默认 true",但实现 `InitRootUser==nil` 时返回 `false`;README:245 又写"默认 false"。部署者若省略该字段,实际**不会**创建 root(与注释和部分文档预期相反),易误导安全预期、造成"无可用管理员"或反之。 | +| 中 | `internal/config/config.go:39` | **JWT 密钥来源不可见**:`Spec.SecretKey = env.Runtime.JwtSecretKey`,yaml 中的 `SecretKey: CHANGE_ME` 被覆盖忽略。真实密钥强度/是否强制设置取决于 bsm-sdk 与环境变量,本模块无法确认【信息不足】。 | +| 低 | `internal/logic/hello/ping.go:25-35` + `internal/routers/register.go:32` | `/rest/mgt/session` 为匿名调试端点:写入固定 session 并打印到 stdout,属调试残留,可被探测/泄露。 | +| 低 | `internal/logic/pub/login.go:108-116` | `SaveToken` 将 token 写入 Redis 时 TTL=0(永不过期);该函数未被任何路径调用(死代码),但一旦启用即永久缓存 token。 | + +> 管理员鉴定本身**正确**:`routers/register.go:42-43` 在管理路由挂 `RequireAdmin`,其 `IsSuperAdmin`(`internal/middleware/rbac.go:19-36`)按 `id` 回查 DB 确认账号为 `root` 或拥有 `超级管理员` 角色,**不以 JWT 声明直接判定**,模块内无明显绕过。唯一残留风险在于 SDK 的 `ParseAuth` 是否校验 JWT 签名(见 6.4)。 + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/logic/tool/tool.go:5-14` | **列表查询无分页上限**:`GetSizeAndPage` 对 `size<=0` 才取默认 10,否则直接使用请求值。所有 `*Fetch`/`list` 接口(user/role/app/pmn/dpt)接受任意 `size`,可触发无上限批量查询(全表扫描 / DoS / 数据拖库)。 | +| 中 | `internal/config/config.go:24-29` | `InitRootUserEnabled` 在 nil 时返回 false,与同文件注释及 README 宣称的"默认 true"不一致(同 6.1 中)。 | +| 中 | `internal/models/impl.go:106,113` | **SQL Debug 全量开启**:mysql/postgres 连接均硬编码 `Debug: true`,所有 SQL(含敏感查询)进入日志,生产环境应关闭。 | +| 低 | `internal/logic/user/create.go:57-72`、`internal/logic/application/create.go:50-68` | 创建时对已软删的同 phone/account(或 workspace/title)记录执行 `Unscoped().Delete` 硬删除再重建,可能误删历史数据且不可恢复。 | + +### 6.3 未完成实现 + +- `internal/logic/pub/forget.go:103-104`:`ForgetPwdByPwd`(凭旧密码改密)为空 stub,且未注册路由。 +- `internal/logic/pub/login.go:108`:`SaveToken` 死代码(无调用点)。 +- `internal/models/init_menu.go:16` 引用 `etc/menu.json`;本仓库 `etc/` 下为 `menu.json`(已存在)但 `scripts`/`other` 提及 `menu_structure.json`,文件缺失会导致 `InitRootUserData` 事务因 `Load` 失败而回滚、root 创建失败【信息不足,需确认部署包含该文件】。 +- 全模块**无 Go 单元测试覆盖逻辑层**(仅 `libs` 校验;`test/` 下多为 `.http` 手工用例)。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/models/impl.go:106,113` | SQL Debug 全量日志(同 6.2),泄露查询结构。 | +| 低 | `internal/logic/user/del.go:16-78` | **无"禁止删除 root / 超级管理员"保护**:超级管理员可删除 `root` 或另一超级管理员,导致管理面锁死(DoS)。 | +| 低 | `internal/logic/pub/login.go:80`、`user/create.go:46` 等多处 | 大量 `printer.Error/Info` 打印账号、手机号、内部错误等敏感信息,存在信息泄露。 | +| 低 | 全模块 | 逻辑层无单元测试;`config` 注释与实现、README 与真实路由前缀多处不一致,增加维护与误配风险。 | +| 【信息不足】 | `git.apinb.com/bsm-sdk/core/middleware.ParseAuth` | 管理员判定的前提是 JWT 签名被 `ParseAuth` 正确校验;该函数在 bsm-sdk 内,本模块不可见,无法确认签名校验是否到位(若未校验,可伪造 `id`/`account=root` 的 token 越权)。建议核实 SDK 实现。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| M1 | 高 | `AppKey` 登录明文比对密码列(哈希即凭证) | 密码哈希泄露即等于账号失陷 | +| M2 | 高 | dev 默认 `root/123456` 弱管理员账号 | 非生产环境被注入后门凭据 | +| M3 | 中 | `InitRootUser` 注释/实现/文档行为不一致 | 安全预期误导、root 可能未创建 | +| M4 | 中 | JWT 密钥来源不可见 | 密钥强度不确定 | +| M5 | 中 | 列表查询无分页上限 | 全表扫描 / DoS / 拖库 | +| M6 | 中 | SQL Debug 全量日志 | 查询与数据泄露 | +| M7 | 低 | 匿名 session 调试端点 | 探测/信息泄露 | +| M8 | 低 | 可删除 root、无单测、信息打印 | 健壮性与可维护性 | +| M9 | 【信息不足】 | SDK `ParseAuth` 签名校验未知 | 管理员越权潜在风险 | + +## 8. 修复建议(务实项) + +1. **移除 AppKey 明文比对**:`checkAppKeyAndStatus`(`pub/login.go:147`)不应以 `userPwd != pwd` 直接比较密码列。应用密钥登录应走独立的应用密钥表/字段校验,而非复用用户登录密码;至少应与 `checkPwdAndStatus` 同样走 bcrypt/MD5 哈希比对,杜绝"哈希即凭证"。 +2. **收敛默认账号**:dev 之外不要启用 `InitRootUser`;若需启用,生成随机初始口令并强制首次登录改密,移除硬编码 `123456`(`init_db.go:16`)。 +3. **统一配置语义**:`config.go` 注释与 `InitRootUserEnabled` 实现保持一致(明确"省略即不初始化"或"省略即初始化"),并同步修正 README,避免误导。 +4. **分页上限**:`tool.GetSizeAndPage`(`tool/tool.go`)对 `size` 设上限(如 ≤200),所有 `*Fetch`/`list` 复用。 +5. **关闭 SQL Debug**:`models/impl.go` 的 `Debug: true` 改为按环境(非 dev 关闭)。 +6. **删除调试端点与死代码**:移除 `/rest/mgt/session`(`register.go:32`)与 `SaveToken`(`login.go:108`)。 +7. **删除保护**:`user/del.go` 增加"禁止删除 root / 超级管理员"校验。 +8. **确认 JWT 签名校验**:核实 bsm-sdk `ParseAuth` 是否校验签名;如未校验,管理员鉴定即形同虚设——此为前提性修复。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。 diff --git a/docs/order.md b/docs/order.md new file mode 100644 index 0000000..f91ae31 --- /dev/null +++ b/docs/order.md @@ -0,0 +1,301 @@ +# order(购物车与订单生命周期)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/ec/order` | +| 服务域 | 电商-购物车与订单生命周期 | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 62 个(`pb/` 生成代码 13 个,手写 49 个,手写约 3098 行;含生成代码共 11922 行);`proto/` 5 个 618 行;`internal/logic/` 25 个文件(cart/coupon/mgt/summary/common);`test/rpc/` 6 个文件 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程)、聚合入口 `pkgs/all` 经 `service.Expose` 复用共享 gRPC/Gateway | +| 对外协议 | gRPC + HTTP Gateway,路径 `/order./`,全部为 POST | +| 结论摘要 | 订单状态推进的 3 个"模拟"接口(支付/发货/收货)在配置中已开启 Gateway 且无角色校验,可被任意已登录账号调用、并把订单推到已支付/已发货;`OrderApprove` 完全无鉴权。**跨服务数据库耦合是结构性问题**:`etc/order_prod.yaml` 的 DSN 就是 mall 的库,代码直接读写 `mall_product`/`mall_product_spec`/`mall_store`/`address_library`。业务侧有 3 处确定性错误会直接产生错误金额或错误库存:优惠券计算把金额**加到**总额(`confirm.go:72`)、`submit` 扣库存不检查 `RowsAffected`(`submit.go:209-217`)、多店铺下单 `store_id` 恒为 0(`submit.go:82,166`)。管理端 `OrderModify` 为假成功占位。 | + +## 1. 服务定位与职责 + +围绕一张购物车与一张订单主表,提供两条下单链路(购物车提交 `Summary.Submit`、单商品快速下单 `Summary.QuickCreateByProduct` / 管理端 `Mgt.OrderCreate`)以及订单的查询、确认(运费/优惠券)、取消、售后审批、模拟交易推进。 + +不负责:商品与规格主数据(`module/ec/mall`)、收货地址(`module/ec/address`)、运费模板(mall 侧未实现)、真实支付与物流对接(本模块用"模拟"接口代替)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New` → `impl.NewImpl` → `server.New(nil)` → `service.New` → `srv.Start()` | +| `internal/config/config.go` | 配置结构(Base/Databases/MicroService/Rpc/Gateway/Apm/Etcd)与端口、密钥初始化 | +| `internal/impl/impl.go`、`internal/impl/with.go` | Redis / DB / Etcd 初始化;`withDatabases` 缺少源时直接 `panic` | +| `internal/models/impl.go` | 独立的 DB 装配层(`models.New`/`NewMysql`/`NewPostgres`),持有 `models.DBService` 并 `AutoMigrate` 4 张表 | +| `internal/models/*.go` | 4 个 GORM 模型 + 跨库表的本地投影结构体(`Product`/`Spec`/`OrderAddress`)+ 3 个未接线查询函数 | +| `internal/logic/common/{no,get,reflect}.go` | 订单号生成、按 identity/order_no 取订单、模型到 pb 的转换 | +| `internal/logic/{cart,coupon,mgt,summary}/**` | 业务逻辑,25 个文件 | +| `internal/server/**` | protoc-gen-slc 生成的 gRPC 服务端桩(4 个 server + `new.go` 注册) | +| `pb/**` | protoc 生成的 pb / grpc / gateway 代码(13 个文件,本次只看接口签名) | +| `service/expose.go`、`service/dependencies.go` | 聚合宿主注入:注册 4 组 gRPC + Gateway handler,并允许外部覆盖 Redis/Etcd/DB/Cache | +| `proto/*.proto` | 5 个契约文件(const 为公共消息块) | +| `etc/order_{dev,test,prod}.yaml` | 三套配置,均为 postgres + `Port: 12442`/`Gateway.Port: 12441` | +| `test/rpc/*.go` | 6 个手工联调用例(全部依赖 `//go:build integration` 与 `BSM_ORDER_TOKEN` 环境变量) | +| `README.md` | 仅一行标题 `# order`(`README.md:1-2`),无任何说明 | + +依赖注入:`service.Dependencies`(`service/dependencies.go:12-17`)由 `pkgs/all` 传入共享连接覆盖 `impl` 全局量(`pkgs/all/internal/service/order.go:10-19`);注意 `applyDependencies`(`service/dependencies.go:19-29`)**未处理 `Cache` 字段**。 + +## 3. 接口清单 + +共 4 个服务、22 个 RPC,全部映射为 `POST /order./`(`pb/*.pb.gw.go` 的 `MustPattern`,如 `pb/summary.pb.gw.go:728-737`)。"鉴权"列取自各 logic 文件实际调用的 `service.ParseMetaCtx` 参数。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| Fetch | `/order.Cart/Fetch` | 购物车列表 | token(任意角色) | `logic/cart/fetch.go:16-17` | +| Create | `/order.Cart/Create` | 加入购物车 | token(任意角色) | `logic/cart/create.go:20-21` | +| Modify | `/order.Cart/Modify` | 改数量 | token(任意角色) | `logic/cart/modify.go:17-19` | +| Delete | `/order.Cart/Delete` | 删除购物车项 | token(任意角色) | `logic/cart/delete.go:15-17` | +| ByStatus | `/order.Coupon/ByStatus` | 按状态取优惠券 | Mall_Admin | `logic/coupon/by_status.go:16-18` | +| OrderCreate | `/order.Mgt/OrderCreate` | 管理端建单 | Mall_Admin + Owner 非空 | `logic/mgt/order_create.go:21-30` | +| OrderModify | `/order.Mgt/OrderModify` | 修改订单 | Mall_Admin + Owner 非空 | `logic/mgt/order_modify.go:14-27` **占位(假成功)** | +| OrderGet | `/order.Mgt/OrderGet` | 订单详情(B 端) | `agency` 为空时 Mall_Admin,非空时任意 token | `logic/mgt/order_get.go:19-32` | +| OrderListByStore | `/order.Mgt/OrderListByStore` | 店铺订单列表 | 同 OrderGet | `logic/mgt/order_list_by_store.go:19-32` | +| OrderCancel | `/order.Mgt/OrderCancel` | 取消订单 | Mall_Admin + Owner 非空 | `logic/mgt/order_cancel.go:17-26` | +| OrderReturnable | `/order.Mgt/OrderReturnable` | 申请退款/退货 | Mall_Admin | `logic/mgt/order_returnable.go:18-22` | +| OrderApprove | `/order.Mgt/OrderApprove` | 售后审批 | **无任何鉴权** | `logic/mgt/order_approve.go:17-27` | +| QuickCreateByProduct | `/order.Summary/QuickCreateByProduct` | 单商品快速下单 | Mall_Admin | `logic/summary/quick_create_by_product.go:21-26` | +| Submit | `/order.Summary/Submit` | 购物车提交下单 | 带 `address` 字段时任意 token,否则 Mall_Admin | `logic/summary/submit.go:24-33` | +| Check | `/order.Summary/Check` | 查未付款订单 | Mall_Admin | `logic/summary/check.go:17-22` | +| Get | `/order.Summary/Get` | 订单详情(C 端) | token(任意角色) | `logic/summary/get.go:15-20` | +| List | `/order.Summary/List` | 我的订单列表 | Mall_Admin | `logic/summary/list.go:19-24` | +| Confirm | `/order.Summary/Confirm` | 确认订单(运费/优惠券) | token(任意角色) | `logic/summary/confirm.go:16-21` | +| Cancel | `/order.Summary/Cancel` | 取消订单(C 端) | token(任意角色) | `logic/summary/cancel.go:17-22` | +| SimulatePay | `/order.Summary/SimulatePay` | **模拟支付** | token(任意角色) | `logic/summary/simulate_pay.go:18-23` | +| SimulateShipments | `/order.Summary/SimulateShipments` | **模拟发货** | token(任意角色) | `logic/summary/simulate_shipments.go:18-23` | +| SimulateReceiving | `/order.Summary/SimulateReceiving` | **模拟收货** | token(任意角色) | `logic/summary/simulate_receiving.go:18-23` | + +### 声明但未实现 / 占位 / 模拟的接口(已核实) + +| 接口 | 位置 | 现状 | +| --- | --- | --- | +| `Mgt.OrderModify` | `logic/mgt/order_modify.go:25-33` | 鉴权与 `auth.Owner == nil` 判空之后只剩两行 `// TODO`,直接返回 `Code:0 "OK"`,**不修改任何数据**。`internal/server/mgt_server.go:24-26` 已把它接到 gRPC 与 Gateway(`pb/mgt.pb.gw.go:537`),属对外可达的假成功接口。 | +| `Summary.SimulatePay` | `logic/summary/simulate_pay.go:17,25` | 注释即"模拟支付"。真实写库,但交易号、金额、支付类型、备注全为硬编码常量:`PayTradeNo: "Pay12371937812897"`、`PayAmount: 10000000`(=100000.00 元)、`PayType: 4`(余额)、`PayRemark: "支付备注"`,且与订单实际 `trans_price` 无关。 | +| `Summary.SimulateShipments` | `logic/summary/simulate_shipments.go:17,29` | 注释即"模拟发货"。只写 `status=3` 与 `delivery_identity`,不校验当前状态、不产生物流单。 | +| `Summary.SimulateReceiving` | `logic/summary/simulate_receiving.go:17,29` | 注释即"模拟收货"。只把 `status` 置 4。 | +| 配送员信息查询 | `logic/summary/quick_create_by_product.go:88-97` | 整段查询 `delivery_member` 的代码被注释,`member_identity` 请求参数无任何效果(`:119-120` 的赋值同样被注释)。 | +| 地址与运费计算 | `logic/summary/confirm.go:36-50` | `if in.AddressIdentity != ""` 分支内只有一段被注释的地址查询与 `GetLogisticsFee` 调用,函数体为空实现。 | +| `models.QuicklyCreateOrder` / `models.GetSummaryCnt` / `models.GetSummaryList` | `internal/models/query.go:22`、`internal/models/order_summary.go:70,83` | 三个模型层函数全仓无调用者,且其中 SQL 引用了模型与表结构中不存在的列(见 6.2)。 | +| `README.md` | `module/ec/order/README.md:1-2` | 只有标题,无接口/部署/数据模型说明。 | + +## 4. 数据模型与表 + +本模块自身有 4 张表(`internal/models/impl.go:15-20` 的 `migrateTables` 自动迁移),另外**直接读写 4 张他服务的表**。 + +### 自有表 + +| 表名 | 模型文件 | 主键 / 唯一键 | 说明 | +| --- | --- | --- | --- | +| `order_summary` | `internal/models/order_summary.go:12` | `id` PK;`identity` 唯一索引;`order_no` **普通索引**;`store_id`/`store_identity`/`status` 索引 | 订单主表 | +| `order_details` | `internal/models/order_details.go:9` | `id` PK(`gorm.Model`);`summary_identity`/`product_identity` 索引;`order_no` 索引 | 订单明细 | +| `order_cart` | `internal/models/order_cart.go:15` | `id` PK;`identity` 唯一索引;`cart_identity` 索引;`product_identity` 索引 | 购物车,无 `passport_identity` 唯一性约束 | +| `order_coupon` | `internal/models/order_coupon.go:10` | `id` PK;`identity` 唯一索引 | 优惠券,归属用 `passport_id`/`passport_identity` | + +### 表 `order_summary`(`internal/models/order_summary.go:12`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id`/`created_at`/`updated_at`/`deleted_at` | uint/TIMESTAMP | PK + 软删除 | `gorm.Model` | +| `identity` | varchar(36) | uniqueIndex | 订单唯一标识(UUID) | +| `passport_id`/`passport_identity` | uint / varchar(36) | 索引 | 下单人(`Std_Passport`);`Submit` 写的是当前登录人 | +| `order_no` | varchar(36) | 索引(**非唯一**) | 18 位订单号,生成见 `logic/common/no.go:10-14` | +| `store_id`/`store_identity` | uint / varchar(36) | 索引 | 店铺(`Submit` 写入值有缺陷,见 6.2) | +| `partner_id` | int32 | default 0 | 分销商 ID,直接取自请求 | +| `total_price`/`trans_price`/`refund_price`/`logistics_fee`/`coupon_amount` | int64 | default 0 | 订单金额/实付/已退/运费/优惠额(后两项从不被写入,见 6.2) | +| `coupon_identity` | varchar(64) | default `''` | 优惠券标识(全仓从不写入) | +| `remark`/`args` | text | default `''` | 备注 / 附加参数 | +| `status` | int32 | default 0,索引 | 1 未支付 / 2 已支付 / 3 已发货 / 4 已收货 / 5 已完成 / 6 已收货退款 / 7 未发货退款 / 8 已退货 / -1 已取消 | +| `address_identity` | varchar(36) | — | 地址库标识 | +| `county`/`province`/`city`/`area`/`address`/`contact`/`phone` | varchar(255/50) | default `''` | **下单时冗余落库的收货地址(PII)** | +| `approve` | int8 | default 0 | -2 未通过 / 0 默认 / 1 申请退款 / 2 申请退货 / 3 申请退款退货 / 4 申请通过 | +| `reason` | varchar(255) | default `''` | 售后原因 | +| `logistics_number`/`delivery_time`/`delivery_address`/`delivery_identity`/`car_identity`/`car_brand`/`car_version`/`license_number`/`member_name`/`member_phone` | varchar | — | 配送信息;`SimulateShipments` 只写 `delivery_identity` | +| `pay_type`/`pay_amount`/`pay_trade_no`/`pay_time`/`pay_remark` | int8/int64/varchar/time/text | default | 支付信息;`SimulatePay` 全部硬编码 | +| `order_details` | — | `foreignKey:SummaryIdentity;references:Identity` | 关联明细(`order_summary.go:61`) | + +### 表 `order_details`(`internal/models/order_details.go:9`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `summary_identity` | varchar(36) | Index | 所属订单标识 | +| `order_no` | varchar(36) | index,`not null` | 订单号(冗余) | +| `product_id`/`product_identity` | int64 / varchar(36) | `not null` / Index | 商品 | +| `spec_id` | int64 | — | **指向 `mall_product_spec.id`**(跨库外键语义) | +| `type`/`title`/`spec_title`/`spec_no`/`cover_image`/`product_args` | int8/varchar/text | default | 类型、标题、规格名、规格编号、封面、参数 | +| `number` | int32 | default 0 | 数量,**在 `Submit` 中为 0 时跳过扣库存** | +| `unit_price`/`sales_price`/`total_price` | int64 | default 0 | 实际单价/原价/小计(单位分) | +| `gas_types` | int32 | default 1 | 燃气类型 | +| `supply_id` | int64 | default 0 | 供应商 ID(来自跨库商品表) | + +### 表 `order_cart`(`internal/models/order_cart.go:15`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键,**被 `Cart.Modify`/`Cart.Delete` 直接当作唯一入口条件** | +| `identity` | varchar(36) | uniqueIndex | 购物车行标识 | +| `cart_identity` | varchar(36) | index,`not null` | 购物车分组标识(来自请求,未登录购物用) | +| `passport_id`/`passport_identity` | uint / varchar(36) | 索引 | 归属用户(`Cart.Create` 只写 `passport_identity`,`:43`) | +| `product_id`/`product_identity`/`spec_id` | int64/varchar(36)/int64 | 索引 | 商品与规格(`spec_id` 指向 `mall_product_spec.id`) | +| `number` | int32 | default 0 | 数量(无正值校验) | +| `product_args` | text | default `''` | 下单前由前端回传的商品参数快照 | + +### 直接读写的外部表(跨服务数据库耦合) + +| 表 | 归属模块 | order 侧的读写位置 | +| --- | --- | --- | +| `mall_product` | `module/ec/mall` | 读:`logic/summary/submit.go:82,112`、`logic/summary/quick_create_by_product.go:51,53`、`logic/mgt/order_create.go:57` | +| `mall_product_spec` | `module/ec/mall` | 读:`submit.go:123,204`、`quick_create_by_product.go:68`、`order_create.go:67`;**写**:`submit.go:209`(`stock - n`)、`order_create.go:141`(`stock - n`)、`order_approve.go:60`(`stock + n`) | +| `mall_store` | `module/ec/mall` | 读:`quick_create_by_product.go:35` | +| `address_library` | `module/ec/address` | 读:`submit.go:68`、`quick_create_by_product.go:79`、`order_create.go:101` | +| `delivery_member` | 未在本仓库中找到对应模型 | 读:`quick_create_by_product.go:90`(整段被注释) | + +两侧数据库连接配置指向同一个库:`etc/order_prod.yaml:7` 与 `module/ec/mall/etc/mall_prod.yaml:7` 的 DSN 均为 `dbname=ec_mall`。order 侧这些查询用 `Table("...")` 加本地投影结构体(`internal/models/query.go:3` 的 `Product`、`:16` 的 `Spec`、`internal/models/types.go:7` 的 `OrderAddress`),没有 GORM 模型、没有编译期约束。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /order.Summary/Submit"] --> B["ParseMetaCtx:带 address 时任意 token"] + B --> C["按 passport_identity 取整辆购物车"] + C --> D{"请求带回 address"} + D -->|是| E["直接用请求里的地址字段"] + D -->|否| F["按 address_identity 直查 address_library(不校验归属)"] + E --> G["逐项直查 mall_product / mall_product_spec"] + F --> G + G --> H["按 store_identity 分组,用库里的 spec.price 计算金额"] + H --> I["事务:写 order_summary + order_details"] + I --> J["逐条 stock >= n 条件更新 mall_product_spec 库存(不检查影响行数)"] + J --> K["按 passport_identity 清空购物车"] + K --> L["返回首个 order_no"] +``` + +```mermaid +flowchart TD + A["POST /order.Summary/SimulatePay"] --> B["ParseMetaCtx(nil):任意已登录角色"] + B --> C["按请求传入的 identity 列表批量更新 order_summary"] + C --> D["status=2, pay_type=4, pay_amount=10000000, pay_trade_no=Pay1237...897"] + E["POST /order.Summary/SimulateShipments"] --> F["ParseMetaCtx(nil) + member_identity 非空"] + F --> G["status=3, delivery_identity=入参"] + H["POST /order.Summary/SimulateReceiving"] --> I["ParseMetaCtx(nil)"] + I --> J["status=4"] + K["POST /order.Mgt/OrderApprove"] --> L["无鉴权"] + L --> M["按 approve 分支改 status 或回补 mall_product_spec.stock"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `logic/summary/simulate_pay.go:20,25`、`logic/summary/simulate_shipments.go:20,29`、`logic/summary/simulate_receiving.go:20,29` | **模拟支付/发货/收货在生产可达且无角色校验**。三个 RPC 在 `proto/summary.proto:33,36,39` 声明、`internal/server/summary_server.go:53-66` 已注册、`pb/summary.pb.gw.go:735-737` 暴露为 POST 路由;实现只用 `service.ParseMetaCtx(ctx, nil)`(只验 token 有效性,不做角色判断,见 `bsm-sdk/core/service/meta.go:36-45`),随后按**请求传入的 identity 列表**直接改状态。任何已登录账号(含 `role=staff`)都能把任意订单标记为已支付、已发货、已收货。`etc/order_prod.yaml:23` 的 `Gateway.Enable: true`,SDK 网关绑定 `0.0.0.0`(`bsm-sdk/core/service/service.go:106`),生产网络直接可达。 | +| **高** | `logic/summary/simulate_pay.go:25` | 支付信息硬编码:`PayTradeNo: "Pay12371937812897"`、`PayAmount: 10000000`、`PayType: 4`(余额)、`PayRemark: "支付备注"`。金额与订单 `trans_price` 完全无关,等于以任意金额标记已支付,账实不符。 | +| **高** | `logic/mgt/order_approve.go:17-27` | `OrderApprove` **没有任何 `ParseMetaCtx` 调用**:匿名即可审批退款/退货,并触发 `case 2` 的库存回补(`:60`)。 | +| **高** | `logic/summary/get.go:17,27` + `logic/common/get.go:15` | `Get` 只验 token,`GetOrderSummaryByIdentity` 按 `identity` 直查,**不校验订单与调用方的关系**(既非 `passport_identity` 也非 `store_identity`)。任意已登录用户可读任意订单,返回体经 `logic/common/reflect.go:58-64` 带出省市区详细地址、联系人、电话。 | +| **高** | `logic/mgt/order_get.go:21-24,39-46`、`logic/mgt/order_list_by_store.go:21-24,52-57` | 两处的 `agency` 分支在请求带 `agency` 时把 `ParseOptions` 置为 nil(任意角色可过),并把 `store_identity` **直接取请求参数**,`order_get.go:46` 的查询只有 `store_identity + identity` 两个条件 → 可读任意店铺订单。 | +| **高** | `logic/mgt/order_cancel.go:37`、`logic/summary/cancel.go:28,41`、`logic/mgt/order_returnable.go:19,49` | 都只判角色、不判订单归属:`OrderCancel` 按 `identity` 直改 `status=-1`;`OrderReturnable` 按 `identity` 直改 `approve/reason`;`Cancel` 按 `order_no` 查询与更新。同角色可操作他店订单。 | +| **高** | `logic/cart/create.go:27,49`、`logic/cart/modify.go:26,40`、`logic/cart/delete.go:22` | 购物车写操作缺归属约束:`Cart.Create` 的查重与更新条件是 `cart_identity + product_identity + spec_id`(不带 `passport_identity`),命中后 `Updates(&data)` 会把该行的 `passport_identity` 改成调用者(`:43,49`);`Cart.Modify`/`Cart.Delete` 只按 `id` 定位(`modify.go:26,40`、`delete.go:22`),`id` 为自增主键可枚举。 | +| **高** | `logic/cart/fetch.go:31` | `Where("cart_identity=? or passport_identity=?", in.GetCarIdentity(), auth.Identity)`:`or` 语义使**传入任意 `cart_identity` 即可读到他人的购物车**,与当前账号无关。 | +| **高** | `logic/summary/submit.go:68`、`logic/summary/quick_create_by_product.go:79`、`logic/mgt/order_create.go:101` | 三处都按 `identity` 直查 `address_library`,**从不校验 `owner_identity`/`owner_id`**(该列确实存在于 `module/ec/address/internal/models/address_library.go:17`)。可用任意地址标识下单,并把他人姓名、电话、详细地址写入订单(`address["contact"].(string)`、`address["detail"].(string)` 等,`order_create.go:115-121`)。 | +| **高** | `logic/cart/fetch.go:57` | `UnitPrice: product.CostPrice`——购物车返回体把**成本价**当作"实际单价"给前端(`:40-47` 的子查询只取了 `cost_price` 与最低销售价)。 | +| 中 | `logic/coupon/by_status.go:16-28` | 鉴权到位但业务越权面未收敛:`Where("passport_id=?", auth.ID)` 已按归属过滤(正确);但 `logic/summary/confirm.go:56` 按 `identity` 取券时既不校验 `passport_id`/`passport_identity`,也不校验 `started`/`expired` 有效期与 `condition` 门槛(字段见 `internal/models/order_coupon.go:13,17-19`)→ 可用他人券、过期券、未达门槛券。 | +| 中 | `logic/cart/create.go:37-41`、`logic/cart/modify.go:26,36-40` | `number` 无正值校验,可写入 0 或负数;负数数量在 `submit.go:202` 因 `detail.Number > 0` 判假而**跳过扣库存**,直接生成负数量明细与错误的金额。 | +| 中 | `internal/impl/with.go:85` | `NewPostgres` 中硬编码 `db = db.Debug()`(`NewMysql` 是按 `options.Debug` 判断,`:61-63`),生产环境同样输出含参数的 SQL 日志。 | +| 低 | `logic/summary/submit.go:206` | 库存不足时用标准库 `log.Printf` 输出,与同文件其它位置的 `printer.Error`(`:47` 等)混用,日志格式与级别不受统一管控。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `logic/summary/confirm.go:60,71-73` | **优惠券把金额加到总额**:`couponAmount = coupon.Amount` 之后 `summary.TotalPrice = summary.TotalPrice + couponAmount`。用券后应付金额变大。同时从不回写 `summary.CouponIdentity`/`CouponAmount`(全仓仅 `cancel.go:36-37` 清零、`order_summary.go:26-27` 定义),订单上看不到用了哪张券、抵了多少。 | +| **高** | `logic/summary/cancel.go:48` | 取消订单要按 `summary.CouponIdentity` 把券置回可用,但该字段从未被赋值 → `Where("identity=?", "")` 更新不到任何行,券永远停在 `status=3`;`confirm.go:59` 的 `Update` 也未检查 `RowsAffected`。 | +| **高** | `logic/summary/submit.go:200-218` | **超卖**:先 `Scan(&stock)` 试算(`:204`),再用 `Where("id = ? AND stock >= ?", ...).UpdateColumn("stock", stock - n)`(`:209-212`),但**从不检查 `RowsAffected`**。并发下条件更新影响 0 行时不会报错,订单仍然提交成功、库存未扣。对比 `logic/mgt/order_create.go:150-152` 是正确写法(有 `RowsAffected == 0` 判断)。 | +| **高** | `logic/summary/submit.go:82,91-93,166` | `Select("store_identity")` 未取 `id`,而 `:92` 用的是 `product.StoreId` → 该值恒为 0;且只在 `k == 0` 时赋值一次。多店铺下单时,所有订单摘要写入同一个 `store_id`(0 或首店 ID),店铺维度统计与后续按店查询全部失真。 | +| **高** | `logic/summary/submit.go:45,221-225` | 忽略 `SubmitRequest.id`(`proto/summary.proto:70` 明确注释为"购物车的ID数据"):下单按 `passport_identity` 取全车,成功后按 `passport_identity` **清空全车**。用户勾选部分商品下单会下整辆购物车,其余条目被删除。 | +| **高** | `logic/summary/quick_create_by_product.go:128,138`、`logic/mgt/order_create.go:75,77` | 单价取自 `mall_product.sales_price`,而 mall 侧的商品创建/更新逻辑**从不写这一列**(`module/ec/mall/internal/logic/product/item_create.go:137-160` 的 `itemToModel` 未映射 `SalesPrice`,该列仅有模型定义 `mall_product.go:40`)→ 实际为默认 0,快速下单与管理端建单的成交金额恒为 0;`mall` 侧仅在排序里读它(`module/ec/mall/internal/logic/product/item_fetch.go:73`)。 | +| **高** | `logic/summary/cancel.go:34-53`、`logic/mgt/order_cancel.go:37` | 取消订单不返还库存:库存在 `submit.go:209-211` / `order_create.go:141-143` 已扣减,取消时只改状态。`summary/cancel.go:34` 还在 `status != 1` 时**静默返回 `Code:0 "OK"`**,既不取消也不报错。 | +| **高** | `logic/mgt/order_cancel.go:37`、`logic/summary/simulate_pay.go:25`、`logic/summary/simulate_shipments.go:29`、`logic/summary/simulate_receiving.go:29` | **订单状态机无任何约束**:均可从任意状态跃迁(已发货→已取消、已取消→已支付、未支付→已收货)。`summary/cancel.go:34` 是唯一有状态判断的实现(但失败被静默吞掉)。 | +| 中 | `logic/summary/quick_create_by_product.go:102,128,138` | 同一订单内金额口径不一致:`trans_price`/`total_price` 用规格价 `spec["price"]`(`:102`),明细 `unit_price` 用商品价 `product["sales_price"]`(`:128,138`)→ 订单总额与明细之和不相等;`logic/mgt/order_create.go:75,77,87` 同样用商品价而 `SpecID` 取自另一条规格(`:90`)。 | +| 中 | `logic/summary/quick_create_by_product.go:50-53,68` | 规格按 `product_identity` 取第一条(`:68`,用 `Take`,请求里的 `spec_identity` 未被使用);而特殊商品分支(`serial_id` 为 `gas_by_kg`/`gas_by_bottle`)在 `:51` 用序列号找到商品后,`:68` 仍用 `in.ProductIdentity`(即 `gas_by_kg` 这类字符串)去查 `product_identity` → 必然 not found,特殊商品无法下单。`:53` 的商品查询也未带 `store_identity` 条件(仅 `:51` 分支有),可给 A 店订单塞 B 店商品。 | +| 中 | `logic/summary/quick_create_by_product.go:149-159` | 订单摘要与明细分两次独立 `Create`,**没有事务**:明细写入失败会留下无明细的订单主记录。对比 `submit.go:187-228` 与 `order_create.go:131-156` 都用了事务。 | +| 中 | `logic/mgt/order_approve.go:47-67` | `impl.DBService.Transaction(func...)` 的返回值被丢弃(`:47` 未接收 error),事务失败对外仍返回成功;退货入库用 `Where("product_identity = ?", specs.ProductIdentity)`(`:60`)——按商品维度给**该商品下所有规格**加库存(一个商品多条规格即库存虚增),而正确目标是订单明细里的 `spec_id`。 | +| 中 | `logic/summary/confirm.go:24,36-53`、`logic/summary/submit.go:168` | 运费与备注不落库:`logisFee` 唯一赋值在被注释的分支内(`:47`),恒为 0,`if logisFee > 0`(`:51`)永不成立;`ConfirmRequest.logistics_fee`(`proto/summary.proto:112`)与 `remark`(`:111`)从未被使用;`LogisticsFee` 全仓只在 `submit.go:168` 写 0。 | +| 中 | `logic/mgt/order_returnable.go:40-41` | 用裸 `errors.New("订单状态异常")` 返回(非 errcode,gRPC 侧表现为 Unknown);状态判断只排除 -1/6/7/8,已完成的 status=5 也可再次申请售后。 | +| 中 | `logic/coupon/by_status.go:28,52` | `ByStatus` 完全忽略入参 `status`(`Status.status`,`proto/coupon.proto:13`)→ 查询恒返回该用户全部券;返回体 `Amount: v.Intro` 把描述串当金额(`CouponItem.amount` 是 string,`proto/coupon.proto:25`),`started`/`expired`/`status` 三个字段未填充。 | +| 中 | `logic/summary/check.go:23-30` | 注释是"检测是否有未确认及付款的订单",实现是按 `passport_id` 取**第一条**订单且无任何状态过滤(`:26`);查不到时返回 `ErrDB` 而非明确的"无订单"。 | +| 中 | `logic/mgt/order_get.go:46-50` | 用 `Find` 而非 `First`:查不到不返回错误,`:48-50` 的 `gorm.ErrRecordNotFound` 分支是死分支,接口返回空的 `summary` 而非 404。 | +| 中 | `logic/summary/submit.go:45-52,195` | 空购物车也走通全流程:`summary`/`details` 为空切片时 `CreateInBatches(details, len(details))` 的 `batchSize` 为 0(`:195`),接口仍返回 `Code:0`,`order_no` 为空字符串。 | +| 中 | `internal/models/order_summary.go:72,86,131-138` | `GetSummaryCnt`/`GetSummaryList` 使用 `type`、`buyer_identity`、`merchant_identity`、`sign_status`、`pay_status`、`logistics_status`、`invoice_status` 等 `OrderSummary` 模型中不存在的列(模型字段见 `:12-62`),这些查询必然报 `Unknown column`。二函数与 `internal/models/query.go:22 QuicklyCreateOrder` 均无调用者。`:131` 还把 `maxPrice` 误传给了 `pay_status` 的条件。 | +| 低 | `internal/models/query.go:23,86` | 这两个函数用的是 `models.DBService`,而全部业务逻辑用的是 `impl.DBService`(赋值见 `internal/impl/with.go:46`)。聚合入口经 `service/dependencies.go:27` 只覆盖 `impl.DBService`,`models.DBService` 保持 nil → 一旦这些函数被调用即空指针。 | +| 低 | `logic/summary/submit.go:55-76` | `in.Address != nil` 时才用请求内地址,否则查地址库;但 `models.OrderAddress`(`internal/models/types.go:7-18`)含 `Email`/`ZipCode` 字段,而 `order_summary` 没有对应列,这两项落不了库。 | + +### 6.3 未完成实现 + +见第 3 节末尾表格。要点: + +- **管理端改单为假成功**:`logic/mgt/order_modify.go:25-33` 只有 TODO,返回 `Code:0 "OK"`,但接口已在 `internal/server/mgt_server.go:24` 与 `pb/mgt.pb.gw.go:537` 接线,调用方会误以为改单成功。 +- **三个"模拟"接口是唯一的状态推进手段**:`simulate_pay.go:17,25`、`simulate_shipments.go:17,29`、`simulate_receiving.go:17,29` 注释自述为模拟,硬编码交易号/金额/支付类型,但没有真实支付与物流对接的实现存在。 +- **注释掉的代码块**:`quick_create_by_product.go:88-97`(配送员查询)、`:119-120`(`DeliveryIdentity` 赋值)、`confirm.go:37-49`(地址查询与运费计算),对应的请求字段 `member_identity`、`delivery_time`、`logistics_fee`、`remark` 因此无效果。 +- **状态语义只在注释里**:`order_summary.go:30`(订单状态)与 `:39`(approve 编码)、`order_approve.go:29` 的取值说明都没有枚举定义,`order_returnable.go:24` 又硬编码了 `1/2/3`。 +- **README 未编写**:`module/ec/order/README.md:1-2` 只有标题。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `service/dependencies.go:19-29` + `pkgs/all/internal/service/order.go:15` | `Dependencies` 声明了 `Cache *cache.Cache`,`applyDependencies` 却不处理该字段;`pkgs/all` 仍传入 `Cache: impl.MemoryService` → 依赖注入字段无效(与 `module/base/logs` 同类问题)。 | +| 中 | `internal/impl/with.go:24-31` | `config.Spec.Cache == ""` 时 `RedisCache` 为 nil,但 `:30` 无条件取 `RedisCache.DB` → 空指针;且 `printer.Info` 的格式化参数与占位符不符。 | +| 中 | `logic/summary/quick_create_by_product.go:43,62,102,104,112-117,128-129,133,136-137,140,143-145`、`logic/mgt/order_create.go:62,74-75,83,85-86,89-94,115-122`、`logic/mgt/order_get.go:42-43`、`logic/mgt/order_list_by_store.go:55-56` | 大量对 GORM `map[string]any` 结果的**无保护类型断言**(`store["status"].(int16)`、`product["status"].(int32)`、`spec["price"].(int64)`、`address["province"].(string)` 等)。相关列在两边都有 `default` 但**没有 `not null`**(如 `mall_store.status` 定义于 `bsm-sdk/core/types/db.go:34`),任一列为 NULL 或两侧驱动返回类型变化即 panic;`order_get.go:42-43` 在 token 的 `owner` 不是对象时同样 panic。【信息不足】未在真实数据库确认各列的可空性与驱动返回的具体类型。 | +| 中 | 全模块 | 无单元测试:`test/rpc/*.go` 均为 `//go:build integration` 的手工联调用例(`summary_test.go:1`、`cart_test.go:1`),依赖真实服务端口 `127.0.0.1:12442` 与环境变量 `BSM_ORDER_TOKEN`(`test/rpc/bisic_test.go:13,15,17`),断言只打印返回消息,无任何 `t.Fatal` 判断;`logic/`、`models/`、`internal/impl/` 0 测试。 | +| 中 | `internal/logic/common/no.go:10-14` + `internal/models/order_summary.go:18` | 订单号 = 秒级时间戳拼接 + 6 位随机数,`order_no` 只有普通索引(非唯一)→ 同秒并发存在重号可能;而 `summary/cancel.go:28`、`summary/confirm.go:30`、`common/get.go:28` 都用 `First`(取任意一条),重号会导致操作到错误订单。 | +| 中 | `logic/summary/list.go:43-45`、`logic/mgt/order_list_by_store.go:49-51` | 只要 `page_size > 0` 就直接 `Limit(int(in.PageSize))`,**没有上限**;`OrderListByStore` 还预加载了全部明细(`:60` `Preload("OrderDetails")`)→ 一次请求可拉走全表订单与明细。 | +| 低 | `internal/models/impl.go:31,36,42` | 驱动不支持、DB 打开失败、AutoMigrate 失败三处都走 `log.Fatalln`(直接退出进程),与 `internal/impl/with.go:35,44,53` 的 `panic` 风格并存,启动期错误处理方式不统一。 | +| 低 | `etc/order_{dev,test,prod}.yaml:10` | `Cache: redis://null:...@127.0.0.1:6379/`(无 DB 序号),而 `internal/impl/with.go:30` 会打印 `RedisCache.DB`;`RedisCache` 除该打印外全模块无使用点(死代码)。 | +| 低 | `etc/order_{dev,test,prod}.yaml:19` | `Anonymous: - order.ping.hello`,但 order 的 proto 与 gw 路由(`pb/*.pb.gw.go` 共 22 个方法)中没有 `ping`,是死配置。 | +| 低 | `logic/mgt/order_cancel.go:29-36`、`logic/mgt/order_get.go:34-37` | 重复的参数校验(`in.Identity == ""` 判断两次),`order_cancel.go:29` 与 `:34` 的注释还互相错位。 | +| 低 | `internal/models/impl.go:85` | `NewPostgres` 中 `db = db.Debug()` 与 `NewMysql` 的可选 Debug 行为不一致(详见 6.1)。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| O1 | 高 | 模拟支付/发货/收货在生产可达、无角色校验、金额硬编码 | 资金与订单状态被任意篡改 | +| O2 | 高 | `OrderApprove` 完全无鉴权 | 退款/退货被匿名审批 + 库存被改写 | +| O3 | 高 | 跨服务直连同库,直接读写 `mall_product`/`mall_product_spec`/`mall_store`/`address_library` | 任一表结构变更即 order 运行期报错;订单侧可直接改商品库存 | +| O4 | 高 | 优惠券把金额加到订单总额(`confirm.go:72`) | 金额计算错误,用户应付变高 | +| O5 | 高 | `submit` 扣库存不检查 `RowsAffected` | 超卖(订单成功但未扣库存) | +| O6 | 高 | 多店铺下单 `store_id` 恒为 0 | 订单归属错误,店铺维度统计/查询失真 | +| O7 | 高 | `Submit` 忽略选中条目、清空全车 | 用户被强制下整辆购物车 | +| O8 | 高 | 快速下单/管理端建单单价取自从不写入的 `sales_price` | 成交金额恒为 0 | +| O9 | 高 | 取消订单不返库存 + 状态机无约束 + 取消失败静默成功 | 库存丢失、状态错乱 | +| O10 | 高 | 订单详情越权(`Summary.Get`)、地址越权使用(4 处)、购物车越权(读/写/删) | 用户 PII 泄露与数据被篡改 | +| O11 | 高 | 购物车返回成本价(`cart/fetch.go:57`) | 商业成本信息泄露 | +| O12 | 中 | 优惠券归属/有效期/门槛均不校验 | 券被滥用 | +| O13 | 中 | 无事务的建单(`quick_create_by_product` 两次 Create)、事务错误被丢弃(`order_approve.go:47`) | 脏数据 | +| O14 | 中 | 退货入库按 `product_identity` 回补 | 库存虚增 | +| O15 | 中 | 运费/备注/优惠额字段全链路未落库 | 订单金额要素缺失 | +| O16 | 中 | 分页无上限 + `Preload` 全明细 | 服务被单请求拖垮 | +| O17 | 中 | 无保护类型断言遍布订单逻辑 | 运行期 panic | +| O18 | 中 | 无单元测试、双份 DB 全局、订单号无唯一约束 | 可维护性与数据一致性 | +| O19 | 低 | README 空缺、`Anonymous` 死配置、`RedisCache` 死代码、Debug 常开 | 认知成本与日志噪音 | + +## 8. 修复建议(务实项) + +1. **物理下线模拟接口**(O1):把 `SimulatePay`/`SimulateShipments`/`SimulateReceiving` 从 `proto/summary.proto:33,36,39` 移除或改为仅内部注册(不挂 Gateway);在实现落地前,最直接的做法是三个方法体统一返回 `errcode.ErrUnimplemented`,并把 `logic/summary/simulate_pay.go:25` 的硬编码 `PayTradeNo`/`PayAmount` 删除。若必须保留,至少改成 `ParseOptions{RoleValue: "Mall_Admin"}` 并在更新前加 `status = 1`(支付)/`status = 2`(发货)/`status = 3`(收货)的条件与订单归属校验。 +2. **`OrderApprove` 补鉴权与归属校验**(O2):`order_approve.go:17` 前加 `service.ParseMetaCtx(ctx, &service.ParseOptions{RoleValue: "Mall_Admin"})`,`:24` 与 `:34/:48/:73/:86` 的 `Where` 补 `store_identity`(取自 token owner);`:47` 接收 `Transaction` 的返回值并判断;`:60` 的 `Where("product_identity = ?")` 改为 `Where("id = ?", specs.SpecID)`。 +3. **优惠券计算改减并落库**(O4、O15):`confirm.go:72` 改为 `summary.TotalPrice - couponAmount`(并加下限判断);同时把 `summary.CouponIdentity = in.CouponIdentity`、`summary.CouponAmount = couponAmount` 写入,这样 `cancel.go:48` 的退券逻辑才能生效;`:56` 取券时补 `passport_id`、`started`/`expired`、`condition` 校验。 +4. **库存扣减与返还**(O5、O9、O14):`submit.go:209-217` 参照 `order_create.go:150-152` 增加 `if result.RowsAffected == 0 { return errors.New("库存不足") }`,并删掉 `:204` 的预读(条件更新已足够);`summary/cancel.go:34-53` 与 `mgt/order_cancel.go:37` 在状态置 -1 的同事务内按 `order_details.spec_id` 回补库存;`cancel.go` 在状态不允许取消时应返回错误而不是 `Code:0`。 +5. **修正 `Submit` 的三处错误**(O6、O7):`submit.go:82` 的 `Select` 补上 `id`;删除 `:91-93` 的 `k == 0` 逻辑,改为按 `store_identity` 分组后就地取该组商品的店铺 ID;`:45` 改为按 `in.GetId()` 过滤购物车(为空时再退化为整车),`:221-225` 只删除已下单的那些 `order_cart.id`;`:45` 之后加 `len(cart) == 0` 的显式报错。 +6. **金额口径统一**(O8、O13):`quick_create_by_product.go:102,128,138` 与 `order_create.go:75,77,87` 统一使用 `mall_product_spec.price`(`quick_create_by_product.go:68` 已取到 spec),`order_create.go:67` 的规格查询改为按请求传入的规格标识(`SpecList` 增加 `spec_id`/`spec_no`)而不是 `product_identity` 取任意一条;`quick_create_by_product.go:149-159` 的两条 `Create` 包进 `DBService.Transaction`。 +7. **地址归属校验**(O10):`submit.go:68`、`quick_create_by_product.go:79`、`order_create.go:101` 的地址查询补 `owner_identity = ?`(取自 token),并在 `:79` 处把 `in.AddressIdentity` 与查询结果一并校验;`summary/get.go:27` 按调用方身份收敛查询条件(买家用 `passport_identity`,商家用 `store_identity`)。 +8. **购物车归属收敛**(O10、O11):`cart/modify.go:26,40`、`cart/delete.go:22` 的 `Where` 补 `passport_identity = ?`;`cart/create.go:27,49` 的查重条件补 `passport_identity`;`cart/fetch.go:31` 的 `or` 改为按 `passport_identity` 过滤、`cart_identity` 仅作附加条件;`cart/fetch.go:57` 的 `UnitPrice` 改用 `product.SalesPrice`,不要返回 `CostPrice`。 +9. **输入校验**(O12):`cart/create.go:39` 与 `cart/modify.go:26,40` 增加 `number > 0` 校验;`order_returnable.go:40` 改用 `errcode`。 +10. **降低跨库耦合的运行期风险**(O3):至少把 order 侧对 `mall_product`/`mall_product_spec`/`mall_store`/`address_library` 的查询集中到 `internal/models/query.go` 一处(当前散落在 8 个文件),并给每个投影结构体补注释说明依赖的列名与归属服务;`etc/order_prod.yaml:7` 的 DSN 与 mall 指向同一库这一点应在配置注释中显式写明,避免被误改为独立库。 +11. **分页与日志**(O16、O19):`summary/list.go:43-45`、`mgt/order_list_by_store.go:49-51` 增加 `if size > 200 { size = 200 }`;`internal/impl/with.go:85` 去掉硬编码 `.Debug()`;`submit.go:206` 改用 `printer.Error`。 +12. **类型断言防抖**(O17):`quick_create_by_product.go`、`order_create.go`、`order_get.go`、`order_list_by_store.go` 中所有 `.(T)` 改为逗号断言(`v, ok := m["k"].(T)`)并在 `!ok` 时返回 `errcode.ErrDB`/`ErrRecordNotFound`。 +13. **订单号与约束**(O18):`internal/models/order_summary.go:18` 的 `order_no` 改为 `uniqueIndex`,并在 `Submit`/`OrderCreate` 生成冲突时重试;`internal/models/query.go:23,86` 若确定不用则删除,要用则改为 `impl.DBService`。 +14. **清理与测试**:删除 `internal/models/order_summary.go:70-151`、`internal/models/query.go:22` 三个坏 SQL 函数(或按模型重写后接线);删掉 `order_cancel.go:29-36` 的重复校验与 `etc/*.yaml:19` 的 `order.ping.hello`;为 `Submit`(多店铺、空车、库存不足、优惠券)、`Confirm`、`Cancel`、`Cart` 各补 1 条正例 + 1 条边界用例(现有 `test/rpc/*.go` 只打印返回,无断言);补写 `README.md`。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。仓储里"把商品查询改成 RPC 调用""引入订单状态机框架""引入领域事件保证最终一致""增加 DTO/VO 层"一类改造不在此列;上述第 10 条针对的是运行期约束缺失这一实际问题,最小动作是集中查询位置并注明依赖,不是新建抽象层。 diff --git a/docs/passport.md b/docs/passport.md new file mode 100644 index 0000000..b9472cc --- /dev/null +++ b/docs/passport.md @@ -0,0 +1,171 @@ +# passport(用户账号与身份认证)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/passport` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件约 44 个(含 `cmd/`、`internal/`、`service/`、`proto/` 6 个);`pb/` 下约 21 个生成 `.go` 仅看签名 | +| 入口 | `cmd/main`(gRPC + grpc-gateway 单进程)、`cmd/cli`(调试入口)、聚合入口 `pkgs/all`、`pkgs/ecmall` | +| 对外协议 | gRPC(`passport.Account/Login/Register/Forget/Verify`)+ REST(grpc-gateway,路径形如 `POST /passport.Login/Pwd`) | +| 结论摘要 | 认证核心存在**致命缺陷**:验证码登录/注册/找回均**不校验验证码**;找回密码仅凭 `identity` 即可改密且以**未加盐 MD5** 存储;密码哈希使用 `bcrypt.MinCost`(cost=4);KYC 回调为**空 stub**;Token 缓存键前缀大小写不一致。 | + +## 1. 服务定位与职责 + +用户通行证(账号、密码、第三方登录、KYC 认证、标签、统计)的统一身份服务。负责账号注册、密码/验证码登录、密码找回、KYC 验证回调、用户资料与标签管理。它是**认证与账户存储**服务,不负责网关鉴权本身(鉴权依赖上层网关注入 metadata,由 `service.ParseMetaCtx` 读取)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:配置→初始化 impl→建 gRPC/Gateway→`server.New(nil)`→`service.New` 启动 | +| `cmd/cli/main.go` | 调试入口:仅打印数据库配置 | +| `internal/config/config.go` | 配置结构(`Base/Databases/MicroService/Gateway/Token/Kyc/WeChat` 等)与校验;设置 JWT 过期 | +| `internal/impl/impl.go` | 初始化 Redis、DB、Etcd、内存缓存实例 | +| `internal/vars/{token,status,provider}.go` | 常量:`TokenPrefix="/TOKEN/"`、账号状态、第三方 provider | +| `internal/excode/ex.go` | **空文件**,无内容 | +| `internal/models/*` | 11 个模型 + `query.go`(InitData/Exists) + `cache.go`(Redis 缓存与验证码接口) | +| `internal/logic/common/token.go` | 用 `env.Runtime.JwtSecretKey` 签发 JWT | +| `internal/logic/{login,register,forget,account,verify}/*` | 各业务方法实现 | +| `internal/server/*` | gRPC server 注册(5 个 service 全部委托 logic) | +| `service/{expose,dependencies}.go` | 聚合宿主注入接口(供 `pkgs/all` 复用共享连接) | +| `proto/*.proto` | 5 个服务契约(**无 `google.api.http` 注解**,REST 路径由网关生成) | + +依赖注入:`service.Dependencies` 支持外部传入 Redis/Etcd/DB/Cache,非 nil 时覆盖 `internal/impl` 对象,供聚合入口复用共享连接。 + +## 3. 接口清单 + +> proto 中未声明 `ping/hello` 服务,但 `etc/*.yaml` 的 `MicroService.Anonymous` 列出 `passport.ping.hello`,与当前模块实现不一致(疑似残留)。 + +| 方法 | 路径(gRPC / REST) | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| `Login.Pwd` | `passport.Login/Pwd` | 账号+密码登录 | 否(公开) | `logic/login/pwd.go` | +| `Login.Code` | `passport.Login/Code` | 验证码登录 | 否(公开) | `logic/login/code.go` | +| `Login.Quick` | `passport.Login/Quick` | 验证码快捷登录并注册 | 否(公开) | `logic/login/quick.go` | +| `Register.Pwd` | `passport.Register/Pwd` | 账号密码注册 | 否(公开) | `logic/register/{pwd,do}.go` | +| `Register.Code` | `passport.Register/Code` | 手机验证码注册 | 否(公开) | `logic/register/{code,do}.go` | +| `Forget.Verify` | `passport.Forget/Verify` | 验证手机号和验证码 | 否(公开) | `logic/forget/verify.go` | +| `Forget.Reset` | `passport.Forget/Reset` | 重置密码 | 否(公开) | `logic/forget/reset.go` | +| `Account.Get` | `passport.Account/Get` | 获取完整用户信息 | 是(`ParseMetaCtx`) | `logic/account/get.go` | +| `Account.SetData` | `passport.Account/SetData` | 更新用户资料 | 是 | `logic/account/set_data.go` | +| `Account.SetPassword` | `passport.Account/SetPassword` | 修改密码 | 是 | `logic/account/set_password.go` | +| `Account.TagCreate` | `passport.Account/TagCreate` | 新增标签 | 是 | `logic/account/tag_create.go` | +| `Account.TagRemove` | `passport.Account/TagRemove` | 删除标签 | 是 | `logic/account/tag_remove.go` | +| `Account.Statistics` | `passport.Account/Statistics` | 获取统计数据 | 是 | `logic/account/statistics.go` | +| `Verify.Request` | `passport.Verify/Request` | 发起 KYC 验证 | 是 | `logic/verify/request.go` | +| `Verify.JumioCallback` | `passport.Verify/JumioCallback` | KYC 回调 | 否(公开) | `logic/verify/jumio_callback.go` | + +> 全部声明的 gRPC 方法均已委托到 logic(server 层无 Unimplemented 未实现项);但 `Verify.JumioCallback` 与 `Forget.Verify` 为**逻辑占位/伪实现**(见 6.3)。 + +## 4. 数据模型与表 + +| 表名 | 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | --- | +| `passport_account` | `id` | uint | PK | 自增主键 | +| | `identity` | varchar | - | 用户唯一码(对外公开) | +| | `account` | varchar(255) | 默认 `''` | 账号 | +| | `phone` | varchar(20) | 默认 `''` | 手机号 | +| | `email` | varchar(255) | 默认 `''` | 邮箱 | +| | `password` | varchar(255) | not null | 密码(bcrypt;重置时误写为 MD5) | +| | `salt` | varchar(255) | not null | 密码盐 | +| `passport_data` | `passport_id`/`passport_identity` | - | - | 关联账号 | +| | `nickname/avatar/sign/cover` | varchar | 默认 `''` | 资料 | +| | `sex/birthday` | int8/time | - | 性别/生日 | +| | `country/province/city/area` | varchar | 默认 `''` | 地区 | +| | `score/level` | int32 | 默认 0 | 积分/等级 | +| | `rights` | varchar(255) | 默认 `''` | 权限 | +| | `email_verify/phone_verify/face_verify/document_verify/kyc_verify` | int32 | 默认 0 | 各验证状态 | +| `passport_tags` | `passport_id`/`name`/`icon` | - | - | 用户标签 | +| `passport_verify` | `passport_id` + 各验证时间/证件/KYC 字段 | - | - | 认证详情(KYC 回调未写入) | +| `passport_provider` | `provider`/`provider_id`/`access_token`/`refresh_token` | - | - | 第三方登录绑定 | +| `passport_score` / `passport_statistics` / `passport_rights_expiry` / `passport_notify` | 见各模型文件 | - | - | 积分/统计/特权有效期/消息 | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST passport.Login/Code"] --> B["校验 phone/code/country 非空"] + B --> C["GetPassportAccountByField(phone)"] + C --> D["直接签发 JWT 返回"] + D --> E["未比对 Redis 验证码"] + F["POST passport.Forget/Reset"] --> G["校验 identity/password 非空"] + G --> H["Update password = Md5(pwd)"] + H --> I["账号被接管/密码无法登录"] + J["POST passport.Login/Pwd"] --> K["bcrypt.CompareHashAndPassword"] + K --> L["签发 JWT 并写 Redis(/TOKEN/)"] + M["POST passport.Register/Pwd"] --> N["bcrypt.MinCost 哈希+盐"] + N --> O["Create account + data"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/login/code.go:16-59` | **验证码登录不校验验证码**。仅判断 `in.Phone/in.Code/in.Country` 非空后直接 `GetPassportAccountByField("phone",...)` 并签发 JWT;从未调用 `GetVerificationCodeCache` 比对 `in.Code`。任意已知手机号 + 任意 code 即可登录。 | +| **高** | `internal/logic/login/quick.go:20-64` | **快捷登录并注册同样不校验 code**,且当手机号不存在时(:32-64)自动创建账号。攻击者可用任意 code 对任意手机号批量建号并登录。 | +| **高** | `internal/logic/forget/reset.go:17-41` | **找回密码无验证码/令牌校验**。仅凭 `identity` + `password` 即 `Update("password", ...)`。前置 `Forget.Verify` 也未真正校验 code(见 6.3)。`identity` 在登录/注册响应中公开返回,攻击者可据此直接接管任意账号。 | +| **高** | `internal/logic/forget/reset.go:31` | **以未加盐 MD5 存储密码**:`Update("password", utils.Md5(in.Password))`,与全模块 bcrypt 体系不一致;且登录校验走 bcrypt(`login/pwd.go:27`),重置后的密码既不安全又无法用于登录(逻辑自相矛盾)。 | +| 中 | `internal/logic/register/do.go:30`、`internal/logic/account/set_password.go:35`、`internal/models/query.go:21` | **密码哈希 cost 过低**:使用 `bcrypt.MinCost`(=4)而非默认 10,抗暴力破解能力弱。 | +| 中 | `internal/vars/token.go:3` vs `internal/logic/register/do.go:84` | **Token 缓存键前缀不一致**:`vars.TokenPrefix="/TOKEN/"` 用于 `login/pwd.go:48`、`login/code.go:41`;而 `register/do.go:84` 用 `config.Spec.Token.Prefix`(yaml 为 `/token/`)。同一 identity 在 Redis 中形成两把不同键,登出/校验可能取错或失效。 | +| 低 | `internal/logic/login/code.go:41` | 写 Redis token 时 TTL 传 `0`(永不过期),与 `login/pwd.go:48` 用 `_vars.JwtExpire` 不一致;缓存 token 永久留存(仅依赖 JWT 自身过期)。 | +| 低 | `test/grpc/main.go:45` | 硬编码测试 JWT(`authorization` 明文 token,含 `id/identity`)出现在测试代码,若被复用可越权。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/logic/account/get.go:44` | `impl.DBService.Where("passport_id=?", AUTH.ID).First(data)` **未处理 error**;查询失败或记录不存在时 `data` 仍是零值指针(非 nil),`if data != nil` 恒真,后续 `data.Birthday.Format` 等执行,DB 错误被吞掉、返回空资料。 | +| 中 | `internal/logic/forget/reset.go:31` | 重置密码**不同步更新 `salt`**(`passport_account.salt` 列存在),且写入 MD5,与 bcrypt 校验路径不兼容(同 6.1)。 | +| 中 | `internal/logic/register/do.go:53,70` | 账号与扩展数据两次 `Create` **非事务**:若第二步失败,会留下无扩展数据的孤儿账号。 | +| 低 | `internal/logic/register/code.go:13-31` | `Code` 注册仅校验手机号格式与唯一性,**未校验 `in.Code`**,与"验证码注册"语义不符(同 6.1 高)。 | + +### 6.3 未完成实现 + +- `internal/logic/verify/jumio_callback.go:13-35`:KYC 回调**仅 `printer.Info` 打印完整 payload 后返回成功**,未校验回调签名、未更新 `passport_verify` 的 KYC 状态——典型占位实现。 +- `internal/logic/forget/verify.go:14-32`:名义"验证手机号和验证码",实际**不比对 code**(无任何 Redis 验证码查询),属伪实现。 +- `internal/models/query.go:12-39`:`InitData` 计划初始化 `demo/welcome` 默认账号(密码 bcrypt MinCost),但 `cmd/main/main.go:37` 已注释未启用;含**硬编码默认口令**,属潜在隐患(一旦启用即成后门)。 +- `internal/models/cache.go:144-172`:`IncrementLoginAttempts`/`ClearLoginAttempts` **从未被调用**,README 声称的"内置限流"未落地。 +- `internal/models/cache.go:124-142`:`SetVerificationCodeCache`/`GetVerificationCodeCache` 提供验证码存取,但**所有登录/注册/找回逻辑均未调用**,验证码形同虚设(与 6.1 高关联)。 +- `internal/excode/ex.go`:空文件,错误码未定义。 +- `cmd/cli/main.go`:仅打印 DB 配置,无实际用途(调试残留)。 +- `etc/*.yaml` 的 `SecretKey: CHANGE_ME` 字段在 `config.go` 的 `SrvConfig` **无对应字段**,未被解析;真实 JWT 密钥来自 `env.Runtime.JwtSecretKey`(`logic/common/token.go:10`),其初始化不在本模块可见范围【信息不足】。 +- 全模块**无 `*_test.go` 单元测试**。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/logic/account/get.go:44` | DB 错误未处理(同 6.2)。 | +| 中 | `internal/logic/login/*`、`forget/reset.go`、`verify/jumio_callback.go` | 多处 `printer.Error/Info` 输出内部错误与敏感参数(如 `jumio_callback.go:21` 打印完整 KYC 回调含 PII、`reset.go:33` 打印 identity、`login/pwd.go:34` 打印 DB 错误),存在信息泄露。 | +| 低 | `internal/vars/token.go:3` vs `config` | Token 前缀两套定义(见 6.1)。 | +| 低 | 全模块 | 无单元测试;`InitData`/`CreateAccount` 等存在死代码或未被调用路径。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| P1 | 高 | 验证码登录不校验 code | 任意手机号可登录,认证失效 | +| P2 | 高 | 快捷登录/注册不校验 code 且自动建号 | 批量伪造账号 | +| P3 | 高 | 找回密码仅凭 identity 改密、无校验 | 任意账号被接管 | +| P4 | 高 | 重置密码以未加盐 MD5 存储且与 bcrypt 不兼容 | 密码泄露风险 + 重置后无法登录 | +| P5 | 中 | bcrypt.MinCost(cost=4) | 密码暴力破解成本降低 | +| P6 | 中 | Token 缓存键前缀大小写不一致 | 登录态缓存错乱/登出失效 | +| P7 | 中 | KYC 回调空 stub、Forget.Verify 伪实现 | 认证状态不可信 | +| P8 | 中 | 验证码缓存接口从未被调用 | 全链路验证码机制失效 | +| P9 | 低 | 内部错误/PII 经日志泄露 | 信息泄露 | +| P10 | 低 | DB 错误未处理、无事务、无测试 | 健壮性与可维护性 | + +## 8. 修复建议(务实项) + +1. **验证码必须校验**:在 `login/code.go`、`login/quick.go`、`register/code.go`、`forget/verify.go` 中调用 `GetVerificationCodeCache(phone)` 并比对 `in.Code`(一次一消);`forget/reset.go` 必须要求先通过 `Verify` 且校验一次性令牌,再允许改密。 +2. **重置密码用 bcrypt**:`forget/reset.go:31` 改为 `bcrypt.GenerateFromPassword([]byte(newPwd+salt), bcrypt.DefaultCost)` 并同步更新 `salt`,与登录校验一致。 +3. **提高哈希 cost**:`register/do.go`、`account/set_password.go`、`query.go` 的 `bcrypt.MinCost` 改为 `bcrypt.DefaultCost`(10)。 +4. **统一 Token 前缀**:仅保留一处定义(建议用 `config.Spec.Token.Prefix`),`login` 与 `register` 共用,避免 `/TOKEN/` 与 `/token/` 双键。 +5. **KYC 回调落地**:`jumio_callback.go` 增加签名校验并更新 `passport_verify` 的 `kyc_verify`/`kyc_status`;`Forget.Verify` 真正比对验证码。 +6. **收敛默认账号**:保持 `InitData` 不启用,或删除该函数与硬编码 `welcome` 口令。 +7. **错误处理**:`account/get.go:44` 处理 `First` 的 error;`register/do.go` 两次 Create 用事务包裹。 +8. **清理**:移除 `cmd/cli` 调试输出、空 `excode/ex.go`、未调用的限流函数(或真正接入);敏感信息勿进 `printer`。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。 diff --git a/docs/pkgs-all.md b/docs/pkgs-all.md new file mode 100644 index 0000000..1cd46a6 --- /dev/null +++ b/docs/pkgs-all.md @@ -0,0 +1,198 @@ +# pkgs/all(全量聚合宿主)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `pkgs/all`(module 路径 `bsm/full/pkgs/all`,全量聚合宿主) | +| 所属域 | 聚合运行入口(组合基础与平台 10 个 + 电商交易 4 个 + 资金支付 1 个,共 15 个服务) | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 27 个 / 1331 行(其中 `_test.go` 4 个 / 238 行);`etc/default_dev.yaml` 145 行;`go.mod` 148 行 | +| 监听端口 | gRPC `0.0.0.0:12000`、HTTP `0.0.0.0:12001`(`pkgs/all/etc/default_dev.yaml:3-8`) | +| 结论摘要 | 分层清晰、分流实现可读,但**上线路径缺失**(无 prod 配置、构建脚本不覆盖聚合入口)、**两个密钥默认值可直接启动**(JWT 签名密钥与 session 密钥均为占位串)且入口不做校验、**grpc-gateway 的 404 与业务 NotFound 无法区分**导致真实错误响应被 Gin 的 404 覆盖、**gRPC reflection 无开关且匿名可达**、**全链路无 TLS**、**进程级客户端 IP 可被 X-Forwarded-For 伪造**。另有匿名白名单与模块自身声明口径不一致、缺注册表测试护栏等一致性问题。 | + +## 1. 定位与职责 + +`pkgs/all` 是"全量聚合宿主":在**一个进程**内启动 15 个业务模块的 gRPC 服务、grpc-gateway 路由和原生 REST 路由,进程内注入一份共享的数据库、Redis、etcd 与内存缓存,并统一管理监听地址、鉴权和优雅关闭。 + +它**不承载任何业务逻辑**:所有 `internal/service/*.go` 只做 `ExposeOptions` 组装与注入(`pkgs/all/internal/service/ads.go:9-20` 等 15 个文件,每个 14-22 行),路由与落库全部在 `module/` 各模块内。它也不负责建表与种子数据:聚合路径从不调用模块的 `impl.NewImpl()`,只调用模块 `service.applyDependencies`(`module/ec/order/service/dependencies.go:19-29`)。 + +与 `pkgs/ecmall` 的关系:两者是同一份代码的**两份拷贝**,`internal/server` 下 4 个文件逐字节相同(见 6.3-C5),注册表仅差 `cloud` 一项。 + +## 2. 代码结构与关键文件 + +| 路径 | 行数 | 职责 | +| --- | --- | --- | +| `cmd/main/main.go` | 58 | 进程入口:`--workspace` 解析 → `config.New` → `impl.NewImpl` → `server.New` → `service.Expose` → 注册信号 → `srv.Start` | +| `internal/config/config.go` | 124 | 聚合配置结构(`Server`/`Authorization`/`Databases`/`Services`/5 个模块专项节)、监听地址规范化与校验、共享配置回填、`Enabled` 服务开关 | +| `internal/server/server.go` | 133 | gRPC server(挂 unary 鉴权拦截器 + reflection)、grpc-gateway mux、gin engine、`/rpc/:module/:service/:method` 动态路由、404 兜底分流、`*http.Server` 参数、`Stop` | +| `internal/server/authorization.go` | 126 | JWT 签发校验(HS256)、HTTP 中间件、gRPC unary 拦截器、匿名白名单匹配、`/rpc/...` → `/{pkg}.{Svc}/{M}` 路径规范化 | +| `internal/server/dynamic.go` | 194 | 动态 RPC:通过 gRPC reflection 取 descriptor → `dynamicpb` + `protojson` → 内网 gRPC `Invoke`,方法描述符缓存、4 MiB 请求体上限、头部白名单透传 | +| `internal/server/response.go` | 78 | 统一错误体 `{code,message,details,timeseq}`、gRPC code → SDK errcode 映射 | +| `internal/impl/impl.go` | 42 | 进程级共享资源:Memory、Redis、DB、Etcd | +| `internal/service/service.go` | 46 | 注册表(15 项)+ session 中间件 + 按 `Enabled` 逐个 `Expose` | +| `internal/service/{ads,cloud,cms,feedback,fts,initial,logs,mgt,passport,sender,address,mall,market,order,wallet}.go` | 14-22 | 各模块注入:`Dependencies` + (`GRPC`/`Gateway` 或 `Engine`)+(可选 `Config`) | +| `etc/default_dev.yaml` | 145 | 唯一一份配置:端口、DB/Redis DSN、`SecretKey`、`Authorization`(含匿名白名单)、`Services`、5 个模块专项节 | +| `internal/server/authorization_test.go` | 102 | 匿名/裸 JWT/`Bearer` 拒绝、iat 超期拒绝 | +| `internal/server/dynamic_test.go` | 76 | 动态 RPC 调 `grpc.health.v1.Health/Check`、头部透传过滤 | +| `internal/server/server_test.go` | 24 | gin 路由可用性 | +| `internal/config/config_test.go` | 36 | dev 配置非空 + gRPC/HTTP 端口不同 + 密钥长度 + 5 个专项节存在 | +| `go.mod` | 148 | 15 个 module replace 到 `../../module/...`,`bsm-sdk/core` replace 到 `../../../../bsm-sdk/core` | + +注册表(`internal/service/service.go:14-33`,15 项,顺序即注册顺序):`ads, cloud, cms, feedback, fts, initial, logs, mgt, passport, sender, address, mall, market, order, wallet`。与 `etc/default_dev.yaml:46-61` 的 `Services` 列表逐项一致(已核对)。 + +## 3. 启动流程与请求分流 + +启动顺序(`cmd/main/main.go:22-57`): + +1. `flag` 解析 `--workspace`(默认 `default`),经 `utils.MustString` 按 `^[a-zA-Z0-9][a-zA-Z0-9_-]{0,63}$` 校验并转小写(SDK `utils/ext.go:39-46`)。 +2. `config.New(ServiceKey)`:`conf.New` 读 `/etc/_.yaml` → 归一化两个监听地址 → 校验两地址不同、`Authorization.Key` 非空且长度 ∈ {16,24,32}、`Expire>0` → 写 `env.JwtSecretKey` 与 `coreVars.JwtExpire` → 回填共享配置(`pkgs/all/internal/config/config.go:56-83`)。 +3. `impl.NewImpl()`:Memory → Redis → DB → Etcd(`internal/impl/impl.go:30-42`)。注意 DB 缺失时 SDK 直接 `panic`(见 6.3-C3)。 +4. `allserver.New(...)`:建 gRPC server(**无条件** `reflection.Register`)+ gin engine(`gin.New()` + Logger + Recovery)。 +5. `service.Expose(srv)`:先挂 session 中间件,再按注册表 + `Enabled` 逐个注册;任一模块 `Expose` 出错则整体 `panic`(`internal/service/service.go:35-46`、`cmd/main/main.go:42-44`)。 +6. `srv.Start(grpcAddr, httpAddr)`:先监听两个 TCP,再建动态网关(失败则关闭两个 listener),挂 `/rpc/:module/:service/:method`,组装 404 兜底 handler,起两个 goroutine 并阻塞在第一个返回值上。 + +请求分流(`internal/server/server.go:61-78` + `authorization.go:96-120`): + +```mermaid +flowchart TD + C["客户端请求"] --> A["httpMiddleware:canonicalHTTPPath + isAnonymous
authorization.go:55-66"] + A -->|"匿名 或 JWT 有效"| G["grpc-gateway mux ServeHTTP(响应写入 bufferedResponse)
server.go:63-65"] + A -->|"鉴权失败"| E1["HTTP 200 + {code,message,details,timeseq}
authorization.go:122-126"] + G -->|"status != 404"| F1["flush 缓冲响应给客户端
server.go:66-68"] + G -->|"status == 404"| H["gin ServeHTTP 兜底
server.go:70"] + H --> R1["POST /rpc/:module/:service/:method
server.go:61"] + R1 --> D["resolveMethod:gRPC reflection 取 descriptor(带缓存)
dynamic.go:115-176"] + D --> D1["streaming → 直接报 Unimplemented
dynamic.go:79-82"] + D --> D2["protojson 解 JSON(≤4 MiB)→ 内网 gRPC Invoke(insecure)
dynamic.go:84-112"] + H --> R2["/rest/fts/... /rest/logs/... /rest/mgt/...(各模块自注册)
service/{fts,logs,mgt}.go → module 内层 JwtAuth"] + H -->|"无路由"| N["gin 默认 404(纯文本)"] + D2 --> E2["内网 gRPC 再次经过 unary 拦截器校验 JWT
authorization.go:42-53"] +``` + +四条入口的能力边界: + +| 入口 | 形态 | 覆盖范围 | 鉴权节点 | +| --- | --- | --- | --- | +| 原生 gRPC | `/{pkg}.{Svc}/{M}` | 12 个 gRPC 模块(fts/logs/mgt 无 gRPC 面) | gRPC unary 拦截器(`server.go:32`) | +| grpc-gateway | `POST /{pkg}.{Svc}/{M}` | 同 12 个模块 | HTTP 中间件 + 内网 unary 拦截器(双重) | +| 动态 RPC | `POST /rpc/{pkg}/{Svc}/{M}` | 同 12 个模块,仅 unary | HTTP 中间件(按网关形态匹配白名单)+ 内网 unary 拦截器 | +| 原生 REST | `/rest/{fts,logs,mgt}/...` | 仅这 3 个模块 | HTTP 中间件 + 模块内层 `JwtAuth`(仅 `/rest/fts/uploader`、`/rest/mgt/{user,app,role,pmn,dpt}/*`) | + +## 4. 配置与安全 + +| 项 | 现状 | 证据 | +| --- | --- | --- | +| 监听地址 | gRPC `12000` / HTTP `12001`,`BindIP 0.0.0.0`;两地址相同则 panic | `etc/default_dev.yaml:3-8`;`config.go:60-62` | +| 端口缺省 | `Port` 为空时**随机分配 1024-65535**,且"两地址不同"校验必然通过 | SDK `conf/new.go:87-97`;`config.go:85-89` | +| JWT | HS256,裸 JWT 放 `Authorization` 头(无 `Bearer`);校验算法、`exp`、`iat` 不为未来、`now-iat ≤ Expire` | `authorization.go:68-94`;`authorization_test.go:41` | +| JWT 密钥 | `Authorization.Key` 必须 16/24/32 字节;写入全局 `env.JwtSecretKey`,模块签发处共用 | `config.go:63-73` | +| 默认密钥 | `CHANGE_ME_32_BYTE_JWT_SECRET_KEY`(恰好 32 字节 → 通过校验) | `etc/default_dev.yaml:22`;`config.go:66-69` | +| session 密钥 | `config.Spec.SecretKey`(= `conf.Base.SecretKey`)拼 `"-session"` 作为 cookie store 的 HMAC key;**无任何校验** | `internal/service/service.go:36`;SDK `conf/types.go:11`;`config.go:63-72` 未校验 | +| 默认 session 密钥 | `CHANGE_ME` | `etc/default_dev.yaml:17` | +| 鉴权中间件覆盖 | HTTP:单一中间件包住"gateway + gin"整个 handler,因此 `/rpc/...` 与 `/rest/...` 全部在覆盖内;gRPC:仅 `grpc.UnaryInterceptor`(无 Stream) | `server.go:32,74`;`authorization.go:55-66` | +| 匿名白名单 | 11 个 gRPC 网关形态 + 8 个 REST 路径,共 19 条;`/grpc.reflection.`、`/grpc.health.` 前缀**硬编码放行** | `etc/default_dev.yaml:24-43`;`authorization.go:96-103` | +| gRPC reflection | 无条件注册、无开关;且被 `isAnonymous` 放行为匿名 | `server.go:33`;`authorization.go:98-100` | +| TLS | 全模块无 TLS:`net.Listen("tcp", ...)`、内网 `insecure.NewCredentials()`;`grep -i "tls\|certificate\|x509" pkgs` 无命中 | `server.go:45,49`;`dynamic.go:45` | +| HTTP server 参数 | `ReadHeaderTimeout=10s`、`IdleTimeout=120s`、`MaxHeaderBytes=1MiB`、**无 `ReadTimeout`/`WriteTimeout`** | `server.go:72-78` | +| gRPC server 参数 | 无 MaxRecvMsgSize、无 keepalive、无 StreamInterceptor | `server.go:32` | +| 客户端 IP | `gin.New()` 未 `SetTrustedProxies`(gin v1.12.0 默认信任 `0.0.0.0/0`、`::/0`) | `server.go:34`;gin `gin.go:225,214` | +| 限流 | 入口层无任何限流/防爆破逻辑 | `grep -i "ratelimit\|limiter\|throttle" pkgs` 无命中 | +| 白名单一致性 | 与模块自身声明不一致(logs 三条 REST、ads.Fetch/ByPos) | 见 6.1-A7 | +| 服务开关 | `BSM_SERVICES`(非空即覆盖 `Services`)> `Services`;名字大小写不敏感、含 `all` 即全开 | `config.go:109-124` | +| 运行模式 | gin 未按 `BSM_RuntimeMode` 设置模式 | `server.go:34`;SDK `middleware/mode.go:9-16` | +| CORS | 聚合未启用;独立 fts/mgt 启用(SDK 实现为 `AllowAllOrigins: true`) | `server.go:34`;`module/base/fts/cmd/main/main.go:26`、`module/base/mgt/cmd/main/main.go:39`;SDK `middleware/cors.go:8-18` | + +匿名白名单逐条核对结果:11 条 gRPC 条目全部对应真实方法(`passport` 的 `Login.{Pwd,Code,Quick}`、`Register.{Pwd,Code}`、`Forget.{Verify,Reset}`、`Verify.{Request,JumioCallback}` 见 `module/base/passport/proto/*.proto`;`mall.Staff/Login` 见 `module/ec/mall/proto/staff.proto:9`;`market.Agency/Login` 见 `module/ec/market/proto/agency.proto:9`),8 条 REST 条目也全部对应已注册路由(`module/base/fts/internal/routers/register.go:22-23`、`module/base/logs/internal/routers/register.go:24`、`module/base/mgt/internal/routers/register.go:31-36`)。即**无死条目**;问题在"该放行的没放行"(见 6.1-A7)。 + +## 5. 审计发现 + +### 5.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `etc/default_dev.yaml:17` + `internal/service/service.go:36` | **session 密钥用未校验的占位值**。`srv.HTTP.Use(sessions.Sessions("mysession", cookie.NewStore([]byte(config.Spec.SecretKey+"-session"))))`,`SecretKey` 取自 `conf.Base`(SDK `conf/types.go:11`),配置值为 `CHANGE_ME`;`config.New` 只校验 `Authorization.Key`(`config.go:63-72`),从不校验 `SecretKey`。默认配置下 cookie 的 HMAC 密钥是公开可猜字符串,`mysession` 可被伪造(当前唯一使用点是 `module/base/mgt/internal/logic/hello/ping.go:25-35` 的 SessionDemo 演示)。 | +| **高** | `etc/default_dev.yaml:22` + `internal/config/config.go:66-69` | **JWT 签名密钥默认值是可用的公开字面量**。`CHANGE_ME_32_BYTE_JWT_SECRET_KEY` 恰好 32 字节,直接通过长度校验并写入 `env.NewEnv().JwtSecretKey`(`config.go:73`)。任何知道该字面量的人都能自行 HS256 签发带 `iat/exp` 的 token,通过 `authorization.go:74-79` 的校验。模块的 token 也由同一密钥签发(`module/base/mgt/internal/logic/pub/login.go:90`、`module/base/passport/internal/logic/common/token.go:10`),无法通过"只改某一侧"缓解。 | +| **高** | `pkgs/all/etc`(目录仅 1 个文件) | **不存在生产配置**。`conf.New` 读 `/etc/default_.yaml`(SDK `conf/new.go:35-36`),`BSM_RuntimeMode` 默认 `dev`(SDK `env/env.go:20`);设为 `prod` 时读 `default_prod.yaml`,缺失即回退 `workspace_default_prod.yaml`(`new.go:39-43`),两者都不存在 → `log.Fatalf`(`new.go:49-52`)。即 `BSM_RuntimeMode=prod` 的聚合进程**无法启动**,只能以 dev 模式上线(偏离 README 的"评估关闭 reflection、入口层启用 TLS"前提)。 | +| **中** | `internal/server/server.go:33` + `authorization.go:96-100` | **gRPC reflection 无开关且匿名可达**。`reflection.Register(grpcServer)` 无条件执行,`isAnonymous` 对 `/grpc.reflection.` 前缀直接返回 true,因此匿名用户可拉取全部已注册服务的 `FileDescriptorProto`,枚举 35 个服务/方法名与完整消息结构(`dynamic.go:124-143` 本身就走这条路径)。这不泄露数据,但等于把内部 API 全貌公开,便于后续针对性构造调用。 | +| **中** | `internal/server/authorization.go:98` | `/grpc.health.` 被硬编码为匿名,但全仓库没有任何模块注册 health 服务(`grep -rn "health" module` 仅命中注释与 `wallet/logic/payment/hello.go` 的字符串)。当前是死名单;一旦按惯例补上 health 服务,它会先天地对匿名开放。 | +| **中** | `internal/server/server.go:45,49` + `dynamic.go:45` | **全链路明文**。两个 listener 都是 `net.Listen("tcp", ...)`,无 `TLSConfig`;动态网关到本机 gRPC 用 `insecure.NewCredentials()`;`pkgs` 下 `grep -i "tls\|certificate\|x509"` 无任何命中。JWT 与业务数据以明文在 `0.0.0.0:12000/12001` 上传输,反向代理是否终止 TLS 属于部署前提,进程内无任何能力。 | +| **中** | `internal/server/server.go:34`、`dynamic.go:180-188` | **进程级客户端 IP 可被伪造**。聚合用 `gin.New()` 且未调用 `SetTrustedProxies`,gin v1.12.0 默认 `trustedProxies = {"0.0.0.0/0","::/0"}` 且 `ForwardedByClientIP=true`(gin `gin.go:225,214`),因此 `c.ClientIP()` 直接采用客户端自报的 `X-Forwarded-For`;动态 RPC 又把请求里所有 `x-` 前缀头原样透传给内网 gRPC(`dynamic.go:182-188`),grpc-gateway 也会把请求 `X-Forwarded-For` 拼进 metadata(gateway `runtime/context.go:185-193`)。受影响落点示例:登录 token 里记录的 client IP(`module/base/mgt/internal/logic/pub/login.go:90`)。 | +| **中** | `internal/server/authorization.go:55-66`、`server.go:35` | **匿名登录入口无任何限流**。白名单里的 `passport.Login/Pwd|Code|Quick`、`passport.Register/*`、`passport.Forget/*`、`market.Agency/Login`、`mall.Staff/Login`、`/rest/mgt/login` 均可无限次调用;入口层只有 `gin.Logger()` 与 JWT 校验,没有失败计数、锁定或速率限制(`grep -i "ratelimit\|limiter\|throttle" pkgs` 无命中)。 | +| **中** | `etc/default_dev.yaml:24-43` 对比 `module/base/logs/internal/routers/register.go:18-29`、`module/base/ads/etc/ads_dev.yaml:15-16` | **匿名口径不一致(双向)**。① logs 的 `POST /rest/logs/create|fetch|total` 注册在模块的"匿名组"(该模块自己的 `JwtAuth` 被注释掉,`register.go:23`),而聚合白名单只列了 `/rest/logs/ping` → 同一路由独立进程匿名、聚合进程要求 JWT。② `ads.Fetch/ByPos` 在模块 dev 配置里被声明匿名,聚合白名单没有它 → 独立可调、聚合 401(与 `docs/ads.md` 的同类结论一致)。③ 被白名单放开的 3 个登录入口(passport 4 条、market/mall 各 1 条)在聚合下确实是匿名的,口径一致。 | +| **低** | `internal/server/authorization.go:96-120` | 白名单为**精确字符串匹配**,且 gRPC 网关形态与 REST 真实路径混列在同一 `[]string` 里;`/rpc/...` 只在"恰好 4 段"时才被改写成网关形态(`canonicalHTTPPath`)。新增方法/路由若忘记登记,只会静默变成"需要 JWT",没有任何一致性校验或启动期告警。 | +| **低** | `internal/server/dynamic.go:115-176` | 动态 RPC 的 descriptor 缓存键由**请求路径拼接**(`moduleName + "." + serviceShortName`,`dynamic.go:73`),未命中时每次都向内网反射服务发起一次 RPC;缓存无上限、无 TTL。任意匿名调用者可用随机符号名制造大量反射查询(放大内网调用),且 `protojson` 解析错误信息原样回显(`dynamic.go:95-98`)。 | + +### 5.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/server/server.go:63-71` | **gateway 的 404 与业务 `NotFound` 无法区分,真实响应被丢弃**。`recorder.status != http.StatusNotFound` 被当作"网关未命中路由"的判据;但 grpc-gateway 对业务 `NotFound` 也渲染成 HTTP 404(gateway `runtime/errors.go:48-49`),与路由未命中的 404(`runtime/mux.go:464,552`)状态码完全相同。因此任何 gateway 方法返回 `codes.NotFound`("记录不存在"这类高频业务分支)时,**已经生成好的 `{code,message,...}` 响应被丢弃**,请求落到 gin 后因无匹配路由,客户端最终收到 gin 的纯文本 `404 page not found`。 | +| **中** | `internal/server/server.go:82-89` | **启动期错误只处理一半**。`errCh` 容量 2、只读一个值就返回:当 HTTP `Serve` 先失败时,gRPC listener 与 server 既不 `Stop` 也不 `Close`(反之亦然);`Start` 无 `Shutdown` 兜底。 | +| **中** | `cmd/main/main.go:48-53` + `internal/server/server.go:92-110` | **优雅关闭的超时被复用且错误被丢弃**。`Stop(ctx)` 内部先用同一个 `ctx` 等 `GracefulStop`,超时后 `s.GRPC.Stop()`,随后 `s.http.Shutdown(ctx)` 使用的仍是**已经过期**的 ctx → 立即返回 `context deadline exceeded`,在途 REST 请求被直接中断;外层 `_ = srv.Stop(ctx)` 又丢弃了返回值,失败完全无声。 | +| **中** | `internal/server/server.go:112-133` | **全量缓冲响应**。所有 grpc-gateway 响应先写入 `bytes.Buffer`(`server.go:64`),判定不是 404 后才整体 `flush`(`server.go:125-132`):响应体在内存中同时存在缓冲与写出两份,列表类接口(mall 商品、order 订单、cms 文章)在大响应/高并发下放大内存占用;`flush` 复制响应头用 `Add` 而非 `Set`(`server.go:128`);`WriteHeader` 无重复调用保护(`server.go:123`)。 | +| **中** | `internal/server/server.go:32` | **流式 RPC 无鉴权拦截**。鉴权只挂在 `grpc.UnaryInterceptor`,未注册 `StreamInterceptor`;`isAnonymous` 虽放行 `/grpc.reflection.`,但**任何**流式方法都不经过拦截器。当前 66 个 proto 文件、35 个服务均无 `stream` 声明(`grep -n "stream " --include=*.proto module` 无命中),因此尚无实际暴露;动态 RPC 侧已显式拒绝流式(`dynamic.go:79-82`),风险落在原生 gRPC 面。 | +| **中** | `etc/default_dev.yaml:73` + `module/base/fts/internal/logic/provider.go:71` | **配置里的下载地址没有对应路由**。聚合配置把上传站点写成 `http://127.0.0.1:12001/files`,本地存储返回的地址是 `Site + "/" + bucket + "/" + subdirpath + "/" + fileName`;但 fts 只注册了 `/rest/fts/ping|config|uploader`(`module/base/fts/internal/routers/register.go:11-26`),全仓库没有 `Static`/`StaticFS`/`/files` 路由(`grep -rn "Static\|/files" --include=*.go module/base/fts` 无命中)→ 上传成功后返回的 URL 在聚合 HTTP 端口上必然 404。 | +| **低** | `internal/config/config_test.go:11-36` | **配置测试只做非空校验**:校验 `Services` 非空、两端口不同、密钥长度、5 个专项节存在,但**不校验 `Services` 与注册表一致**;`pkgs/all` 也缺少 ecmall 那样的 `internal/service/service_test.go`(实测 `go test ./pkgs/all/...` 输出 `internal/service [no test files]`)。改注册表(加/删服务)在 `pkgs/all` 没有测试护栏,只能靠人眼对齐 `service.go:14-33` 与 `etc/default_dev.yaml:46-61`。 | +| **低** | `internal/config/config.go:109-124` + `internal/service/service.go:35-46` | **服务列表写错会静默启动空宿主**。`Enabled` 只做"名字匹配/含 all"判断,不校验名字是否存在;`Expose` 对 0 个服务注册不报错。若 `BSM_SERVICES` 或 `Services` 里全是无效名(例如把 `mall` 写成 `mail`),进程会正常监听 `12000/12001`,但除 reflection 外全部 404,且没有任何启动期提示。 | +| **低** | `internal/config/config.go:85-89` | **端口漏配会随机绑定**。`conf.CheckPort` 在 `Port` 为空时随机生成 1024-65535(SDK `conf/new.go:90-97`);聚合有两个 listener,`Port` 缺失时各自随机,"两地址必须不同"的校验必然通过 → 服务在随机端口"成功"启动,网关/探针配置全部失效,随机端口还可能恰好被占用而启动失败。 | + +### 5.3 未完成/不一致 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `pkgs/all/etc/`(仅 `default_dev.yaml`)+ `scripts/build-all-linux.sh:7,30` | 聚合入口**既没有 prod 配置也不被构建脚本覆盖**:构建脚本 `MODULE_ROOT="${WORKSPACE_ROOT}/module"` 且用 `find "${MODULE_ROOT}"` 收集模块,`pkgs/` 完全不在范围内,`pkgs/*` 也不会被复制 `*_prod.yaml`。结合 5.1 的"无 prod 配置",聚合入口当前只有"开发模式 + 手工 go build"这一条路径。 | +| **中** | `internal/config/config.go:78` + SDK `with/databases.go:13-15` | **`Databases` 未纳入配置校验**。`conf.NotNil(Spec.Service, Spec.Cache)` 只校验两项;`Databases` 缺失时 `impl.NewImpl()`(`impl.go:38`)内部 `panic("No Database Source Found !")` —— 崩溃点比配置校验更晚、信息更差;且 `impl.NewImpl()` 在 `server.New`/`Expose` 之前执行(`cmd/main/main.go:32-44`),此时 Redis/Etcd 已建连,失败回滚缺失(无 `defer Close`)。 | +| **中** | `module/base/passport/internal/logic/verify/jumio_callback.go:13-34` | **被匿名放开的第三方回调是空壳**。`/passport.Verify/JumioCallback` 在聚合白名单里(`etc/default_dev.yaml:33`),实现只 `printer.Info` 后返回成功,代码内自带 TODO:「In production, implement proper callback handling logic」,且不校验回调签名。当前无实际危害(不写库),但补实现时必须先加签名校验,否则任何人可直接伪造 KYC 结果。 | +| **低** | `pkgs/all` vs `pkgs/ecmall` | **注册表护栏不对等**:ecmall 有 `internal/service/service_test.go`(断言各模块 gRPC/REST 面注册成功)与 `config_test.go:15` 的 `ecmallServices` 一致性断言,`pkgs/all` 两者都没有(只有 5.2 提到的非空校验)。 | +| **低** | `internal/server/{server,authorization,dynamic,response}.go` | **与 ecmall 的两份拷贝必须手工同步**:diff 结果这 4 个文件逐字节相同(唯一差异是 `server.go:79-80` 的日志字面量 `all`/`ecmall`,ecmall);`internal/impl/impl.go` 仅 import 路径不同;`internal/config/config.go` 仅注释不同;注册表仅差一行(`pkgs/ecmall/internal/service/service.go` 无 `{"cloud", exposeCloud}`)。任何单侧修改都会让两个入口的鉴权/分流行为产生差异,目前没有测试或 CI 阻止这种漂移。 | +| **低** | 无 | 聚合入口**没有进程级健康检查端点**。独立入口都注册了 `app.HEAD("/", infra.Health)`(`module/base/fts/cmd/main/main.go:36`、`module/base/logs/cmd/main/main.go:39`、`module/base/mgt/cmd/main/main.go:41`),聚合只有三个模块自带的 `/rest/*/ping`,`HEAD /` 会落到 gin 404。 | + +### 5.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **中** | `internal/server/server.go:72-78` | 未设置 `WriteTimeout`(也未设 `ReadTimeout`),只有 `ReadHeaderTimeout`/`IdleTimeout`/`MaxHeaderBytes`;慢速读取响应可长期占用连接。同时 gin 侧没有请求体大小限制(`/rest/fts/uploader` 的上限只在 fts 逻辑层),而动态 RPC 侧有 4 MiB 限制(`dynamic.go:30,84-92`)——两条入口的边界不对称。 | +| **低** | `internal/server/server.go:34` | 未按 `BSM_RuntimeMode` 设置 gin 模式(独立入口会调用 `middleware.Mode`,SDK `middleware/mode.go:9-16`)→ 聚合在生产环境仍停留在 gin Debug 模式(除非外部设置 `GIN_MODE`)。 | +| **低** | `internal/server/server.go:34` | 聚合未启用 CORS,而独立 fts/mgt 启用(SDK 实现是 `AllowAllOrigins: true`)→ 同一接口"独立进程可被浏览器调用、聚合入口不可用"的行为差异。 | +| **低** | `internal/server/server.go:79-80` | 用 `fmt.Printf` 直接打印监听地址,绕过 SDK 的 `printer`;`gin.Logger()` 无条件记录全部请求(`server.go:35`),探针/大流量下噪音明显。 | +| **低** | `internal/server/server.go:112-133` | `bufferedResponse` 只实现 `Header/WriteHeader/Write`,没有 `http.Flusher`/`Hijacker`/`CloseNotifier`;当前 unary JSON 路径可用,但任何流式/大文件/SSE 型网关响应都会静默退化。 | +| **低** | `internal/server/dynamic.go:34-36` | 方法描述符缓存是 `map` + `sync.RWMutex`,无容量上限、无淘汰;键取自请求路径,长期运行下键空间取决于外部输入。 | +| **低** | `internal/server/authorization.go:26-39` | 匿名白名单在 `newAuthorization` 中经 `normalizePath` 归一化后装入 map,重复项静默去重;条目写错(如把 `/rest/logs/ping` 写成 `rest/logs/ping`)不会被发现——`normalizePath` 会补上前导 `/` 掩盖这类笔误,但写成 `/rest/logs/ping/` 之类则会被静默视为不同路径。 | + +## 6. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| A1 | 高 | 生产/测试配置缺失,构建脚本不覆盖 `pkgs/*` | 上线路径、部署可靠性 | +| A2 | 高 | session 密钥取未校验的 `SecretKey`(默认 `CHANGE_ME`) | cookie 完整性与伪造 | +| A3 | 高 | JWT 密钥默认值是可直接使用的公开字面量 | 全系统身份伪造 | +| A4 | 高 | gateway 404 与业务 `NotFound` 不可区分,真实响应被覆盖 | 所有 gateway 方法的错误语义 | +| A5 | 中 | reflection 无开关且匿名可达 | 内部 API 全貌泄露 | +| A6 | 中 | 无 TLS | 传输机密性 | +| A7 | 中 | 客户端 IP 可经 XFF 伪造并透传到内网 metadata | 审计与风控数据可信性 | +| A8 | 中 | 匿名登录入口无任何限流 | 账号安全 | +| A9 | 中 | 匿名口径与模块声明不一致(logs 三条 REST、ads.Fetch/ByPos) | 独立/聚合行为不一致、上线 401 | +| A10 | 中 | 流式 RPC 不受鉴权拦截器覆盖(当前无流式方法) | 未来扩接口时的越权风险 | +| A11 | 中 | Fts 本地存储返回的 `/files` 地址无对应路由 | 文件下载不可用 | +| A12 | 中 | 优雅关闭超时后 HTTP 立即中断且错误被丢弃 | 关闭期请求可靠性 | +| A13 | 中 | gateway 响应全量缓冲 | 大响应下的内存放大 | +| A14 | 中 | `Databases` 未校验 + 资源初始化无失败回滚 | 启动期崩溃与连接泄漏 | +| A15 | 中 | Jumio 回调空壳(无签名校验)却已匿名放开 | 补实现后的数据伪造风险 | +| A16 | 低 | 无注册表一致性测试与告警、服务名写错静默空宿主 | 变更回归 | +| A17 | 低 | 端口漏配随机绑定、无 WriteTimeout、gin 模式/CORS 与独立入口不一致、无健康端点、两份拷贝需手工同步 | 运维与可维护性 | + +## 7. 修复建议(务实项) + +1. **补生产配置与构建覆盖**(A1):在 `pkgs/all/etc/` 增加 `default_prod.yaml`(至少替换全部 `CHANGE_ME`、明确 `BindIP`),并把 `scripts/build-all-linux.sh` 的 `MODULE_ROOT` 扩展到 `pkgs/`(或额外的 `PKGS_ROOT` 循环),同时补 `pkgs/all/etc/default_prod.yaml` 的拷贝分支。 +2. **让占位密钥不能启动**(A2、A3):在 `internal/config/config.go` 现有的 `Authorization.Key` 校验旁,加两条最小校验——`Authorization.Key` 不得等于样例字面量、`SecretKey` 非空且不得等于 `CHANGE_ME`(或直接要求 32 字节)。这是 4 行代码级别的改动,不要引入配置加密框架。 +3. **修 404 分流**(A4):让 gateway 用独立标记区分"路由未命中"。最小做法是自定义 `gwRuntime.ServeMux` 的 `WithRoutingErrorHandler`(或在 `bufferedResponse` 上记录"是否由 `DefaultRoutingErrorHandler` 写出"),只在 `codes.NotFound` **且** 路由未命中时才回退 gin;其余情况直接 `flush`。 +4. **关掉或收紧 reflection**(A5):给 `Server` 增加一个 `Authorization.Reflection bool`(默认 false),仅在为 true 时 `reflection.Register`;同时从 `isAnonymous` 中删除 `/grpc.health.` 前缀,等真有 health 服务时再显式加白名单。注意动态 RPC 依赖反射,关闭前需确认该入口是否保留——若保留,则应改为"仅允许 loopback 调用动态入口"。 +5. **客户端 IP 与头部清理**(A7):在 `server.New` 里 `engine.SetTrustedProxies(...)` 指定真实代理网段(无代理时传 `nil` 关闭 XFF 取值),并在 `outgoingMetadata` 中**不要**透传客户端自带的 `x-forwarded-for`/`x-real-ip`(改为服务端重算)。 +6. **匿名白名单口径对齐**(A9):二选一后同步两处——若要 logs 的 `create/fetch/total` 在聚合下也匿名,则加入 `Authorization.Anonymous`;若不打算匿名,则把 `module/base/logs/internal/routers/register.go:18-29` 的"匿名组"改名并补上模块自身 JwtAuth。`ads.Fetch/ByPos` 同理(改配置即可,零代码)。 +7. **文件下载路由**(A11):要么在 fts 的路由注册里补上静态目录(本地存储方式),要么把 `Fts.Local.Site` 改成真实存在的下载前缀,二者取其一,避免返回不可访问的 URL。 +8. **启动与关闭的收尾**(A12、A14、A17):`Start` 里任一 `Serve` 返回错误时关闭两个 listener 并 `Shutdown`;`Stop` 里给 `http.Shutdown` 单独一个未过期的短超时 ctx,并把 `main.go:52` 的错误打印出来;`config.New` 增加 `Databases` 非空校验;`Port` 为空时改为**报错退出**而不是随机端口。 +9. **入口限流**(A8):仅在 `httpMiddleware` 内对匿名路径加一个基于内存的简单计数/令牌桶(按路径 + 客户端 IP),不引入外部依赖;不要为它建框架。 +10. **加固两份拷贝与测试护栏**(A16):给 `pkgs/all` 补一个与 `pkgs/ecmall/internal/service/service_test.go` 等价的注册表测试,并在 `internal/config/config_test.go` 中断言 `Services` 与注册表逐项一致;两份 `internal/server` 拷贝的同步问题,用"两处测试用例内容一致"来兜(不加抽取层)。 +11. **补齐入口可观测与一致性**(A13、A17 的其余项):`http.Server` 补 `WriteTimeout`;`engine.SetMode` 按 `BSM_RuntimeMode` 设置;注册 `HEAD /` 健康端点;如需 CORS 再显式启用(当前缺失本身不算缺陷,但需与独立入口行为对齐并明确决策)。 +12. **流式方法的前置约束**(A10):在 `grpc.NewServer` 处补 `grpc.ChainStreamInterceptor`,与 unary 共用同一份 `validate`;若短期内不打算支持流式,也在代码中写明"新增流式方法前必须先加拦截器"。 + +> 本报告只列与现有实现直接相关的修复项,不引入新的分层或抽象封装。审计中"把两个聚合入口的 server 抽成公共包""给动态 RPC 引入请求 DTO 层""用服务网格解决 mTLS"一类改造不在此列。 diff --git a/docs/pkgs-ecmall.md b/docs/pkgs-ecmall.md new file mode 100644 index 0000000..32978bb --- /dev/null +++ b/docs/pkgs-ecmall.md @@ -0,0 +1,205 @@ +# pkgs/ecmall(电商聚合宿主)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `pkgs/ecmall`(module 路径 `bsm/full/pkgs/ecmall`,电商聚合宿主) | +| 所属域 | 聚合运行入口(电商交易 4 + 资金支付 1 + 平台运营 6 + 基础支撑 3,共 14 个服务) | +| 审计日期 | 2026-09-22 | +| 代码规模 | Go 文件 27 个 / 1395 行(其中 `_test.go` 5 个 / 317 行);`etc/default_dev.yaml` 151 行;`go.mod` 146 行;另有 `AGENT.md` 776 行 | +| 监听端口 | gRPC `0.0.0.0:12300`、HTTP `0.0.0.0:12301`(`pkgs/ecmall/etc/default_dev.yaml:3-8`) | +| 结论摘要 | 与 `pkgs/all` 是同一实现的另一份拷贝(`internal/server` 4 个文件逐字节相同),差异仅在注册表(少 `cloud`)与端口;**多了一层注册表测试护栏**(`service_test.go`)、并用 `AGENT.md §4.5` 主动记录了匿名口径差异,是本次两个聚合入口里工程质量更好的一份。但**共性问题同样存在**:无 prod 配置与构建覆盖、两个密钥默认值可启动且不校验、gateway 404 与业务 `NotFound` 不可区分、reflection 无开关且匿名可枚举、无 TLS、进程级 XFF 可伪造、`/files` 下载地址无路由。此外**聚合模式不建表不播种**(`Mgt.InitRootUser: true` 在聚合下不生效),注册表清单在 3 处各存一份需手工同步。 | + +## 1. 定位与职责 + +`pkgs/ecmall` 是面向电商业务的聚合宿主:在单进程内注册 **14 个**模块的 gRPC/gateway/REST 入口,共享一份数据库、Redis、etcd 与内存缓存,监听独立的 `12300`/`12301`,因此可与 `pkgs/all`(`12000`/`12001`)同时运行。 + +它同样不承载业务逻辑:`internal/service/*.go` 每个文件只有 14-22 行,只做 `ExposeOptions` 组装(`pkgs/ecmall/internal/service/ads.go:9-20`)。 + +与 `pkgs/all` 的差异(已用 `diff` 全量核对): + +| 维度 | `pkgs/all` | `pkgs/ecmall` | 证据 | +| --- | --- | --- | --- | +| 注册表 | 15 项(含 `cloud`) | 14 项(无 `cloud`) | `pkgs/all/internal/service/service.go:14-33` vs `pkgs/ecmall/internal/service/service.go:14-32` | +| 端口 | 12000 / 12001 | 12300 / 12301 | 两侧 `etc/default_dev.yaml:3-8` | +| `Fts.Local.Site` | `http://127.0.0.1:12001/files` | `http://127.0.0.1:12301/files` | `pkgs/all/etc/default_dev.yaml:73` vs `pkgs/ecmall/etc/default_dev.yaml:79` | +| `internal/server/*` | —— | **完全相同**(仅 `server.go:79-80` 的日志字面量 `all`/`ecmall` 不同) | `diff` 结果 | +| `internal/config/config.go` | —— | 仅多一段顶部注释(`config.go:20-25`) | `diff` 结果 | +| `internal/impl/impl.go` | —— | 仅 import 路径不同 | `diff` 结果 | +| 注册表测试 | 无 | `internal/service/service_test.go`(62 行) | 实测 `go test ./pkgs/all/...` 输出 `internal/service [no test files]` | +| 配置一致性断言 | 只校验非空 | 额外断言 `Services` == `ecmallServices` 常量 | `internal/config/config_test.go:15,43-52` | +| `Cloud` 配置节 | 无(cloud 无专项配置) | 无 | 两侧 `SrvConfig` 均无 `Cloud` 字段 | + +`internal/config/config.go:20-25` 的注释明确了这个取舍:「Modules that are not part of ecmall keep their own configuration in their own process」。 + +## 2. 代码结构与关键文件 + +| 路径 | 行数 | 职责 | +| --- | --- | --- | +| `cmd/main/main.go` | 58 | 进程入口:`--workspace` → `config.New` → `impl.NewImpl` → `server.New` → `service.Expose` → 信号 → `srv.Start` | +| `internal/config/config.go` | 130 | 配置结构 + 监听地址规范化 + 密钥校验 + 共享配置回填 + `Enabled` | +| `internal/config/config_test.go` | 53 | dev 配置非空、端口不同、密钥长度、5 个专项节存在,且 `Services` 必须逐项等于 `ecmallServices` | +| `internal/server/server.go` | 133 | gRPC server(unary 拦截器 + reflection)、gateway mux、gin engine、动态路由、404 兜底、`*http.Server` 参数、`Stop` | +| `internal/server/authorization.go` | 126 | HS256 JWT 校验、HTTP 中间件、gRPC unary 拦截器、匿名白名单、`/rpc/...` 路径规范化 | +| `internal/server/dynamic.go` | 194 | 动态 RPC(reflection 取 descriptor + `dynamicpb` + `protojson` + 内网 `Invoke`) | +| `internal/server/response.go` | 78 | 统一错误体与 gRPC code → errcode 映射 | +| `internal/impl/impl.go` | 42 | 进程级共享资源(Memory/Redis/DB/Etcd) | +| `internal/service/service.go` | 45 | 注册表(14 项)+ session 中间件 + 逐个 `Expose` | +| `internal/service/service_test.go` | 62 | 注册表护栏:断言 11 个模块前缀的 gRPC 服务 + 6 条 REST 路由确实注册成功 | +| `internal/service/{ads,cms,feedback,fts,initial,logs,mgt,passport,sender,address,mall,market,order,wallet}.go` | 14-22 | 各模块注入(`Dependencies` ± `GRPC`/`Gateway`/`Engine`/`Config`) | +| `etc/default_dev.yaml` | 151 | 唯一一份配置 | +| `AGENT.md` | 776 | ecmall 开发上下文(§3.5 记录聚合不建表不播种、§4.5 记录匿名口径差异、§5 服务注册表) | +| `go.mod` | 146 | 14 个 module replace 到 `../../module/...`,`bsm-sdk/core` → `../../../../bsm-sdk/core` | + +注册表(`internal/service/service.go:14-32`):`ads, cms, feedback, fts, initial, logs, mgt, passport, sender, address, mall, market, order, wallet`,与 `etc/default_dev.yaml:50-64` 的 `Services` 逐项一致(已核对,并被 `config_test.go:43-52` 强制)。 + +## 3. 启动流程与请求分流 + +启动顺序(`cmd/main/main.go:22-57`):`--workspace` 校验 → `config.New`(读 `ecmall/etc/default_.yaml`、归一化地址、校验两地址不同与密钥、写入 `env.JwtSecretKey`/`coreVars.JwtExpire`、回填共享配置)→ `impl.NewImpl()`(Memory/Redis/DB/Etcd)→ `ecmallserver.New`(gRPC server + reflection + gin engine)→ `service.Expose`(session 中间件 + 注册表逐个注册,失败即 panic)→ `srv.Start` → `SIGINT/SIGTERM` → `srv.Stop`。 + +分流实现与 `pkgs/all` 完全一致(两个文件逐字节相同),因此下文只标注 ecmall 侧行号: + +```mermaid +flowchart TD + C["客户端请求"] --> A["httpMiddleware:canonicalHTTPPath + isAnonymous
authorization.go:55-66"] + A -->|"匿名 或 JWT 有效"| G["grpc-gateway mux ServeHTTP(写入 bufferedResponse)
server.go:63-65"] + A -->|"鉴权失败"| E1["HTTP 200 + {code,message,details,timeseq}
authorization.go:122-126"] + G -->|"status != 404"| F1["flush 缓冲响应
server.go:66-68"] + G -->|"status == 404(路由未命中 或 业务 NotFound)"| H["gin ServeHTTP 兜底
server.go:70"] + H --> R1["POST /rpc/:module/:service/:method
server.go:61"] + R1 --> D["resolveMethod:gRPC reflection 取 descriptor(带缓存)
dynamic.go:115-176"] + D --> D1["streaming → Unimplemented
dynamic.go:79-82"] + D --> D2["protojson 解 JSON(≤4 MiB)→ 内网 gRPC Invoke(insecure)
dynamic.go:84-112"] + H --> R2["/rest/fts/... /rest/logs/... /rest/mgt/...(模块自注册)
service/{fts,logs,mgt}.go"] + H -->|"无路由"| N["gin 默认 404(纯文本)"] + D2 --> E2["内网 gRPC 再经 unary 拦截器校验 JWT
authorization.go:42-53"] +``` + +| 入口 | 形态 | ecmall 覆盖范围 | 鉴权节点 | +| --- | --- | --- | --- | +| 原生 gRPC | `/{pkg}.{Svc}/{M}` | 11 个 gRPC 模块(fts/logs/mgt 无 gRPC 面) | gRPC unary 拦截器(`server.go:32`) | +| grpc-gateway | `POST /{pkg}.{Svc}/{M}` | 同 11 个模块 | HTTP 中间件 + 内网 unary 拦截器 | +| 动态 RPC | `POST /rpc/{pkg}/{Svc}/{M}` | 同 11 个模块,仅 unary | HTTP 中间件(按网关形态匹配白名单)+ 内网 unary 拦截器 | +| 原生 REST | `/rest/{fts,logs,mgt}/...` | 仅这 3 个模块 | HTTP 中间件 + 模块内层 `JwtAuth`(`/rest/fts/uploader`、`/rest/mgt/{user,app,role,pmn,dpt}/*`) | + +## 4. 配置与安全 + +| 项 | ecmall 现状 | 证据 | +| --- | --- | --- | +| 监听地址 | gRPC `12300` / HTTP `12301`,`BindIP 0.0.0.0`;两地址相同则 panic | `etc/default_dev.yaml:3-8`;`config.go:66-68` | +| 端口缺省 | `Port` 为空时随机 1024-65535(SDK 行为),两地址必然不同 | SDK `conf/new.go:87-97`;`config.go:91-95` | +| JWT | HS256,裸 JWT 放 `Authorization` 头(无 `Bearer`),校验 `exp`/`iat`/`now-iat ≤ Expire` | `authorization.go:68-94`;`authorization_test.go:36-41` | +| JWT 密钥 | `Authorization.Key` 长度 ∈ {16,24,32},写入全局 `env.JwtSecretKey` 供模块签发共用 | `config.go:72-80` | +| 默认密钥 | `CHANGE_ME_32_BYTE_JWT_SECRET_KEY`(32 字节,直接通过校验) | `etc/default_dev.yaml:22`;`config.go:72-75` | +| session 密钥 | `config.Spec.SecretKey`(`conf.Base`)拼 `"-session"`,配置值 `CHANGE_ME`,**无校验** | `internal/service/service.go:35`;`etc/default_dev.yaml:17`;`config.go:69-78` 未校验 | +| 匿名白名单 | 19 条(11 条网关形态 + 8 条 REST 路径),`/grpc.reflection.`、`/grpc.health.` 前缀硬编码放行 | `etc/default_dev.yaml:24-43`;`authorization.go:96-103` | +| gRPC reflection | 无条件注册、无开关、匿名可达 | `server.go:33`;`authorization.go:98-100` | +| TLS | 无:`net.Listen("tcp", ...)` + `insecure.NewCredentials()`;`grep -i "tls\|certificate\|x509" pkgs` 无命中 | `server.go:45,49`;`dynamic.go:45` | +| HTTP server 参数 | `ReadHeaderTimeout=10s`、`IdleTimeout=120s`、`MaxHeaderBytes=1MiB`;无 `ReadTimeout`/`WriteTimeout` | `server.go:72-78` | +| gRPC server 参数 | 无 MaxRecvMsgSize、无 keepalive、**无 StreamInterceptor** | `server.go:32` | +| 客户端 IP | `gin.New()` 未 `SetTrustedProxies`(gin v1.12.0 默认信任全网段) | `server.go:34`;gin `gin.go:225,214` | +| 限流 | 无 | `grep -i "ratelimit\|limiter\|throttle" pkgs` 无命中 | +| 运行模式 | gin 未按 `BSM_RuntimeMode` 设置 | `server.go:34` | +| CORS | 未启用(独立 fts/mgt 启用,SDK 实现为 `AllowAllOrigins: true`) | `server.go:34`;`module/base/fts/cmd/main/main.go:26`、`module/base/mgt/cmd/main/main.go:39` | +| 建表/播种 | 聚合不执行:`applyDependencies` 只覆盖包变量(`module/base/mgt/service/dependencies.go:19-32`),不含 `models.New`/`AutoMigrate`/`InitRootUserData`(唯一调用链是 `module/base/mgt/internal/impl/with.go:20` ← 模块自己的 `impl.NewImpl()`) | `grep -rn "NewImpl()\|AutoMigrate\|models.New(" module/*/*/service/*.go` 无命中 | + +匿名白名单逐条核对:11 条 gRPC 条目全部对应真实方法(`module/base/passport/proto/{login,register,forget,verify}.proto`、`module/ec/mall/proto/staff.proto:9`、`module/ec/market/proto/agency.proto:9`),8 条 REST 条目全部对应已注册路由(`module/base/fts/internal/routers/register.go:22-23`、`module/base/logs/internal/routers/register.go:24`、`module/base/mgt/internal/routers/register.go:31-36`)——**无死条目,问题在"该放行的没放行"**(见 5.1)。 + +`AGENT.md` 与代码的一致性:`AGENT.md:193-216` 的《匿名白名单现状》表与 `etc/default_dev.yaml:24-43` 逐条相符,并主动列出"模块声明匿名但聚合不放行"的 4 组接口(ads.Fetch/ByPos、sender 三个方法、feedback 四个方法、logs 三个 REST)。**文档是诚实的,但没有任何代码或测试强制两者一致**(见 5.3)。 + +## 5. 审计发现 + +### 5.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `etc/default_dev.yaml:22` + `internal/config/config.go:72-75` | **JWT 签名密钥默认值是可直接使用的公开字面量**。`CHANGE_ME_32_BYTE_JWT_SECRET_KEY` 恰好 32 字节 → 通过长度校验 → 写入 `env.NewEnv().JwtSecretKey`(`config.go:79`)。`authorization.go:74-79` 只校验 HS256 签名与 `iat/exp`,因此知道该字面量即可自行签发任意身份的 token;模块侧签发也用同一密钥(`module/base/mgt/internal/logic/pub/login.go:90`、`module/base/passport/internal/logic/common/token.go:10`),无法单侧规避。 | +| **高** | `etc/default_dev.yaml:17` + `internal/service/service.go:35` | **session 密钥来自未校验的 `SecretKey`**。`cookie.NewStore([]byte(config.Spec.SecretKey+"-session"))`,配置值 `CHANGE_ME`;`config.New` 只校验 `Authorization.Key`(`config.go:69-78`),对 `SecretKey` 从不校验。默认配置下 `mysession` cookie 的 HMAC 密钥可猜(当前唯一使用点是 mgt 的 SessionDemo 演示路由)。 | +| **高** | `pkgs/ecmall/etc/`(仅 1 个文件) | **不存在生产配置**。`BSM_RuntimeMode=prod` 时读 `default_prod.yaml`,缺失即回退 `workspace_default_prod.yaml`(SDK `conf/new.go:35-43`),两者都不存在 → `log.Fatalf`(`new.go:49-52`)。即聚合入口只能以 dev 模式启动;`scripts/build-all-linux.sh` 又只扫描 `module/`(`:7,30`),聚合既无生产配置也不在构建范围内。 | +| **中** | `internal/server/server.go:33` + `authorization.go:96-100` | **reflection 无开关且匿名可达**:匿名可枚举 11 个模块的全部 gRPC 服务/方法与完整消息结构(动态 RPC 自身就依赖它,`dynamic.go:124-143`)。 | +| **中** | `internal/server/authorization.go:98` | `/grpc.health.` 硬编码匿名,但没有任何模块注册 health 服务(`grep -rn "health" module` 只命中注释与字符串)→ 当前是死名单,将来补 health 服务会先天匿名。 | +| **中** | `internal/server/server.go:45,49` + `dynamic.go:45` | **全链路明文**,无 TLS 配置能力;JWT 与业务数据在 `0.0.0.0:12300/12301` 明文传输(是否由前置代理终止 TLS 属部署前提,进程内无保障)。 | +| **中** | `internal/server/server.go:34`、`dynamic.go:180-188` | **客户端 IP 可伪造**:未 `SetTrustedProxies`,gin v1.12.0 默认 `trustedProxies={"0.0.0.0/0","::/0"}` 且 `ForwardedByClientIP=true`(gin `gin.go:225,214`)→ `c.ClientIP()` 取客户端自报 XFF;动态 RPC 把请求中所有 `x-` 前缀头原样透传内网(`dynamic.go:182-188`);grpc-gateway 也会把请求 XFF 拼入 metadata(gateway `runtime/context.go:185-193`)。落点示例:`module/base/mgt/internal/logic/pub/login.go:90` 把 client IP 写进 token。 | +| **中** | `internal/server/authorization.go:55-66`、`server.go:35` | **匿名登录入口无任何限流**:`passport.Login/Pwd|Code|Quick`、`passport.Register/*`、`passport.Forget/*`、`market.Agency/Login`、`mall.Staff/Login`、`/rest/mgt/login` 可无限次调用;入口只有 `gin.Logger()` 与 JWT 校验。 | +| **中** | `etc/default_dev.yaml:24-43` 对比模块实现 | **匿名口径与模块声明不一致(ecmall 侧比 all 多一项)**:① `logs` 的 `POST /rest/logs/create|fetch|total` 在模块里注册于"匿名组"(`module/base/logs/internal/routers/register.go:18-29`,模块自身 `JwtAuth` 被注释掉)→ 聚合下要求 JWT。② `ads.Fetch/ByPos`(`module/base/ads/etc/ads_dev.yaml:15-16` 声明匿名)、`sender.Sms/Send`、`sender.Sms/Verify`、`sender.Mail/Send`、`feedback.Method/{List,Get,Delete,Remark}` 在模块侧匿名或不做归属校验,聚合白名单均不含 → 上线 401。③ `cloud` 不在 ecmall 注册表内,即使 `BSM_SERVICES=cloud` 也会被静默忽略(`config.Enabled("cloud")` 返回 true,但注册表里没有对应 `expose` 项)。 | +| **低** | `internal/server/authorization.go:96-120` | 白名单是精确字符串匹配,gRPC 形态与 REST 真实路径混列同一列表;`/rpc/...` 仅在恰好 4 段时改写为网关形态(`canonicalHTTPPath`)。新增方法/路由忘记登记只会静默变成"需要 JWT",无一致性校验或告警。 | +| **低** | `internal/server/dynamic.go:73,115-176` | 动态 RPC 描述符缓存键由请求路径拼接(无上限、无 TTL),未命中时每次向内网反射服务发一次 RPC;任意匿名调用者可用随机符号名放大内网调用,`protojson` 解析错误原样回显(`dynamic.go:95-98`)。 | + +### 5.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/server/server.go:63-71` | **gateway 404 与业务 `NotFound` 不可区分,真实响应被丢弃**。`recorder.status != http.StatusNotFound` 被用作"网关未命中路由"的判据,但 grpc-gateway 对业务 `NotFound` 同样渲染 HTTP 404(gateway `runtime/errors.go:48-49`),与路由未命中的 404(`runtime/mux.go:464,552`)状态码相同。任何 gateway 方法返回 `codes.NotFound`("记录不存在"等高频分支,如 `mall.Product`/`order.Summary`/`cms.Post` 的查询)时,已生成的 `{code,message,...}` 响应被丢弃,客户端收到 gin 的纯文本 `404 page not found`。 | +| **中** | `internal/server/server.go:82-89` | **启动期错误只处理一半**:`errCh` 只读一个值即返回,先失败的一方不会关闭另一侧的 listener/gRPC server,也没有 `Shutdown` 兜底。 | +| **中** | `cmd/main/main.go:48-53` + `internal/server/server.go:92-110` | **优雅关闭超时被复用、错误被丢弃**:`GracefulStop` 超时后用同一个已过期的 ctx 执行 `s.http.Shutdown(ctx)`,立即返回 deadline exceeded,在途 REST 请求被中断;外层 `_ = srv.Stop(ctx)` 丢弃返回错误。 | +| **中** | `internal/server/server.go:32` | **流式 RPC 不受鉴权覆盖**:只有 `grpc.UnaryInterceptor`,无 `StreamInterceptor`。当前 66 个 proto、35 个服务无 `stream` 声明(`grep -n "stream " --include=*.proto module` 无命中),故无实际暴露;动态 RPC 侧已显式拒绝流式(`dynamic.go:79-82`),风险在原生 gRPC 面(`12300`)。 | +| **中** | `internal/server/server.go:112-133` | **全量缓冲响应**:所有 gateway 响应先写入 `bytes.Buffer` 再整体 flush(`server.go:64,125-132`);`cms.Post.List`、`mall.Product`、`order.Summary.List` 这类列表响应在内存中同时存在两份;`flush` 用 `Add` 复制响应头(`server.go:128`);`WriteHeader` 无重复调用保护(`server.go:123`)。 | +| **中** | `etc/default_dev.yaml:79` + `module/base/fts/internal/logic/provider.go:71` | **配置的下载地址没有对应路由**:`Fts.Local.Site: http://127.0.0.1:12301/files`,本地存储返回 `Site + "/" + bucket + "/" + subdirpath + "/" + fileName`;而 fts 只注册 `/rest/fts/ping|config|uploader`(`module/base/fts/internal/routers/register.go:11-26`),全仓库无 `Static`/`StaticFS`/`/files` 路由(`grep -rn "Static\|/files" --include=*.go module/base/fts` 无命中)→ 上传返回的 URL 在 `12301` 上必然 404。 | +| **中** | `internal/config/config.go:92-95` | **端口漏配会随机绑定**:`conf.CheckPort` 在 `Port` 为空时随机生成 1024-65535(SDK `conf/new.go:90-97`),两个 listener 各自随机且"必须不同"的校验必然通过 → 在随机端口"成功"启动;`ecmall` 的随机端口还可能撞上 `pkgs/all` 的 `12000/12001` 或模块 dev 端口。 | +| **低** | `internal/config/config_test.go:15,43-52` | **注册表清单存在 3 处拷贝**:`internal/service/service.go:14-32`(注册表本体)、`internal/config/config_test.go:15`(`ecmallServices` 常量)、`etc/default_dev.yaml:50-64`(`Services`)。测试只能保证后两者一致;**新增服务**若漏改常量与 yaml,两项测试都不会失败(`service_test.go:31` 只断言 11 个固定前缀存在,不断言"注册表 == yaml")。`AGENT.md:247` 已把"改 3 处"写成约定,属人工约束。 | +| **低** | `internal/config/config.go:115-130` + `internal/service/service.go:34-45` | **服务名写错会静默启动空宿主**:`Enabled` 不校验名字是否存在,`Expose` 对 0 个注册不报错;`BSM_SERVICES=mail`(mall 的笔误)会得到一个只在 `12300/12301` 上回答 404 + reflection 的进程。 | +| **低** | `cmd/main/main.go:23-29` | workspace 名会被 `utils.MustString` 转成小写(SDK `utils/ext.go:40-41`),因此 `--workspace=Tenant01` 会去找 `tenant01_dev.yaml`;README 描述一致,但配置文件命名必须全小写,否则报错信息(`workspace must match ...`)与实际文件名不匹配,易误判。 | + +### 5.3 未完成/不一致 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `pkgs/ecmall/etc/` + `scripts/build-all-linux.sh:7,30` | **无 prod/test 配置,且不在构建脚本范围内**:`MODULE_ROOT="${WORKSPACE_ROOT}/module"`,`pkgs/` 完全不被扫描,`pkgs/ecmall/etc` 只有 `default_dev.yaml`。上线路径目前只有"开发模式 + 手工 `go build`"。 | +| **中** | `etc/default_dev.yaml:86-88` + `module/base/mgt/internal/models/impl.go:61-74` | **聚合下 `Mgt.InitRootUser: true` 不生效**。建表与 root 播种唯一调用链是 `module/base/mgt/internal/impl/with.go:20` ← 模块自己的 `impl.NewImpl()`;聚合只调 `applyDependencies`(`module/base/mgt/service/dependencies.go:19-32`),`grep -rn "NewImpl()\|AutoMigrate\|models.New(" module/*/*/service/*.go` 无命中。yaml 已用注释说明(`default_dev.yaml:68-70` 指向 `AGENT.md §3.5`),但配置项本身仍在,容易被误读为"已开启初始化"。 | +| **中** | `module/base/passport/internal/logic/verify/jumio_callback.go:13-34` | **被匿名放开的回调是空壳**:`/passport.Verify/JumioCallback` 在白名单(`etc/default_dev.yaml:33`),实现只 `printer.Info` 后返回成功,代码内自带 TODO「In production, implement proper callback handling logic」,不校验签名。当前无实际危害,补实现前必须先加签名校验。 | +| **中** | `internal/config/config.go:84` + SDK `with/databases.go:13-15` | **`Databases` 未纳入配置校验**:`conf.NotNil(Spec.Service, Spec.Cache)` 只两项;`Databases` 缺失时 `impl.NewImpl()`(`impl.go:38`)内部 `panic("No Database Source Found !")`。`impl.NewImpl()` 早于 `server.New`/`Expose`(`cmd/main/main.go:31-44`),此时 Redis/Etcd 已建连且无失败回滚。 | +| **低** | `internal/server/*` vs `pkgs/all/internal/server/*` | **两份拷贝需手工同步**:`server.go`/`authorization.go`/`dynamic.go`/`response.go` 逐字节相同(唯一差异是 `server.go:79-80` 的日志字面量),没有测试或 CI 阻止单侧漂移;改一处漏一处会导致"同一接口在两个入口鉴权/分流行为不同"。 | +| **低** | 无 | **无进程级健康检查端点**:独立入口均注册 `app.HEAD("/", infra.Health)`(`module/base/fts/cmd/main/main.go:36`、`module/base/logs/cmd/main/main.go:39`、`module/base/mgt/cmd/main/main.go:41`),ecmall 只有三个模块自带的 `/rest/*/ping`,`HEAD /` 落到 gin 404。 | +| **低** | `AGENT.md:110` | 文档引用行号漂移:`AGENT.md` 说 `SecretKey` 被 `internal/service/service.go:29` 使用,实际在 `:35`。文档正文正确、行号过期,属长期维护噪音(同类引用见 §4.2/§4.4 等处的行号)。 | + +### 5.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **中** | `internal/server/server.go:72-78` | 无 `WriteTimeout`/`ReadTimeout`,只有 `ReadHeaderTimeout`/`IdleTimeout`/`MaxHeaderBytes`;同时 gin 侧无请求体大小限制(`/rest/fts/uploader` 的上限只在 fts 逻辑层),与动态 RPC 的 4 MiB 上限(`dynamic.go:30,84-92`)不对称。 | +| **低** | `internal/server/server.go:34` | 未按 `BSM_RuntimeMode` 设置 gin 模式(独立入口会调 `middleware.Mode`,SDK `middleware/mode.go:9-16`)→ 生产环境仍为 gin Debug 模式(除非外部设置 `GIN_MODE`)。 | +| **低** | `internal/server/server.go:34` | 未启用 CORS,而独立 fts/mgt 启用(SDK 实现 `AllowAllOrigins: true`)→ 同一接口"独立可被浏览器调用、聚合不可用"的行为差。 | +| **低** | `internal/server/server.go:79-80` | `fmt.Printf` 直接打印监听地址,绕过 SDK `printer`;`gin.Logger()` 无条件记录全部请求(`server.go:35`)。 | +| **低** | `internal/server/server.go:112-133` | `bufferedResponse` 只实现 `Header/WriteHeader/Write`,缺 `http.Flusher`/`Hijacker`/`CloseNotifier`;当前 unary JSON 可用,任何流式/大文件网关响应都会静默退化。 | +| **低** | `internal/server/dynamic.go:34-36` | 描述符缓存无条件增长(`map` + `RWMutex`,无上限、无 TTL),键来自外部输入的路径拼接。 | +| **低** | `internal/config/config.go:115-130` | `BSM_SERVICES` 一旦非空即**完全覆盖** `Services`,且无去重、无存在性校验;`Enabled` 同时对 `"all"` 特判,语义("全开"与"名单"混用)只能从 README/AGENT 得知。 | +| **低** | 测试覆盖 | 有护栏但不深:`service_test.go:15-53` 断言 11 个前缀与 6 条 REST 路由存在,`config_test.go` 断言 yaml 与常量一致;但**没有**对 `httpMiddleware` 白名单与注册表一致性的断言、没有对 404 分流语义的断言(本次 E4 的 404 覆盖问题因此未被测试发现)。 | + +## 6. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| E1 | 高 | JWT 密钥默认值是可直接使用的公开字面量 | 全系统身份伪造 | +| E2 | 高 | session 密钥取未校验的 `SecretKey`(默认 `CHANGE_ME`) | cookie 完整性与伪造 | +| E3 | 高 | 无 prod 配置,且构建脚本不覆盖 `pkgs/*` | 上线路径、部署可靠性 | +| E4 | 高 | gateway 404 与业务 `NotFound` 不可区分,真实响应被覆盖 | 所有 gateway 方法的错误语义 | +| E5 | 中 | reflection 无开关且匿名可达(该入口依赖它) | 内部 API 全貌泄露 | +| E6 | 中 | 无 TLS | 传输机密性 | +| E7 | 中 | 客户端 IP 可经 XFF 伪造并透传内网 metadata | 审计与风控数据可信性 | +| E8 | 中 | 匿名登录入口无任何限流 | 账号安全 | +| E9 | 中 | 匿名口径与模块声明不一致(logs 三条 REST、ads/sender/feedback 多组接口) | 独立/聚合行为不一致、上线 401 | +| E10 | 中 | 流式 RPC 不受鉴权拦截器覆盖(当前无流式方法) | 未来扩接口时的越权风险 | +| E11 | 中 | `Fts.Local.Site` 指向的 `/files` 无路由 | 文件下载不可用 | +| E12 | 中 | 优雅关闭超时后 HTTP 立即中断且错误被丢弃 | 关闭期请求可靠性 | +| E13 | 中 | gateway 响应全量缓冲 | 大响应下的内存放大 | +| E14 | 中 | 聚合不建表不播种,`Mgt.InitRootUser: true` 不生效 | 首次部署缺表/缺管理员 | +| E15 | 中 | `Databases` 未校验 + 资源初始化无失败回滚 | 启动期崩溃与连接泄漏 | +| E16 | 中 | Jumio 回调空壳(无签名校验)却已匿名放开 | 补实现后的数据伪造风险 | +| E17 | 低 | 注册表 3 处拷贝、服务名写错静默空宿主、两份 server 拷贝需手工同步 | 变更回归与一致性 | +| E18 | 低 | 端口漏配随机绑定、无 WriteTimeout、gin 模式/CORS 与独立入口不一致、无健康端点、文档行号漂移 | 运维与可维护性 | + +## 7. 修复建议(务实项) + +1. **密钥不能是占位值**(E1、E2):在 `internal/config/config.go` 现有 `Authorization.Key` 校验之后,加"`Authorization.Key` 不得等于样例字面量"与"`SecretKey` 非空且不得等于 `CHANGE_ME`"两条判断(各 3 行);顺带把 `Databases` 纳入 `conf.NotNil`(E15)。不引入配置中心或加密方案。 +2. **补生产配置与构建覆盖**(E3):新增 `pkgs/ecmall/etc/default_prod.yaml`(替换全部 `CHANGE_ME`、显式 `BindIP`),并让 `scripts/build-all-linux.sh` 的模块收集范围覆盖 `pkgs/`(E3 与工作区脚本报告同源)。 +3. **修 404 分流**(E4):自定义 gateway 的 routing error handler(或让 `bufferedResponse` 记录"是否由 routing error handler 写出"),只在"路由未命中"时回退 gin;业务 `NotFound` 必须原样 flush。这是 `server.go:63-71` 一处判断的修正,不需要改接口契约。 +4. **reflection 开关化**(E5):给 `Server.New` 增加一个布尔形参/配置项(默认关闭),仅在开启时 `reflection.Register`;同时从 `isAnonymous` 删除 `/grpc.health.` 前缀。注意动态 RPC 依赖反射——若要保留 `/rpc/...`,应改为只允许来自 loopback 的调用(`remoteAddr` 判定即可)。 +5. **客户端 IP 与头部清理**(E7):`server.New` 内 `engine.SetTrustedProxies(<真实代理网段>)`(无代理传 `nil`);`outgoingMetadata` 不再透传客户端自带的 `x-forwarded-for`/`x-real-ip`,改为服务端重算。 +6. **匿名白名单口径对齐**(E9):逐条决策后同步两处——logs 的 `create/fetch/total`、ads 的 `Fetch/ByPos`、sender 的三个方法、feedback 的四个方法,要么加入 `Authorization.Anonymous`,要么在模块侧去掉"匿名"声明。改配置即可,零代码。 +7. **文件下载路由**(E11):在 fts 路由注册补静态目录(本地存储场景),或把 `Fts.Local.Site` 改成真实存在的前缀。 +8. **启动/关闭收尾**(E12、E15):`Start` 中任一侧 `Serve` 返回错误时关闭两个 listener 并 `Shutdown`;`Stop` 中给 `http.Shutdown` 单独的短超时 ctx 并输出错误;`Port` 为空改为报错退出,不要随机绑定(E18)。 +9. **注册表与测试护栏**(E17):在 `config_test.go` 增加"yaml 的 `Services` 与注册表逐项一致"的断言(现只能比对常量);在 `service_test.go` 补一条"白名单中的 REST 路径确实已注册"的断言,把 `AGENT.md §4.5` 的记录变成可执行检查。 +10. **两份拷贝的同步**(E17):在 `pkgs/all` 补与 `pkgs/ecmall/internal/service/service_test.go` 等价的注册表测试;两份 `internal/server` 的漂移用"同一组测试断言 + 文件头注明需同步"来兜,不新建公共包。 +11. **首次部署前置**(E14):在 `README.md`/`AGENT.md` 之外,把"聚合不建表、不播种 root"写成启动期显式检查(例如启动时探测 mgt 关键表是否存在,缺失则拒绝启动并打印指引),避免运行到首个请求才暴露。 +12. **入口限流与可观测**(E8、E18):在 `httpMiddleware` 内对匿名路径加一个基于内存的简单计数限流;`http.Server` 补 `WriteTimeout`;按 `BSM_RuntimeMode` 设置 gin 模式;注册 `HEAD /` 健康端点。 + +> 本报告只列与现有实现直接相关的修复项,不引入新的分层或抽象封装。审计中"合并两个聚合入口""为聚合引入配置中心/服务注册中心""把动态 RPC 改造成网关插件"一类改造不在此列。 diff --git a/docs/relation.md b/docs/relation.md new file mode 100644 index 0000000..206d508 --- /dev/null +++ b/docs/relation.md @@ -0,0 +1,305 @@ +# relation(好友、关注与匹配关系)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/social/relation`(module 路径 `bsm/full/module/social/relation`) | +| 服务域 | 社交与关系 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 44 个文件 / 1823 行;生成代码 `pb/` 10 个文件 / 7751 行(`pb/const.pb.go` 单文件 3368 行);`proto/` 4 个文件 / 520 行;`etc/` 3 个 yaml(52/58/52 行)+ 1 个 supervisor conf;`*_test.go` 0 个;无 `service/`、无 `test/` 目录 | +| 入口 | 仅独立入口 `cmd/main/main.go`(gRPC + grpc-gateway 单进程,dev `Port: 12248` / prod `12426`,`Gateway.Port: 12425`);**未被** `pkgs/all`、`pkgs/ecmall` 接入;`cmd/cli/main.go` 只打印 `config.Spec.Databases` | +| 对外协议 | gRPC 3 个 service / 25 个 RPC;gateway 路径为 `POST /relation.{Service}/{Method}`,模块内**从未注册任何 gateway handler** | +| 结论摘要 | 25 个接口全部只验 JWT,**没有一处做归属校验**:删好友/删关注/改备注/置顶/审批加好友都可指向他人记录;7 个好友与标签接口按不存在的列 `relation_id` 查询(模型里是 `passport_id`),其中 `Friend.TagFetch` 还必然空指针 panic;`Follow.Fetch`/`Match.Fetch` 分别因 `Params` 静默空返回与无效 SQL 不可用;`Match` 的两个写接口改的其实是关注表;缓存分支可返回 `(nil, nil)` 或 panic;`follow.Do` 是遗留占位死代码;网关未接线、无聚合入口,`etc` 中 `KycConf` 与匿名清单均为死配置。 | + +## 1. 服务定位与职责 + +维护三类社交关系与其辅助数据: + +- **好友**:`relation_friend`(好友条目、备注、置顶、会话 ID)、`relation_friend_apply`(申请)、`relation_friend_apply_message`(申请留言)、`relation_friend_tag` + `relation_friend_in_tag`(好友标签分组); +- **关注**:`relation_follow`(from/to 关注关系); +- **匹配**:`relation_match`(推荐关系)。 + +对外提供好友搜索/列表/资料卡/备注/置顶/删除、好友申请的提交/留言/列表/详情/通过/拒绝、好友标签的增改查,以及关注的执行/撤销/状态/列表和匹配的列表/详情/通过/忽略。 + +它是关系链的**读写服务**;用户资料不落在本模块(依赖外部表 `relation_extend`,仓库内无模型也无建表 SQL),消息推送、动态分发、推荐计算均不在本模块内(代码里只有 `//EventMQ:向mesh MQ中心发送报文` 这类注释,无实现)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("relation")` → `impl.NewImpl()` → `server.New(config.Spec.Addr)` → `service.New` → `srv.Start()`;`srv.Use(models.InitData)` 被注释(`:37`) | +| `cmd/cli/main.go` | 调试入口,只做 `config.New("relation")` + `fmt.Println(config.Spec.Databases)` | +| `internal/config/config.go` | `SrvConfig`(Base、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChatConf、Token、Kyc)与 `conf.NotNil(Service, Cache)` 校验 | +| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 | +| `internal/server/new.go` | 创建 `grpc.Server`,注册 `Follow`、`Friend`、`Match` 三个 service(`protoc-gen-slc` 生成);**未创建 `ServeMux`** | +| `internal/server/{follow,friend,match}_server.go` | 25 个 RPC 的 1:1 转发(生成代码) | +| `internal/logic/common/const.go` | 资料卡查询、会话 ID、好友/申请集合装配、好友申请缓存读写(202 行,本模块最重的文件) | +| `internal/logic/follow/{doing,undo,state,fetch,do}.go` | 关注的执行/撤销/状态/列表 + 遗留占位 `Do` | +| `internal/logic/friend/*.go`(17 个) | 好友基础操作、申请管理、标签管理 | +| `internal/logic/match/{fetch,get,do_join,do_ignore}.go` | 匹配列表/详情/通过/忽略 | +| `internal/models/*.go`(9 个) | 7 张表模型 + `query.go`、`base.go`(后两者各 1 行,仅 `package models`) | +| `proto/{follow,friend,match,const}.proto` | 契约;公共消息放在 `proto/const.proto`(`package blocks`,与 service 的 `package relation` 不同) | +| `etc/{relation_dev,relation_prod,relation_test}.yaml` + `supervisor.bsm-social-relation.conf` | 配置与进程托管 | + +依赖注入:本模块没有 `service/` 目录,也没有 `Expose/Dependencies` 注入点,`impl.NewImpl()` 是唯一初始化路径(`internal/impl/impl.go:19-25`)。全仓库对 `module/social/relation` 的引用只有 `go.work:21` 与 `scripts/api-docgen`。 + +## 3. 接口清单 + +HTTP 路径由生成代码固定为 `/relation.{服务}/{方法}`(`pb/follow.pb.gw.go:344-347`、`pb/friend.pb.gw.go:1176-1192`、`pb/match.pb.gw.go:344-347`),全部为 `POST`。**这些 handler 在模块内从未被注册到任何 `ServeMux`**(见 6.3/6.4)。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/relation.Follow/Doing` | 执行关注 | JWT | `logic/follow/doing.go:18` | +| POST | `/relation.Follow/Undo` | 撤销关注 | JWT(**丢弃 claims、无归属校验**) | `logic/follow/undo.go:17` | +| POST | `/relation.Follow/State` | 关注状态 | JWT | `logic/follow/state.go:18` | +| POST | `/relation.Follow/Fetch` | 关注列表 | JWT | `logic/follow/fetch.go:17` | +| POST | `/relation.Friend/Search` | 搜索好友 | JWT(丢弃 claims) | **空实现** `logic/friend/search.go:12` | +| POST | `/relation.Friend/Fetch` | 好友列表 | JWT | `logic/friend/fetch.go:14`(缓存分支可返回 nil,见 6.2) | +| POST | `/relation.Friend/Get` | 用户资料卡 | JWT(无关系校验) | `logic/friend/get.go:14` | +| POST | `/relation.Friend/ModifyNickname` | 修改好友备注 | JWT(归属条件列名错误) | `logic/friend/modify_nickname.go:17` | +| POST | `/relation.Friend/DoPopular` | 置顶好友 | JWT(同上) | `logic/friend/do_popular.go:17` | +| POST | `/relation.Friend/UndoPopular` | 取消置顶 | JWT(同上) | `logic/friend/undo_popular.go:17` | +| POST | `/relation.Friend/Delete` | 删除好友 | JWT(同上) | `logic/friend/delete.go:17` | +| POST | `/relation.Friend/ApplyFetch` | 好友申请列表 | JWT | `logic/friend/apply_fetch.go:14`(缓存分支可返回 nil,见 6.2) | +| POST | `/relation.Friend/ApplyGet` | 好友申请详情 | JWT(**丢弃 claims、无归属校验**) | `logic/friend/apply_get.go:16` | +| POST | `/relation.Friend/ApplyDo` | 发起好友申请 | JWT | `logic/friend/apply_do.go:18` | +| POST | `/relation.Friend/ApplyDoMessage` | 追加申请留言 | JWT(**不校验申请归属**) | `logic/friend/apply_do_message.go:17` | +| POST | `/relation.Friend/ApplyDoPass` | 通过申请并加好友 | JWT(**不校验申请与好友参数一致**) | `logic/friend/apply_do_pass.go:19` | +| POST | `/relation.Friend/ApplyDoReject` | 拒绝申请 | JWT(无影响行数校验) | `logic/friend/apply_do_reject.go:17` | +| POST | `/relation.Friend/TagFetch` | 好友标签列表 | JWT | `logic/friend/tag_fetch.go:15`(**必然 panic**,见 6.2) | +| POST | `/relation.Friend/TagMemberFetch` | 标签成员 | JWT | `logic/friend/tag_member_fetch.go:16`(表/列/参数三处错误,见 6.2) | +| POST | `/relation.Friend/TagDoCreate` | 创建标签 | JWT | `logic/friend/tag_do_create.go:18` | +| POST | `/relation.Friend/TagDoUpdate` | 增删标签成员 | JWT(**无归属限定**) | `logic/friend/tag_do_update.go:18` | +| POST | `/relation.Match/Fetch` | 匹配列表 | JWT | `logic/match/fetch.go:16`(SQL 无效,见 6.2) | +| POST | `/relation.Match/Get` | 匹配详情 | JWT(丢弃 claims) | `logic/match/get.go:14` | +| POST | `/relation.Match/DoJoin` | 匹配通过加好友 | JWT | `logic/match/do_join.go:18`(写的是关注表,见 6.2) | +| POST | `/relation.Match/DoIgnore` | 忽略匹配 | JWT(**丢弃 claims、无归属校验**) | `logic/match/do_ignore.go:17`(删的是关注表,见 6.2) | + +> 鉴权方式:25 个 logic 入口全部调用 `service.ParseMetaCtx(ctx, nil)`(`bsm-sdk/core/service/meta.go:19-49`),要求有效 JWT;`opts` 均为 `nil`,即**不校验角色、不校验私有 IP**。其中 `Follow.Undo`、`Friend.Search`、`Friend.ApplyGet`、`Match.Get`、`Match.DoIgnore` 以及死代码 `follow.Do` 用 `_, err = ...` 丢弃 claims(`follow/undo.go:18`、`friend/search.go:14`、`friend/apply_get.go:17`、`match/get.go:16`、`match/do_ignore.go:19`),无法做归属判断。 + +### 声明但未实现 / 占位接口 + +| 类别 | 接口 | 证据 | +| --- | --- | --- | +| TODO 占位且**无调用点**(死代码) | `follow.Do`(入口实际走 `follow.Doing`) | `internal/logic/follow/do.go:26`(`// TODO: add your logic code & delete this line.`);`internal/server/follow_server.go:19-21` 转发的是 `follow.Doing`;`grep "follow.Do("` 全仓库无调用 | +| 只有参数校验、无查询 | `Friend.Search` | `internal/logic/friend/search.go:18-21`(返回空的 `&pb.PartFriendReply{}`) | +| 模型存在、无写入路径 | `RelationMatch`(匹配记录) | 全仓库对 `RelationMatch` 的唯一使用是 `internal/logic/match/fetch.go:34` 的 `Count`,**没有任何 Create/Update** → 匹配记录只能靠外部写入 | +| 空初始化被注释 | 无 `models.InitData`(`models/query.go` 只有 1 行 `package models`),`cmd/main/main.go:37` 的调用已被注释 | `internal/models/query.go:1`、`internal/models/base.go:1`、`cmd/main/main.go:37` | + +## 4. 数据模型与表 + +模型未注册任何自动迁移:`database.AppendMigrate` / `AutoMigrate` 在 `module/social/**` 内**无匹配**(`grep AppendMigrate module/social` 返回空),7 张表依赖外部建表脚本,仓库内没有对应 SQL。公共字段来自 SDK(`bsm-sdk/core/types/db.go`):`types.Std_IICUDS` = `id`(PK) / `identity varchar(36) uniqueIndex` / `created_at` / `updated_at` / `deleted_at`(index) / `status int8 default 0 index`;`types.Std_Passport` = `passport_id uint index` / `passport_identity varchar(36) index`。 + +### 表 `relation_friend`(`internal/models/relation_friend.go:8-21`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 好友条目自身标识(`apply_do_pass.go:30` 写入 UUID) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | 软删除;`status` 未使用 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | **归属用户**(`apply_do_pass.go:31-32` 写入接受方) | +| `friend_relation_id` | uint | not null | 好友的用户 ID | +| `friend_relation_identity` | varchar(36) | not null | 好友 identity | +| `remark_name` | varchar(255) | 默认 `''` | 好友备注(**接口更新的是 `nickname` 列,见 6.2**) | +| `popular` | int2 | 默认 0 | 是否置顶 | +| `session_id` | varchar | not null | 会话 ID(`common.UniqueSessionID`,`apply_do_pass.go:33`) | + +> 表上**没有** `(passport_id, friend_relation_identity)` 唯一约束,重复加好友只能靠代码判断(当前没有判断);也**没有 `relation_id` 列**,但 6 处查询在用它(见 6.2)。 + +### 表 `relation_follow`(`internal/models/relation_follow.go:8-17`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 关注条目标识(`doing.go:32`、`do_join.go:33`) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | 软删除;`status` 未使用 | +| `from_identity` | varchar(36) | not null | 关注者(取 JWT 的 identity) | +| `to_identity` | varchar(255) | not null | 被关注者(长度与 `from_identity` 的 36 不一致) | + +> 无 `(from_identity, to_identity)` 唯一约束 → 重复关注可无限写入(`doing.go:24-38` 无查重)。 + +### 表 `relation_match`(`internal/models/relation_match.go:6-15`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 匹配记录标识 | +| `created_at`/`updated_at`/`deleted_at` | - | index(deleted_at) | 软删除 | +| `status` | int8 | index,默认 0 | 被 `GetrelationInfoDetailCardByMatch` 当作 `foreign_status` 返回(`common/const.go:165`);**无任何写入点,恒为 0** | +| `relation_identity` | varchar(36) | not null | 关系归属人 | +| `recommend_identity` | varchar(255) | not null | 推荐对象 | + +### 表 `relation_friend_apply`(`internal/models/friend_apply.go:8-20`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 申请标识(`apply_do.go:35`) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | `status`:1 通过(`apply_do_pass.go:43`)/ -1 拒绝(`apply_do_reject.go:26`) | +| `from_id` / `from_identity` | uint / varchar(50) | not null | 申请人(由 JWT 写入) | +| `to_id` / `to_identity` | uint / varchar(50) | not null | 目标(由请求写入,未校验存在) | +| `last_message_id` | uint | not null | 最后一条留言 ID(`apply_do.go:57`、`apply_do_message.go:40` 回写) | + +> 无 `(from_identity, to_identity)` 唯一约束 → 同一对用户可重复申请(当前无查重)。 + +### 表 `relation_friend_apply_message`(`internal/models/friend_apply_message.go:8-18`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 留言标识 | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | - | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 留言人(由 JWT 写入) | +| `apply_id` | uint | not null | 所属申请 ID,**写入时不校验申请是否存在/归属**(见 6.1) | +| `body` | varchar(500) | 默认 `''` | 留言正文 | + +### 表 `relation_friend_tag`(`internal/models/friend_tag.go:8-17`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 标签标识(`tag_do_create.go:30`) | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | - | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 标签归属人 | +| `name` | varchar(255) | not null | 标签名(无去重、无长度上限校验) | + +> 同样**没有 `relation_id` 列**,而 `TagFetch`、`CollectionFriendData` 都在用它过滤(见 6.2)。 + +### 表 `relation_friend_in_tag`(`internal/models/friend_in_tag.go:8-18`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` / `identity` | uint / varchar(36) | PK / uniqueIndex | 关联条目标识 | +| `created_at`/`updated_at`/`deleted_at`/`status` | - | index(deleted_at) | - | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 归属人(`tag_do_create.go:46-47`、`tag_do_update.go:34-35`) | +| `friend_identity` | varchar(36) | not null | 好友 identity | +| `tag_identity` | varchar(36) | not null | 标签 identity | + +> 无 `(tag_identity, friend_identity)` 唯一约束;写接口(`tag_do_create.go:41-52`、`tag_do_update.go:30-37`)不校验标签归属与好友关系。 + +### 外部表 `relation_extend`(仓库内无模型) + +`internal/logic/common/const.go:20,26,166` 三处查询 `relation_extend`,但仓库内既没有该表的模型也没有建表 SQL(表结构无法从代码确证,【信息不足】)。可确证的是**同一张表的三处查询列名互相矛盾**:`Filed`(`common/const.go:15`)取 `nickname/avatar/sex/province/city/area/sign`,而 `GetrelationInfoDetailCardByMatch`(`common/const.go:158`)取 `pe.name`,且 `pb.RelationItem`(`proto/const.proto:307-321`)里**没有 `name` 字段**。 + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /relation.Friend/ApplyDo
建申请 + 建留言 + 回写 last_message_id
无事务 无重复校验"] --> B["relation_friend_apply"] + B --> C["POST /relation.Friend/ApplyFetch
先读 Redis 缓存 再按 version 判断"] + C --> D{"version 非 0 且与缓存相同"} + D -->|"是"| E["reply 保持 nil 返回 nil,nil
网关序列化失败"] + D -->|"否"| F["CollectionFriendApply
按 to_id 取最近 50 条"] + B --> G["POST /relation.Friend/ApplyGet
只按 identity 查 不校验归属"] + G --> H["relation_friend_apply_message 全量留言"] + B --> I["POST /relation.Friend/ApplyDoPass
直接用请求的 friend_relation_id 建好友"] + I --> J["relation_friend 只写接受方一侧"] + I --> K["按 to_identity + apply_identity 置 status=1
不检查影响行数"] + L["POST /relation.Follow/Doing
按 from/to 直接插入 无查重 无自关注校验"] --> M["relation_follow"] + N["POST /relation.Follow/Undo"] --> O["Delete identity=?
丢弃 claims 无归属校验"] + P["POST /relation.Match/DoIgnore"] --> O + Q["POST /relation.Match/DoJoin"] --> M + R["POST /relation.Match/Fetch"] --> S["GetrelationInfoDetailCardByMatch
别名 pe 无效 且 Order by 在 Limit 之后
SQL 必然报错"] + T["POST /relation.Friend/Fetch"] --> U["CollectionFriendData
按 relation_id 过滤 列不存在
错误被忽略 好友列表恒空"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/friend/apply_get.go:17,26-42` | **好友申请详情可越权读取**。入口丢弃 claims(`:17`),`Where("identity=?", in.Identity).First(&apply)` 只按申请 identity 定位,没有 `from_identity/to_identity` 限制 → 任意登录用户拿到一个申请 identity 就能读到该申请双方的资料卡(生日/地区/签名)与全部留言。 | +| **高** | `internal/logic/friend/apply_do_message.go:22-40` | **可向任意申请追加留言**。只校验 `Body != "" && ApplyId != 0`,随后用调用者身份 `Create` 留言并把 `last_message_id` 回写到 `apply_id`(`:40` 按 `id=?` 更新,无归属条件)→ 可污染他人申请会话,也可给不存在的 `apply_id` 造孤儿留言。 | +| **高** | `internal/logic/friend/apply_do_pass.go:24-47` | **可通过申请单方面加任意陌生人为好友**。好友记录直接用请求里的 `friend_relation_id`/`friend_relation_identity` 构建(`:29-32`),**不与 `apply_identity` 对应的申请做比对**;随后的 `UpdateColumn("status", 1)` 虽限定 `to_identity=auth.Identity`,但**不检查 `RowsAffected`**(`:43-47`)→ 传自己的身份 + 任意陌生人的 id/identity 即可在自己名下生成好友记录(无需对方同意),且申请状态是否真的被改无法感知。 | +| **高** | `internal/logic/friend/{delete.go:27,modify_nickname.go:26,do_popular.go:27,undo_popular.go:27}` | **好友操作缺归属判定(且列名本身不存在)**:四处统一写 `Where("relation_id=? and identity=?", auth.ID, in.Identity)`。`relation_friend` 的归属列是 `passport_id`(`relation_friend.go:10` + `bsm-sdk/core/types/db.go:52-55`),表中**没有 `relation_id`**;同时 `in.Identity` 的语义是"好友的 identity",而模型里的 `identity` 是好友条目自身标识 → 归属校验条件整体失效。当前因列不存在必然报错(见 6.2),一旦只把 `relation_id` 改成 `passport_id`,仍会因为 `identity` 语义错位而可以改/删他人好友条目。 | +| **高** | `internal/logic/follow/undo.go:18,26` | **可撤销他人关注关系**。入口丢弃 claims,`Model(&RelationFollow{}).Delete("identity=?", in.Identity)` 只按关注条目 identity 删除,没有 `from_identity = 调用者` 的限制 → 任何登录用户凭 `Follow.Fetch` 返回的 identity 即可删除别人的关注。 | +| **高** | `internal/logic/match/do_ignore.go:19,27` | **可删除他人关注关系(且操作对象错误)**。同样丢弃 claims 后 `Delete("identity=?", in.Identity)`;更严重的是它操作的是 `relation_follow`(`:27`)——"忽略匹配"本应作用于 `relation_match`,因此该接口既无权限、又改错表,`relation_match` 记录永远不会被忽略。 | +| **高** | `internal/logic/friend/tag_do_update.go:39` | **可删除他人标签内的成员**。DEL 分支 `Delete("friend_identity=? and tag_identity=?")` 没有 `passport_id` 限定 → 任何登录用户凭 `tag_identity` 即可移除别人标签里的成员。 | +| 中 | `internal/logic/friend/tag_do_update.go:24-37` | ADD 分支不校验 `tag_identity` 是否属于调用者、不校验 `friend_identity` 是否为好友、无去重 → 可往他人标签里塞任意 identity,也可重复插入同一(标签,成员)。 | +| 中 | `internal/logic/friend/tag_do_create.go:25-52` | 创建标签不校验 `friend_identity` 列表是否为调用者的好友、标签名无去重与长度校验(列宽 255,超长即 DB 报错)→ 标签可被写成任意 identity 集合。 | +| 中 | `internal/logic/follow/doing.go:24-38` | 关注无幂等、不校验目标存在、不禁止关注自己(`from == to`),且 `relation_follow` 无唯一约束 → 同一关系可被无限重复写入,直接污染关注计数(`follow/fetch.go:49` 的 `Count`)。 | +| 中 | `internal/logic/friend/get.go:15-29`、`internal/logic/match/get.go:16-28`、`internal/logic/common/const.go:19-22` | `Friend.Get`/`Match.Get` 只验 JWT 就返回任意 identity 的完整资料卡(昵称/头像/生日/性别/省市/签名),不校验好友或匹配关系 → 任意登录用户可批量拉取用户资料。 | +| 低 | `internal/logic/friend/apply_do.go:23-25` | 只校验 `ToIdentity/Body/ToId` 非空,不校验目标存在、不禁止加自己、不限频率,且同一对用户可无限重复申请(无查重、无唯一约束)。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/friend/tag_fetch.go:22` | **必然空指针 panic**:命名返回值 `reply *pb.FriendTagsReply` 此时为 nil,第 22 行却直接 `Count(&reply.Total)...Scan(&reply.Data)` —— 对 nil 指针取字段地址即 panic(且该 panic 发生在任何 DB 错误之前,net/http / gRPC 都不会返回业务错误码)。同一行还用 `Where("relation_id=?", auth.ID)` 过滤 `relation_friend_tag`,该列不存在(模型是 `passport_id`)。 | +| **高** | `internal/logic/friend/fetch.go:29-50`、`internal/logic/friend/apply_fetch.go:29-50` | **缓存分支两处致命缺陷**。① 当 `in.Version != 0` 且与 `cache.Version` 相等时,第 21-27 行和第 35-40 行两个赋值分支都不会执行,`reply` 保持 **nil** → 第 51 行 `return reply, nil` 返回 `(nil, nil)`,gRPC/gateway 序列化空消息失败(表现为空响应/500)。② 第 47-49 行判断的是 `cacheErr != nil` 却打印 `err.Error()` —— 走到该分支时 `err` 必为 nil(`:42` 已判过),**必然 panic**;可复现路径:`SetCache` 失败但 `GetCache` 命中(如 Redis 只读/写入受限)。 | +| **高** | `internal/logic/common/const.go:120,128,134` + `friend/{delete.go:27,modify_nickname.go:26,do_popular.go:27,undo_popular.go:27,tag_fetch.go:22,tag_member_fetch.go:27}` | **好友/标签查询统一用了不存在的列 `relation_id`**。7 张模型里只有 `passport_id`/`passport_identity`(`relation_friend.go:10`、`friend_tag.go:10`、`friend_in_tag.go:10`),全仓库没有任何 `relation_id` 的定义;按模型声明,这些语句会因列不存在全部失败 → 涉及 **Friend 的 Fetch/Delete/ModifyNickname/DoPopular/UndoPopular/TagFetch/TagMemberFetch 共 7 个接口**。(表结构为外部建表,若线上确有 `relation_id`,则说明模型与真实表不一致,同样需要修正其中一侧。) | +| **高** | `internal/logic/common/const.go:120-146` | **好友列表恒为空**:`:120` 的 `Find` **没有检查错误**(列名问题被静默吞掉),`relationFriend` 始终为空 → `ids` 为空、`:139` 的 `GetrelationInfoDetailCardById` 用空 `ids` 查询。**即便把 `relation_id` 改成正确列名**,`:123` 收集的是 `relation_friend.id`(好友条目自增主键),而 `GetrelationInfoDetailCardById`(`:26`)拿它去 `relation_extend` 按用户身份匹配,且列名写成 `relation_id in ?`(与同表 `:20` 的 `relation_identity = ?` 自相矛盾,`relation_extend` 无 `relation_id` 列)→ 语义与列名双重不匹配,好友卡片无法装配;正确来源应是 `item.FriendrelationIdentity`(`relation_friend.go:12`)。此外 `:141-142` 回填的 `RemarkName`/`Popular` 不在 `Filed` 的查询列里(`common/const.go:15`),备注与置顶**恒为空**;`:134` 的 `Scan(&reply.Tags)` 直接把关系表扫进 `TagItem`,`TagItem.friend_total`(标签成员数)从未统计,**恒为 0**(标签名 `tag_name` 也取不到 `name` 列)。 | +| **高** | `internal/logic/friend/tag_member_fetch.go:27` | **标签成员接口三处错误叠加**:① 表选错——标签成员在 `relation_friend_in_tag`,却查 `RelationFriend`;② 列不存在——`relation_id`、`group_identity`(`group_identity` 是 group 模块的字段,见 `module/social/group/internal/models/group_member.go:12`);③ `Pluck("relation_id", ids)` 的 dest **传值而非指针**,GORM 回填时会对不可寻址切片执行 `Statement.ReflectValue.Set(...)`(`gorm.io/gorm@v1.31.2/scan.go:301-302,347-348`)→ 查询成功即 panic。 | +| **高** | `internal/logic/common/const.go:155-172` + `internal/logic/match/fetch.go:41` | **匹配列表 SQL 无效**:`From relation_match as rm,relation_extend as pe Left join pe on ...` —— `pe` 是 FROM 里已定义的别名而不是表名,逗号连接又与 LEFT JOIN 混用,Postgres 会报 `relation "pe" does not exist`;`Offset ? Limit ? Order by rm.id Desc` 把 `ORDER BY` 放在 `LIMIT/OFFSET` 之后,也是语法错误。因此 `Match.Fetch` 恒返回 `ErrDB`;`pe.name` 与同表的 `nickname`(`common/const.go:15`)互相矛盾,`pb.RelationItem` 里也没有 `name` 字段。 | +| **高** | `internal/logic/match/do_join.go:29-35`、`internal/logic/match/do_ignore.go:27` | **匹配的两个写接口操作的是错误的对象**:`DoJoin`("执行通过,加为好友",`proto/match.proto:14-15`)插入的是 `RelationFollow`(关注记录),不会产生好友关系;`DoIgnore` 删除的是 `RelationFollow`,`relation_match` 的 `status` 从未被更新(全仓库无 `RelationMatch` 写入点)→ 匹配记录永远停在初始状态,列表反复出现同一批人。 | +| **高** | `internal/logic/follow/fetch.go:26-49` | `Params["direction"]` 既非 `MY` 也非 `WHO`(含未传参)时,`default` 分支直接 `return &pb.FetchRelationItemReply{}, nil`(`:37-38`)→ **静默返回空列表而非参数错误**,调用方无法区分"没有关注"与"参数写错";分页只有下限兜底 `PageSize <= 1 → 20`(`:45-47`),**没有上限**。 | +| 中 | `internal/logic/friend/apply_do.go:28-61` | 申请流程三次写库(申请、留言、回写 `last_message_id`)**无事务**,任一步失败都会留下半成品(如申请存在但没有留言);`LastMessageID` 先写 0 再回写(`:33,:57`);`uint(in.ToId)` 直接截断 `int64`。 | +| 中 | `internal/logic/friend/apply_do_pass.go:29-47` | **好友关系单向且可重复**:只写接受方一侧的记录(`:31-32`),申请方的好友列表(按 `passport_id` 查)不会有这条记录;无重复添加校验,`relation_friend` 也无唯一约束。 | +| 中 | `internal/logic/friend/{apply_do_pass.go:43,apply_do_reject.go:26}` | `UpdateColumn("status", ...)` 不检查 `RowsAffected` → 对不存在、不属于自己、甚至已处理过的申请都会返回成功;已通过的申请还能再被置为 -1(`status` 无流转校验)。 | +| 中 | `internal/logic/follow/state.go:29-45` | 语义不一致:找到关注记录时返回关注条目自身的 `identity`,未找到时返回 `vars.OK`(`:31-39`),同一字段两种含义;`gorm.ErrRecordNotFound` 之外统一 `ErrDB`。 | +| 中 | `internal/logic/common/const.go:54,72,120,128` | 多处查询错误被忽略:`:54` 之后的 `Find(&applys)` 检查了错误,但 `:72` 的消息查询、`:120`/`:128` 的好友与标签关联查询都没有检查 `.Error` → 查询失败时静默返回"空关系"。 | +| 中 | `internal/logic/friend/modify_nickname.go:22-24` | 与契约矛盾:`proto/friend.proto:19` 写"如果要清除备注,直接nickname传空值",实现却在 `Nickname == ""` 时返回 `ErrInvalidArgument` → **备注无法清除**。 | +| 低 | `internal/logic/match/fetch.go:23-28`、`internal/logic/follow/fetch.go:42-47` | 两处分页兜底重复实现,且把 `int64` 页码/页大小转成 `int` 后再算 `Offset`(`int(in.PageNo-1)*int(in.PageSize)`),无溢出与上限保护。 | +| 低 | `internal/logic/common/const.go:44` | 好友申请列表硬编码 `maxSize = 50`,`VersionRequest` 也没有分页字段(`proto/const.proto:21-23`)→ 超过 50 条的申请永远不会出现在列表里,且没有任何提示。 | +| 低 | `internal/logic/common/const.go:172` | `Raw(...).Scan(&cards)` 的 `cards` 为 `[]*pb.RelationItem`,属于"把 pb 结构当表模型用",列名必须与 pb 字段严格一致(当前 `name` 就落空),无编译期保护。 | + +### 6.3 未完成实现 + +| 类别 | 内容 | 证据 | +| --- | --- | --- | +| TODO 占位 + 死代码 | `follow.Do`(入口走 `Doing`),全仓库无调用点 | `internal/logic/follow/do.go:26`、`internal/server/follow_server.go:19-21` | +| 空实现(无 TODO 标注) | `Friend.Search` | `internal/logic/friend/search.go:18-21` | +| 数据初始化缺失 | `models` 包无 `InitData`(`models/query.go`、`models/base.go` 各 1 行),`cmd/main/main.go:37` 的注册被注释 → 需要种子数据只能人工写库 | `internal/models/query.go:1`、`cmd/main/main.go:37` | +| 匹配记录无写入路径 | `relation_match` 只被 `Count` 读取,模块内无任何插入/更新 | `internal/logic/match/fetch.go:34`(`grep RelationMatch` 全仓库仅命中此处与模型定义) | +| gateway 未接线 | `pb.RegisterFollowHandlerServer` / `RegisterFriendHandlerServer` / `RegisterMatchHandlerServer` 在模块内**无调用点**;`internal/server/new.go:20-25` 未创建 `ServeMux` | `internal/server/new.go:20-33`、`cmd/main/main.go:32` | +| 未接入聚合入口 | `pkgs/all/internal/service/service.go:18-32`(15 项)与 `pkgs/ecmall/internal/service/service.go:18-32`(14 项)均无 `social/*`;本模块无 `service/` 目录,没有 `Expose/Dependencies` | `pkgs/all/internal/service/service.go:18-33`、`pkgs/ecmall/AGENT.md:26` | +| 无建表手段 | `database.AppendMigrate`/`AutoMigrate` 在 `module/social/**` 无匹配 → 7 张表依赖外部 SQL,仓库内无脚本 | `grep AppendMigrate module/social` 无结果 | +| README 空壳 | 全文只有标题 | `README.md:1-2` | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 中 | `internal/server/new.go:20-25` + `cmd/main/main.go:32` | `Server.Mux`(`new.go:16`)声明后从未赋值,`GatewayMux: s.Mux` 传给 SDK 的是**类型非空、值为 nil** 的 `*runtime.ServeMux`;`service.Start` 用它直接 `http.ListenAndServe`(`bsm-sdk/core/service/service.go:106-111,120-129`),请求到达时 `runtime.ServeMux.ServeHTTP` 会读取自身字段(`grpc-gateway/v2@v2.30.0/runtime/mux.go:407-437`)→ **nil 接收者 panic**(net/http 按连接 recover,客户端表现为连接中断)→ `Gateway.Enable: true` 得到的 HTTP 侧完全不可用。 | +| 中 | `internal/config/config.go:56` | `conf.NotNil(Spec.Service, Spec.Cache)` 未校验 `Databases`,而 `with.Databases` 在 `cfg == nil` 时 `panic("No Database Source Found !")`(`bsm-sdk/core/with/databases.go:12-15`)→ 配置漏配即启动 panic。当前 `etc/relation_dev.yaml:4-7` 有 `Databases` 段,可正常初始化。 | +| 中 | `etc/relation_dev.yaml:16` + `internal/impl/impl.go:24` | `MicroService.Anonymous` 写的是 `relation.ping.hello`,而本服务只有 `Follow`/`Friend`/`Match` 三个 service、不存在 `ping` 方法(`internal/server/new.go:28-30`)→ 匿名清单无效;配置中**没有 `Etcd` 段**,`with.Etcd(nil)` 直接返回 nil(`bsm-sdk/core/with/etcd.go:14-17`),一旦把 `MicroService.Enable` 改成 true,`service.Start` 会因 `EtcdClient == nil` 执行 `os.Exit(1)`(`bsm-sdk/core/service/service.go:66-70`)。 | +| 中 | `etc/relation_prod.yaml:31-35` | `KycConf` 是死配置:`SrvConfig` 只映射 `Kyc`(`internal/config/config.go:25`),`KycConf` 整块不会被读取(dev/test 两份没有这一段,属复制粘贴残留)。 | +| 中 | `etc/relation_dev.yaml`、`etc/relation_prod.yaml`、`etc/relation_test.yaml` | 三份配置除 `Port`(12248 / 12426 / 12426)外**逐行相同**,全部指向 `127.0.0.1` + `dbname=bsm_dev`、相同 `Gateway.Port: 12425` → 环境无法区分;数据库口令以明文形式入库(`password=CHANGE_ME`)。 | +| 低 | `internal/logic/common/const.go:176-188` | 缓存写入 `Set(..., 0)`(永不过期,`:182`),而 `Friend.Delete`/`ApplyDoPass`/`ApplyDoReject` 等变更路径**没有任何缓存失效**(全模块无 `Del` 调用)→ 好友/申请列表会长期返回旧数据,只能依赖 `version` 变化被动刷新。 | +| 低 | `internal/logic/friend/fetch.go:29`、`internal/logic/friend/apply_fetch.go:29` | `cache, err := common.GetCache(...)` 复用了上一段的 `err`,把 `CollectionFriendData/CollectionFriendApply` 的错误静默覆盖 → 错误归因混乱(也是 6.2 中 `cacheErr`/`err` 混用问题的根源)。 | +| 低 | `internal/impl/impl.go:21-24` | `MemorySerice` 初始化后全模块零使用;`EtcdService` 只出现在被跳过的注册路径(Redis 仅用于 `common` 的缓存,属实际使用)。 | +| 低 | `internal/models/relation_friend.go:11-12` | 字段命名 `FriendrelationID`/`FriendrelationIdentity` 与库里 `friend_relation_id` 的断词方式不一致(`FriendRelationID` 更贴切),同一文件也无文件头注释(其余模型文件都有)。 | +| 低 | 全模块 | 0 个 `*_test.go`;`test/` 目录不存在,无任何请求样例;`cmd/cli/main.go` 只打印配置。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| R1 | 高 | 好友申请详情无归属校验,可读取他人申请与留言 | 隐私泄露 | +| R2 | 高 | 可向任意申请追加留言(不校验归属与存在性) | 数据污染、可伪造会话 | +| R3 | 高 | 通过申请时不校验申请与好友参数,可单方面加任意人为好友 | 越权、关系数据失真 | +| R4 | 高 | 好友删除/备注/置顶的归属条件列名与语义双错 | 功能不可用 + 越权风险 | +| R5 | 高 | `Follow.Undo`、`Match.DoIgnore` 按 identity 无归属删除 | 可删他人关注关系 | +| R6 | 高 | `TagDoUpdate` DEL 无归属限定,ADD 无归属与好友校验 | 可改他人标签 | +| R7 | 高 | `Friend.TagFetch` 对 nil reply 取字段地址 → 必然 panic | 接口崩溃 | +| R8 | 高 | `Friend.Fetch`/`ApplyFetch` 可返回 `(nil, nil)`、并在 `cacheErr` 分支 panic | 接口不可用/崩溃 | +| R9 | 高 | 7 个接口按不存在的列 `relation_id` 查询 | 好友与标签功能整体不可用 | +| R10 | 高 | `CollectionFriendData` 吞错误 + `Filed` 缺列 + `friend_total` 未统计 | 好友列表恒空、备注/置顶/标签数恒空 | +| R11 | 高 | `TagMemberFetch` 表错 + 列错 + `Pluck` 非指针 dest | 接口不可用或 panic | +| R12 | 高 | `GetrelationInfoDetailCardByMatch` SQL 无效(别名 `pe`、`Order by` 位置) | 匹配列表恒失败 | +| R13 | 高 | `Match.DoJoin`/`DoIgnore` 操作关注表,匹配状态永不更新 | 功能错位、匹配列表重复 | +| R14 | 中 | 关注无幂等、自关注未拦截;关系表无唯一约束 | 关注计数虚高 | +| R15 | 中 | 申请流程无事务、好友关系单向、状态无流转校验 | 半成品/不一致数据 | +| R16 | 中 | `Follow.Fetch` 参数未识别时静默返回空;无分页上限 | 功能不可用 + 资源风险 | +| R17 | 中 | gateway 未接线、`Mux` 为 nil、无 `service.Expose` | HTTP 不可用、无法被聚合 | +| R18 | 中 | `Databases` 未校验、`KycConf` 死配置、三份 yaml 同内容、缺 `Etcd` 段 | 部署脆弱、环境不分 | +| R19 | 低 | `follow.Do` 死代码、空 `InitData`、缓存无失效、命名不一致、无测试、README 空壳 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **补归属校验**(R1/R2/R3/R4/R5/R6):所有涉及"某个人的关系记录"的写/读接口,把 `auth` 从 `ParseMetaCtx` 取回(不要写 `_`),并在 SQL 条件里加上归属列——关系记录用 `passport_id = auth.ID`(`relation_friend`、`relation_friend_tag`、`relation_friend_in_tag`),关注记录用 `from_identity = auth.Identity`(`relation_follow`);`ApplyGet`/`ApplyDoMessage` 加 `to_identity = auth.Identity OR from_identity = auth.Identity`;`ApplyDoPass` 先用 `apply_identity` 查出申请,核对 `to_identity == auth.Identity` 且 `from_identity == in.FriendRelationIdentity` 之后再建好友记录,并检查 `RowsAffected`。 +2. **修列名**(R4/R9/R10):把 7 处 `relation_id=?` 改成模型里真实存在的列——好友条目按 `passport_id` 归属、按 `friend_relation_identity`(或 `identity`,需统一语义)定位目标;`TagFetch` 的标签归属用 `passport_id`;`TagMemberFetch` 改为 `relation_friend_in_tag` 按 `passport_id + tag_identity` 取 `friend_identity` 列表(`Pluck("friend_identity", &ids)`,dest 必须取地址)。 +3. **修 panic 与返回**(R7/R8/R11):`TagFetch` 先 `reply = &pb.FriendTagsReply{}` 再用 `&reply.Total`;`Friend.Fetch`/`ApplyFetch` 把 `cacheErr` 分支的打印改为 `cacheErr.Error()`,并在两个赋值分支都不成立时补一次真实查询(或在 `Version == 0` 缺缓存时直接查库),避免返回 nil。 +4. **修好友列表装配**(R10):`CollectionFriendData` 里给 `:120`/`:128` 的 `Find` 加错误检查;`:123` 收集的 id 改为 `item.FriendrelationIdentity`(好友的用户身份),并把 `GetrelationInfoDetailCardById`(`common/const.go:26`)的 `relation_id in ?` 改成 `relation_identity in ?`(与 `:20` 统一);`Filed`(`common/const.go:15`)补上 `remark_name`、`popular`;标签成员数改为按 `tag_identity` 聚合一次 `count` 再填 `TagItem.friend_total`。 +5. **修匹配链路**(R12/R13):`GetrelationInfoDetailCardByMatch` 的 SQL 改为 `FROM relation_match rm JOIN relation_extend pe ON pe.relation_identity = rm.recommend_identity WHERE rm.relation_identity = ? ORDER BY rm.id DESC LIMIT ? OFFSET ?`,并统一用 `nickname`(与 `Filed` 一致);`Match.DoJoin` 改为写入 `relation_friend`(复用 `ApplyDoPass` 的建好友逻辑),`Match.DoIgnore` 改为按 `relation_identity + recommend_identity` 更新/删除 `relation_match`,两者都补 `RowsAffected` 校验。 +6. **补幂等与事务**(R3/R14/R15):`Follow.Doing` 先按 `from_identity + to_identity` 查一次(并拒绝 `from == to`);`ApplyDo` 前查是否已有同类申请或已是好友;`ApplyDoPass` 建好友前查重;`ApplyDo` 的三次写入与 `ApplyDoPass` 的"建好友 + 改申请状态"分别放进 `impl.DBService.Transaction`。 +7. **修参数与分页**(R16):`Follow.Fetch` 的 `default` 分支返回 `ErrInvalidArgument` 而不是空列表;`follow`/`match` 的分页补上限(如 50),`CollectionFriendApply` 的 `maxSize=50` 改为可传入的分页参数;`Follow.State` 的返回语义固定为一种(建议只返回"是否关注"与关注条目 identity,不再用 `vars.OK` 表达未关注)。 +8. **清理与一致性**(R19):删除 `internal/logic/follow/do.go` 的死代码 `Do`(或在其真正实现前改为返回 `codes.Unimplemented`);`Friend.Search` 未实现前返回明确的未实现错误;`nickname` 空值清除备注的语义按 `proto/friend.proto:19` 实现;`Friend.Delete`/`ApplyDoPass`/`ApplyDoReject` 变更后删除对应缓存 key;`KycConf`(`etc/relation_prod.yaml:31-35`)删除。 +9. **接线与配置**(R17/R18):`internal/server/new.go` 中 `Mux: gwRuntime.NewServeMux()` 并调用 `pb.RegisterFollowHandlerServer`/`pb.RegisterFriendHandlerServer`/`pb.RegisterMatchHandlerServer`;或在 `service/` 下补与 wallet 同构的 `Expose` 以便接入 `pkgs/all`;`config.New` 补 `conf.NotNil(Spec.Databases)`;三份 yaml 区分环境与口令来源,`Anonymous` 改为真实存在的方法或删除,若要启用微服务注册需补 `Etcd` 段。 +10. **测试**:为 `Follow.Doing/Undo`(幂等与归属)、`Friend.ApplyDo/ApplyGet`(归属)、`TagFetch`(不再 panic)、`Fetch`(缓存三条分支)各补一条正例 + 一条边界用例(当前 0 测试)。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层或抽象封装。为 relation 引入"统一关系模型""好友/关注抽象层""DTO/VO 分层""缓存统一框架"一类改造不在建议范围内——上述 10 条都是对现有函数、SQL 与配置的最小修正。 diff --git a/docs/sender.md b/docs/sender.md new file mode 100644 index 0000000..a085679 --- /dev/null +++ b/docs/sender.md @@ -0,0 +1,216 @@ +# sender(短信与邮件发送)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/base/sender` | +| 服务域 | 基础与平台服务 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 文件 18 个、1043 行(其中 `cmd/cli/main.go` 125 行为独立的 SMTP 手工工具、`test/grpc/{sms,mail}_test.go` 共 100 行且均带 `//go:build integration`);`proto` 3 个(`sms.proto` 28 行、`mail.proto` 21 行、`const.proto` 326 行);`pb/` 生成文件 7 个 | +| 入口 | `cmd/main`(gRPC + HTTP Gateway 单进程);聚合入口 `pkgs/all`(`pkgs/all/internal/service/sender.go:10-22`,会传入 `Config: config.Spec.Sender`) | +| 对外协议 | gRPC + REST Gateway,REST 路径 `/sender.Sms/Send`、`/sender.Sms/Verify`、`/sender.Mail/Send`(`pb/sms.pb.gw.go:104,124`、`pb/mail.pb.gw.go:77`) | +| 结论摘要 | **腾讯云短信是空实现**——`internal/logic/sms/send.go:151-153` 的 `TencentSender` 直接 `return nil, nil`,调用后不报错、返回 `"null"`,调用方会误认为发送成功。阿里云通道存在 3 个高危缺陷:日发送量限制**从未生效**(`limitKey` 只有读、无写,`send.go:42-51`)、验证码用 `SetNX` 写入但**忽略返回值**、却把新生成的码发出(`send.go:62-65`),导致"收到的码校验不过";黑名单集合 `BlackListCacheKey` 全仓只有读、无写(`send.go:37`),配置项 `BlackListFilter` 从未被读取。验证码用 `math/rand` 生成(`send.go:169`)且 `Verify` 成功后**不删除**该码(`verify.go:28-32`)→ 可重放;失败反而删除 → 一次输错即作废。邮件只实现了 `qq` 一个渠道(`mail/send.go:55-60`),且 `mail/send.go:89-94` 在 `smtp.NewClient` 出错时对 nil client 调 `Close()`。三个接口在模块 yaml 中全部登记为匿名(`etc/sender_dev.yaml:16-18`),聚合部署白名单未放行。 | + +## 1. 服务定位与职责 + +为其它服务提供**出站消息通道**:短信(阿里云 / 腾讯云)与邮件(SMTP),并附带一套手机验证码的生成/存储/校验能力。它自身不决定"何时发什么",由业务方传入 `provider`/`template_code`/`sign_name`(短信)或 `template_key`(邮件)与模板参数;模板正文对邮件存放在 `sender_template` 表,对短信存放在阿里云侧的模板 ID。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口。`ServiceKey = "sender"`(`:17`);`config.New` → `impl.NewImpl` → `server.New(nil)`(`:29`)→ `service.New(...)` + `srv.Start()`(`:32-48`) | +| `cmd/cli/main.go` | 125 行的独立 SMTP 手工工具:硬编码 `smtp.exmail.qq.com:465`(`:13-14`)、发件人 `yanweidong@senlinai.com`(`:89`)、凭据取自环境变量 `BSM_SMTP_USER`/`BSM_SMTP_PASSWORD`/`BSM_SMTP_TO`(`:34-38`),带 3 次重试(`:68`) | +| `internal/config/config.go` | `SrvConfig`(`:17-28`)+ `SmtpConf`(`:31-38`)/`SmsConf`(`:41-46`)/`codeConf`(`:49-56`);`New` 只校验 `conf.NotNil(Spec.Service, Spec.Cache)`(`:70`) | +| `internal/impl/impl.go` | 初始化 `MemorySerice`(拼写错误,`:16,22`)/`RedisService`/`DBService`/`EtcdService`,最后调 `withProvider()`(`:30`) | +| `internal/impl/provider.go` | 服务商客户端构建:`withProvider` 按 yaml 键名匹配 `google`/`qq`/`aliyun`/`tencent`(`:31-50`);`NewSMTP` **恒返回 nil**(`:52-54`);`NewAliyun`(`:56-79`)/`NewTencent`(`:81-92`)出错即 `panic` | +| `internal/logic/sms/send.go` | `Send`(`:26-100`)→ 校验手机号/模板码 → 黑名单 → 日限流 → 生成/取验证码 → 分派 `AliyunSender`(`:102-149`)/`TencentSender`(`:151-153`) | +| `internal/logic/sms/verify.go` | `Verify`(`:12-40`):比对 Redis 中的验证码 | +| `internal/logic/sms/const.go` | 4 个 Redis 键常量(`:3-8`) | +| `internal/logic/mail/send.go` | `Send`(`:22-69`):查模板 → 渲染 → `QQ`(`:76-152`);`ValidateEmail`(`:71-74`) | +| `internal/models/sender_template.go` | `sender_template` 表模型(`Title`/`Key`/`Subjet`/`Body`,`:10-13`);字段名 `Subjet` 为拼写错误 | +| `internal/server/{sms_server,mail_server,new}.go` | 把 `Sms`/`Mail` 注册到 gRPC(`new.go:33-34`) | +| `internal/excode/ex.go` | 11 个错误码;其中 `ErrAppName`/`ErrMustWhiteList`/`ErrExpired` 无引用(见 6.3) | +| `internal/routers` | **不存在**(走 gRPC + HTTP Gateway,无 Gin 路由) | +| `service/{expose,dependencies}.go` | 聚合宿主注入点:`Expose` 覆盖 `config.Spec`(`expose.go:24-26`)并注册 Gateway handler(`:30-35`) | +| `proto/{sms,mail,const}.proto` | 前两个定义本模块 3 个 rpc;`const.proto` 为本模块无关的共享消息 | +| `etc/sender_{dev,prod,test}.yaml` | Port 12208 / Gateway 12207;**prod 与 test 均为 dev 的近似副本**(本机库/Redis、占位 AK) | +| `test/grpc/{sms,mail}_test.go` | 均带 `//go:build integration`(`sms_test.go:1`、`mail_test.go:1`),默认打真实地址(`sms_test.go:17` 为 `192.168.31.148:12208`) | +| `test/http/*.http` | 3 个手工请求样例,指向 `http://api.apinb.com/sender.{Sms,Mail}/*` | +| `README.md` / `UPGRADE_SUMMARY.md` | 与实现不符的文档(见 6.4) | + +## 3. 接口清单 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/sender.Sms/Send` | 发送短信(可顺带生成验证码) | 无(模块 yaml 登记匿名 `etc/sender_dev.yaml:17`) | `internal/logic/sms/send.go:26` ← `internal/server/sms_server.go:18` | +| POST | `/sender.Sms/Verify` | 校验手机验证码 | 无(`etc/sender_dev.yaml:18`) | `internal/logic/sms/verify.go:12` ← `sms_server.go:22` | +| POST | `/sender.Mail/Send` | 按模板发送邮件 | 无(`etc/sender_dev.yaml:16`) | `internal/logic/mail/send.go:22` ← `internal/server/mail_server.go:18` | +| POST | gRPC `/sender.Sms/Send|Verify`、`/sender.Mail/Send` | 同上(同一 handler) | 同上 | `internal/server/new.go:33-34` | + +**声明但未实现/占位的接口**:无"声明了但 server 未实现"的方法(`proto/sms.proto:6-9`、`proto/mail.proto:6-8` 与 `internal/server/*_server.go` 完全对应)。但**已声明并已接入路由的 provider 存在空实现**: + +| 声明位置 | 空实现位置 | 表现 | +| --- | --- | --- | +| `proto/sms.proto:13` 的 `provider` 字段(`send.go:82-86` 分派 `tencent`) | `internal/logic/sms/send.go:151-153` `func TencentSender(args *pb.SmsSendRequest) (map[string]any, error) { return nil, nil }` | `result == nil && err == nil` → `send.go:95-99` 返回 `Reply: "null"` 且无错误,**调用方误判为发送成功** | +| `internal/impl/provider.go:35-38` 匹配 `google` SMTP | `internal/impl/provider.go:52-54` `func NewSMTP(...) *smtp.Client { return nil }` | `Provider.Google`/`Provider.QQ` 恒为 nil(`mail.Send` 未使用 Provider,故该结构为死代码) | +| `internal/logic/mail/send.go:55-60` 的 provider switch 只有 `case "qq"` | 无 `google`/其它分支 | 传 `provider=gmail` 直接 `ErrNotProvider`(`test/grpc/mail_test.go:37` 正是这么调的) | + +> 鉴权补充:与 `initial` 同构的三重问题——① `etc/sender_dev.yaml:14` 为 `MicroService.Enable: false`,匿名清单不会写入 etcd(`D:\work\bsm-sdk\core\service\service.go:66-90`);② 独立部署时模块网关把 `GatewayMux` 直接交给 `http.ListenAndServe`(`service.go:106-111`、`:120-129`),**不带任何鉴权中间件**,因此这三个接口对网络可达方完全开放;③ 聚合部署时 `pkgs/all/etc/default_dev.yaml:24-43` 未放行任何 `/sender.*`,与模块 yaml 的匿名声明矛盾;④ 即便改 `Enable: true`,清单条目写作 `sender.Sms.Send`(`etc/sender_dev.yaml:17`),而框架发现的方法是 `Sms.Send`(`service.go:149-158`)、网关规范化路径是 `/sender.Sms/Send`(`authorization.go:105-112`)——三种格式互不相等,永远不命中。 + +## 4. 数据模型与表 + +### 表 `sender_template`(`internal/models/sender_template.go:8-14`) + +| 字段 | 类型 | 约束 | 说明 | +| --- | --- | --- | --- | +| `id`/`created_at`/`updated_at`/`deleted_at` | uint / timestamp | PK / 软删 | 来自 `types.Std_IICUDS`(`:9`) | +| `title` | varchar(255) | not null default '' | 模板标题 | +| `key` | varchar(100) | not null **uniqueIndex** | 模板标识,`mail.Send` 按它查询(`mail/send.go:43`) | +| `subjet` | varchar(255) | not null default '' | 邮件主题(字段名拼写错误,`:12`) | +| `body` | text | not null | 模板内容,交给 `html/template` 解析(`mail/send.go:49`) | + +> 该表**没有启用/停用字段**,也没有"渠道/语言/场景"维度(`mail/send.go:43` 只按 `key` 取一条)。短信侧无任何本地表:模板 ID 与签名由调用方每次传入(`proto/sms.proto:14-15`)。 + +### Redis 键(`internal/logic/sms/const.go:3-8`) + +| 常量 | 值 | 用途 | 实际读写情况 | +| --- | --- | --- | --- | +| `KeyPrefix` | `/SMS/Code/` | 验证码:`/SMS/Code/{phone}` | 写 `send.go:65`(`SetNX`)、读 `verify.go:23`、删 `verify.go:35` | +| `BlackListCacheKey` | `/SMS/BlackList/` | 手机号黑名单(Set) | **只有读**(`send.go:37`,`SIsMember`);全仓无任何写入点 → 黑名单永远为空 | +| `LimitCacheKey` | `/SMS/LimitCacheKey/` | 日发送计数:`/SMS/LimitCacheKey/{yyyy-mm-dd}{phone}` | **只有读**(`send.go:42,45`);全仓无任何 `INCR`/`Set` → 计数恒为 0 | +| `FormatDay` | `2006-01-02` | 日限流键的日期片段 | — | + +## 5. 核心流程 + +```mermaid +flowchart TD + A["POST /sender.Sms/Send"] --> B["校验 phone 非空且匹配正则,否则 ErrPhone"] + B --> C["校验 template_code 非空,否则 ErrTemplate"] + C --> D["SISMEMBER /SMS/BlackList/{phone}"] + D -->|"命中"| E["返回 ErrInBlackList"] + D -->|"未命中"| F["GET /SMS/LimitCacheKey/{date}{phone} 取 twice"] + F --> G{"twice > Code.MaxSentLimit"} + G -->|"是"| H["返回 ErrSentLimit"] + G -->|"否"| I{"is_gen_code"} + I -->|"true"| J["GenValidateCode 生成新码,SETNX 写入 Redis(忽略返回值)"] + I -->|"false"| K["从 paramters[code] 取调用方传入的码"] + J --> L["按 provider 分派:aliyun 走阿里云 OpenAPI"] + K --> L + L --> M{"provider"} + M -->|"tencent"| N["TencentSender 返回 nil, nil(空实现)"] + M -->|"其它"| O["返回 ErrNotProvider"] + L --> P["json.Marshal(result) 后作为 Reply 返回"] + N --> P +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 高 | `internal/logic/sms/verify.go:28-32` | **验证码可重放**:`if code == in.Code { return "true" }` 命中后**不删除** Redis 键,仅在失配路径才 `Del`(`:34-35`)。有效期 300s(`etc/sender_dev.yaml:46`)内同一验证码可无限次校验通过;结合下面"无尝试次数限制",`Verify` 被暴力枚举成功后仍可反复使用。注释 `//verify pass ; delete the requestId`(`:34`)与实际行为(删的是失败分支)相反,说明是写错分支。 | +| 高 | `internal/logic/sms/verify.go:12-40` | **无失败次数限制/无锁定**:`Verify` 不统计失败次数、不锁定手机号、无验证码错误计数,6 位数字码在 300s 内可被离线暴力枚举。且验证码键**只用手机号**(`const.go:5`,`verify.go:22`),不区分 `app`/`template_code`,同一手机号在多业务间共用一枚码,任一业务泄漏即全部可验证。 | +| 高 | `internal/logic/sms/send.go:37-39`(读)+ `const.go:6` | **黑名单形同虚设**:判定逻辑存在(`SIsMember` → `ErrInBlackList`),但 `/SMS/BlackList/` 集合在**整个仓库内没有任何写入点**;配置项 `Code.BlackListFilter`(`internal/config/config.go:55`,yaml `etc/sender_dev.yaml:49-50`、`etc/sender_prod.yaml:49-50`)也**从未被代码读取**(全仓检索仅命中定义处与 yaml)。结论:无法通过该机制拦截任何号码。 | +| 高 | `internal/logic/sms/send.go:41-51`(读) | **日发送量限制从未生效**:`limitKey := LimitCacheKey + 日期 + 手机号` 只被 `Get`(`:45`)读取,全仓没有任何 `Incr`/`Set` 写入该键,`twice` 恒为 0,`twice > MaxSentLimit`(`:49`)恒为假。任意外部调用方可在一天内对同一号码无限次发送短信(成本与骚扰风险)。注意判断本身也应是 `>=`(当前语义为"已超过 N 次才拒",即实际允许 N+1 次)。 | +| 高 | `internal/logic/sms/send.go:66-73`、`:104-109` | **验证码内容由调用方控制**:`is_gen_code=false` 时直接从 `in.Paramters["code"]` 取"验证码"(`:68-71`);即使 `is_gen_code=true`,`AliyunSender` 在设置好 `templateParam["code"]` 之后又用 `args.Paramters` **覆盖同名键**(`:107-109`)。因此调用方可让短信正文携带任意 `code`(配合 `sign_name`/`template_code` 自填)= 把短信通道当成任意内容/任意签名发送器(配合 6.1 前三条约等于开放短信网关)。 | +| 中 | `etc/sender_dev.yaml:16-18`、`etc/sender_prod.yaml:16-18`、`etc/sender_test.yaml:16-18` | 三个接口全部登记匿名;叠加 6.1 的开放发送能力,未鉴权即可调用。且 `Verify` 对所有号码可探测(返回 `true`/`false`),可用来枚举"哪些手机号正在流程中"。 | +| 中 | `etc/sender_dev.yaml:40` | 仓库内提交了疑似真实的阿里云 AccessKeyId `LTAI5tKEmKuuoixE4iw8NZbX`(`AccessKeySecret` 已脱敏为 `CHANGE_ME`)。KeyId 本身不应入库;若该 Key 仍有效,应连同 Secret 一并轮换并从历史中清理。 | +| 中 | `internal/logic/sms/verify.go:23-26` | Redis 读取失败时把底层错误原文回给调用方:`errcode.NewError(1311, err.Error())`,泄漏内部错误文本;同时"验证码不存在/已过期"也走这条路径,而专用错误码 `excode.ErrExpired`(`internal/excode/ex.go:14`)定义了却没用。 | +| 中 | `internal/logic/sms/send.go:96,146-147` | 用 `fmt.Println` 直接把**含手机号与模板参数**的请求体、以及响应结果打到 stdout(并会被 supervisor 落盘到 `etc/supervisor.bsm-apps-sender.conf:8` 的 `/data/app/logs/apps-sender.log`)。手机号属个人敏感信息,日志中不应明文记录。 | +| 中 | `internal/logic/mail/send.go:127-129` | 邮件头用字符串拼接构造:`"Subject: " + subject + "\n\n"`,`subject` 来自数据库模板(`mail/send.go:57`,`tplRecord.Subjet`)且未做 CRLF 过滤。若模板可被管理端写入 `\r\n`,即形成邮件头注入(可注入 `Bcc:` 等)。同时未设置 `Content-Type`/`MIME-Version`/`Content-Transfer-Encoding`,中文主题/正文按裸 UTF-8 发送,多数客户端会乱码。 | +| 低 | `internal/logic/mail/send.go:43-46` | 模板查询只按 `key` 精确匹配、无启用/渠道维度;模板 `body` 直接交给 `html/template` 解析(`:49`),DB 内容的可信任边界未在代码中约束。`html/template` 会转义变量,故不构成模板注入,但纯文本邮件会因转义出现 `&` 一类字符。 | +| 低 | `internal/logic/sms/send.go:29,155-159` | 手机号校验仅一条正则(`^(1[3|4|5|6|7|8|9][0-9]\d{4,8})$`),无长度上限与号段白名单;`in.GetPhone()` 未做去空格/去 `+86` 归一,同一真实号码可用 `+86138…`、`86138…` 等写法绕过按号码的黑名单/限流(在其失效前)。 | +| 低 | `etc/supervisor.bsm-apps-sender.conf:6` | 进程以 `user=root` 运行。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 高 | `internal/logic/sms/send.go:62-65` + `:81` | **发出的验证码可能不是存入 Redis 的那一个**。生成新码时用 `SetNX` 写入(`:65`),**完全忽略返回值**;若该手机号的键仍存在(上一次发送距今不足 `Expire`=300s),`SetNX` 不生效、Redis 保留**旧码**,而紧接着 `smsCode`(新码)被传给 `AliyunSender`(`:81`)发到用户手机上。用户收到新码却校验不过,只有等旧码过期重新发送才可能成功。正确做法是 `Set`(覆盖)或在使用 `SetNX` 的成功分支才把该码发出。 | +| 高 | `internal/logic/sms/send.go:151-153` | **腾讯云短信为空实现**(按任务核实项,确认):函数体只有 `return nil, nil`。`send.go:83-85` 的 `impl.Provider.Tencent == nil` 前置判断也拦不住它(`internal/impl/provider.go:46-48` 在配置存在 tencent 时已创建非 nil 客户端),因此 `provider=tencent` 时接口返回 HTTP 200 + `Reply: "null"`,调用方无从知晓短信根本没发出去。 | +| 中 | `internal/logic/sms/send.go:161-176` | 验证码用**非密码学安全**的 `math/rand`:`rand.Seed(time.Now().UnixNano())`(`:169`)+ `rand.Intn(10)`(`:173`)。种子与时间强相关,验证码可被预测/爆破(配合 6.1 无尝试限制)。应改用 `crypto/rand`。另 `width == 0` 时兜底为 4 位(`:163-165`),而调用侧在配置不合法时兜底为 6 位(`:57-59`),两处默认值不一致。 | +| 中 | `internal/logic/sms/send.go:55-59` | 配置不合法时**静默改写运行时配置**:`config.Spec.Code.Length < 4 \|\| > 10` 时把全局 `config.Spec.Code.Length` 直接改成 6(`:58`)。这是对共享可变状态的写操作,并发请求下互相影响,且掩盖了配置错误(既不报错也不打日志)。 | +| 中 | `internal/logic/sms/send.go:75-99` | `result, err` 的取值不做 `nil` 判定就 `json.Marshal(result)`(`:95`),`nil` 会序列化成 `"null"` 并作为 `Reply` 返回(`:98`)。除腾讯空实现外,任何"返回 nil, nil"的实现都会被当作成功。同时 `err` 直接向上抛(`:91-93`),未按渠道归一成模块错误码。 | +| 中 | `internal/logic/sms/send.go:41-42` | 限流键把日期与手机号直接拼接(`LimitCacheKey + "2006-01-02" + phone`),无分隔符:`/SMS/LimitCacheKey/2026-09-22` + `13800138000`。本身不影响正确性,但键的可读性与后续按号段/前缀统计都变差;一旦将来改成"先日期后号码"以外的顺序,历史计数全部错位。 | +| 中 | `internal/logic/mail/send.go:89-94` | **nil 指针解引用**:`client, err := smtp.NewClient(...)` 出错时,下一行立即 `client.Close()`(`:91`)。此时 `client` 为 nil,一旦 `smtp.NewClient` 返回错误(如服务端在握手阶段即断开)将 panic,而不是返回错误。 | +| 中 | `internal/logic/mail/send.go:119-124` 与 `:146-150` | `writer` 被关闭两次:`:124` 有 `defer writer.Close()`,`:146` 又显式 `writer.Close()`。第二次 `Close` 的错误会被当作发送失败返回(`:146-149`),同一封邮件可能被误报失败。 | +| 中 | `internal/logic/mail/send.go:151` | 成功路径**没有调用 `client.Quit()`/`Close()`**,也未关闭 `tls.Conn`(`:78`)。每次发信都会残留一条未正常关闭的 TLS 连接,依赖 GC 回收;持续发信会堆积连接/文件描述符(对比 `cmd/cli/main.go:120` 的正确写法:成功时 `client.Quit()`)。 | +| 低 | `internal/logic/sms/send.go:45-48` | `Get(...).Int()` 用 `!errors.Is(err, redis.Nil)` 把"键不存在"(正常,视为 0 次)与真错误区分开——这部分写法正确(`redis.Nil` 由 SDK 导出,`D:\work\bsm-sdk\core\cache\redis\redis.go:16`)。 | +| 低 | `internal/logic/sms/send.go:57-59` | 长度校验用 `int64` 比较 `config.Spec.Code.Length`,与 `codeConf.Length int64`(`config.go:50`)一致;但 `yaml` 中未配置该字段时为 0,会被静默改成 6,调用方无法感知配置缺失。 | +| 低 | `internal/logic/mail/send.go:22-39` | 参数校验顺序为"先查 provider 配置(`:31-34`)再校验收件人格式(`:37-39`)",`To` 为空时已在 `:26-28` 抛 `ErrInvalidArgument`,但格式错误统一返回 `ErrEmail`(`excode:16`),与"provider 未配置"(`ErrProviderIsNil`)的语义边界清晰,无功能问题。 | +| 低 | `internal/logic/sms/send.go:14`、`internal/logic/mail/send.go:18` | `ctx` 仅被透传给 Redis(`send.go:37/45/65`),未用于任何 HTTP/OpenAPI 调用(阿里云 `CallApi` 未接收 ctx),上游超时无法取消一次正在进行的短信发送。 | +| 低 | `internal/models/sender_template.go:12` | 字段名 `Subjet` 拼写错误(应为 `Subject`),直接映射为列名 `subjet`,与 `mail/send.go:57` 的引用形成"错得一致",但会成为对外数据契约的长期负担。 | + +### 6.3 未完成实现 + +- **`TencentSender` 空实现**:`internal/logic/sms/send.go:151-153`,见 6.2。`internal/impl/provider.go:81-92` 的 `NewTencent` 客户端已完整构建并挂在 `Provider.Tencent` 上,唯独发送函数是空的 → 属于"接线完成、逻辑未写"。 +- **`NewSMTP` 空实现**:`internal/impl/provider.go:52-54` 恒返回 nil,导致 `ProviderClient.QQ`(`:24`)与 `ProviderClient.Google`(`:22`)永远为 nil。`internal/logic/mail/send.go` 并未使用这两个字段,所以它们是**死代码**;`UPGRADE_SUMMARY.md:1-192` 声称的"完整容器化与工具链"也与此无关。 +- **配置项定义了但从未被读取**(全仓检索,仅命中定义处与 yaml): + - `Code.GenerateCode`(`internal/config/config.go:53`,yaml `etc/sender_prod.yaml:47`、`etc/sender_test.yaml:46`)——实际是否生成验证码只取决于请求字段 `is_gen_code`(`send.go:55`)。 + - `Code.CokeyKey`(`config.go:54`,yaml 三处均为 `code`)——真实键名硬编码在 `send.go:104` 的 `templateParam["code"]`。 + - `Code.BlackListFilter`(`config.go:55`)——见 6.1。 +- **错误码定义后无引用**:`ErrAppName`(`internal/excode/ex.go:8`)——`SmsSendRequest` 里根本没有 app 字段;`ErrMustWhiteList`(`:11`)——白名单逻辑不存在;`ErrExpired`(`:14`)——过期路径走的是 `1311`(`verify.go:25`)。 +- **`MemorySerice`(`internal/impl/impl.go:16,22`)**:本模块内除赋值外无任何读取点(`service/dependencies.go:30` 是唯一另一处赋值)→ 内存缓存层初始化后不使用。 +- **`test/grpc/{sms,mail}_test.go` 不是可 CI 执行的测试**:均带 `//go:build integration`(`sms_test.go:1`、`mail_test.go:1`),且依赖真实地址(`sms_test.go:17` 内网 IP、`mail_test.go:19` `api.apinb.com:10020`)。`internal/**` 下**无任何 `*_test.go`**——`Send`/`Verify`/`GenValidateCode`/`ValidateEmail` 四个纯函数零单元测试覆盖。 +- **`test/grpc/mail_test.go:37` 使用 `Provider: "gmail"`**,而 `mail/send.go:55-60` 只实现 `qq`,该用例即使跑起来也必然返回 `ErrNotProvider` → 用例与实现不一致(说明 Gmail 渠道曾是计划项)。 +- **`cmd/cli/main.go`** 与短信/邮件业务完全平行的一套 SMTP 实现(自带重试与 `Quit`),既不复用 `internal/logic/mail`,也不被任何代码引用 → 独立的遗留工具。 +- **`proto/const.proto:1-326`** 定义 `OrderSummaryItem`、`FeedPostItem`、`MarketLoginReply`、`CmsCategoryItem` 等与本模块无关的消息,未被引用 → 死定义。 + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| 高 | `internal/logic/sms/send.go:49`、`:57` | **`config.Spec.Code` 未做非空校验即可解引用**:`codeConf` 是指针(`config.go:27`),`config.New` 只 `conf.NotNil(Spec.Service, Spec.Cache)`(`config.go:70`)。当配置(模块 yaml 或聚合传入的 `pkgs/all` `Sender` 段)缺少 `Code` 时,`config.Spec.Code.MaxSentLimit` 会在第一次调用 `Send` 时 panic(`:49`),而不是启动期报错。`pkgs/all/etc/default_dev.yaml:113-118` 目前有该段,但不构成代码层保证。 | +| 高 | `pkgs/all/etc/default_dev.yaml:98-118` vs `internal/logic/sms/send.go:76-89`、`internal/logic/mail/send.go:31-34` | **聚合配置的 provider 键名与代码期望不匹配**:聚合配置只有 `Sender.SMTP.default` 与 `Sender.SMS.default`,而代码要求键名等于请求传入的 `provider`(`mail/send.go:31` 用 `config.Spec.SMTP[provider]` 直接查表;`sms/send.go:78-80` 需要 `Provider.Aliyun`/`Provider.Tencent` 非 nil,而 `impl/provider.go:42-49` 只在键名为 `aliyun`/`tencent` 时才创建客户端)。结果是聚合部署下短信恒返回 `ErrProviderIsNil`(`:79`、`:84`),邮件要传 `provider=default` 才能走通(与 README/用例中的 `qq`/`gmail` 都不符)。 | +| 中 | `internal/impl/provider.go:56-79`、`:81-92` | 构建客户端失败时 `panic`(`:65`、`:75`、`:88`),且 `panic(err)` 不带上下文,启动期崩溃时无法定位是哪个 provider 的哪一步;`internal/impl/impl.go:30` 在 `NewImpl` 中同步调用,属"启动即崩"路径。 | +| 中 | `etc/sender_prod.yaml:7,10` | 生产配置的 `Databases.Source` 与 `Cache` 仍指向 `127.0.0.1`(`:7`、`:10`),与 `sender_dev.yaml` 完全一致。生产若直接使用该文件会连本机库/本机 Redis。 | +| 中 | `etc/sender_prod.yaml:37-40` | 生产短信配置明显是复制错误且未填真值:`SMS.aliyun.Endpoint: smtp.gmail.com`(`:38`,应为 `dysmsapi.aliyuncs.com`),`AccessKeyId: `、`AccessKeySecret: `(`:39-40`)为占位符。同时三份 yaml 都没有 `tencent` 段,`TencentSender` 即使实现也无可配置的客户端。 | +| 中 | `README.md:155,170` | README 给出的调用地址 `/v1/sms/send`、`/v1/mail/send`(及 `README.md:151` 的 `http://localhost:12202/swagger/`)与真实路径 `/sender.Sms/Send`、`/sender.Mail/Send`(`pb/sms.pb.gw.go:104,124`、`pb/mail.pb.gw.go:77`)不一致,照文档调用必然 404。 | +| 低 | `README.md:9,18` | README 宣称"腾讯云短信""多服务商:支持QQ邮箱、Gmail",与实现(腾讯空实现、邮件仅 `qq`)不符。 | +| 低 | `README.md:149-151`、`:361-365` | README 声明 gRPC `12201` / HTTP `12202`、默认端口 `12201`,而实际 yaml 为 gRPC `12208` / Gateway `12207`(`etc/sender_dev.yaml:2`、`:23`);对不上。 | +| 低 | `README.md:63-103` | README 的配置样例使用 `Service.Name/Port` 嵌套结构(`:63-66`)和 `Databases.Default.{Host,Port,...}`(`:69-76`)的形式,与 `internal/config/config.go:17-28` 实际支持的结构(`Service: ` + `Databases.Source: [...]` 连接串)完全不同,属虚构配置。 | +| 低 | `README.md:352,379` | 声明 `GET /health` 健康检查端点,本模块无任何注册。 | +| 低 | `UPGRADE_SUMMARY.md:19-31,60-68,184-192` | 声称新增了 `Dockerfile`、`docker-compose.yml`、`Makefile`、`CHANGELOG.md`(并称"所有改进都保持了向后兼容性,可以安全地在生产环境中部署"),但仓库中**这四个文件都不存在**(本模块实际只有 `cmd/ internal/ pb/ proto/ etc/ test/ service/` 与 `README.md`/`UPGRADE_SUMMARY.md`)→ 文档不实描述。 | +| 低 | `internal/config/config.go:70` | 与 `ads`/`initial` 相同:`conf.NotNil` 只覆盖 `Service`/`Cache`,未覆盖 `Databases` 与 `Code`;`with.Databases`(`D:\work\bsm-sdk\core\with\databases.go:13-15`)在 `Source` 为空时直接 panic。 | +| 低 | `internal/impl/impl.go:16,22` | 变量名拼写错误 `MemorySerice`(应为 `MemoryService`);同 workspace 的 `pkgs/all/internal/impl` 用正确拼写(`pkgs/all/internal/service/sender.go:16`)。 | +| 低 | `service/expose.go:24-26` | `config.Spec = *options.Config` 直接整份覆盖全局配置;若宿主只填了部分字段(如只给 `SMTP`),其余字段(含 `Code`)会变成零值 → 触发上面第 1 条的 panic。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| S1 | 高 | 腾讯云短信为空实现,返回 `"null"` 且无错误(`send.go:151-153`) | 业务静默失效、用户收不到短信却认为已发送 | +| S2 | 高 | 日发送量限制从未生效(`limitKey` 无写入,`send.go:42-51`) | 短信轰炸、短信费用失控 | +| S3 | 高 | 验证码 `SetNX` 忽略返回值但发送新码(`send.go:62-65,81`) | 收码即校验失败,登录/注册流程不可用 | +| S4 | 高 | 黑名单无写入点、`BlackListFilter` 未读取(`send.go:37`) | 拦截机制形同虚设 | +| S5 | 高 | 验证码可重放 + 无尝试次数限制 + 键仅按手机号(`verify.go:28-32`) | 验证码可爆破、跨业务复用 | +| S6 | 高 | 验证码内容可由调用方指定/覆盖(`send.go:66-73,104-109`) | 短信通道被当作任意内容发送器 | +| S7 | 高 | `config.Spec.Code` 未校验即解引用(`send.go:49`) | 缺配置时运行期 panic | +| S8 | 高 | 聚合配置 provider 键为 `default`,代码要求 `qq`/`aliyun`/`tencent` | 聚合部署下短信恒 `ErrProviderIsNil` | +| S9 | 中 | 验证码用 `math/rand` + 时间种子(`send.go:161-176`) | 验证码可预测 | +| S10 | 中 | 三接口匿名 + 独立部署网关无鉴权中间件;聚合白名单未放行 | 未鉴权调用发送能力 / 上线 401 | +| S11 | 中 | 邮件构造:`client` nil 时 `Close()`(`mail/send.go:91`)、`writer` 双关闭(`:124,146`)、成功不 `Quit`(`:151`) | panic、误报失败、连接泄漏 | +| S12 | 中 | 请求参数(含手机号)与结果明文 `fmt.Println`(`send.go:96,146-147`) | PII 落盘、日志泄露 | +| S13 | 中 | `etc/sender_prod.yaml` 为本机地址 + 错误 Endpoint + 占位 AK | 生产部署误连本机/配置无效 | +| S14 | 中 | 仓库内提交疑似真实阿里云 AccessKeyId(`etc/sender_dev.yaml:40`) | 凭据泄露 | +| S15 | 低 | 死代码/死配置(`NewSMTP`、`MemorySerice`、3 个未读配置项、3 个未用错误码、`const.proto`、`cmd/cli`)+ 无 `*_test.go` | 可维护性、无法回归验证 | +| S16 | 低 | README / UPGRADE_SUMMARY 与实现大面积不符(路径、端口、渠道、Docker 文件) | 文档误导、排障成本 | + +## 8. 修复建议(务实项) + +1. **实现或摘除腾讯通道**:`internal/logic/sms/send.go:151-153` 二选一——要么按 `impl.Provider.Tencent` 调 `sms/v20210111` 的 `SendSms`(客户端已在 `internal/impl/provider.go:81-92` 建好);要么删除 `case "tencent"` 分支(`send.go:82-86`)并同步删掉 `NewTencent`/`Provider.Tencent`,让 `tencent` 直接落到 `default: return ErrNotProvider`(`:87-88`)。同时给 `send.go:95-99` 加 `if result == nil` 判定,返回明确错误,避免把 `"null"` 当成功。 +2. **让限流真正生效**:在 `internal/logic/sms/send.go:49` 之前(或在短信真正发出成功后)对 `limitKey` 执行一次 `Incr` 并 `Expire` 到当天结束;把比较改为 `twice >= config.Spec.Code.MaxSentLimit`。改动点仅 `send.go:41-51` 一处。 +3. **修验证码写入语义**:`internal/logic/sms/send.go:65` 由 `SetNX` 改为 `Set`(覆盖旧码),或判断 `SetNX` 的布尔返回值,仅当写入成功时才把 `smsCode` 交给 `AliyunSender`(`:81`)。二选一即可,关键是"发出去的码必须等于存进去的码"。 +4. **修校验语义**:`internal/logic/sms/verify.go` 把删除动作移到**校验成功**分支(把 `:34-35` 的 `Del` 上移到 `:28-32` 的 `if` 内),并加失败计数(同一个 `key` 连续失败 N 次即删除并返回 `ErrExpired`/新错误码),避免重放与爆破。 +5. **黑名单落地**:明确 `/SMS/BlackList/{phone}` 的写入方(哪个服务/接口写这个 Set),并在模块内提供写入入口;若短期内不实现,则从代码中删掉 `send.go:37-39` 的判定与 `Code.BlackListFilter` 配置,避免"看起来有防护"。 +6. **禁止调用方指定验证码内容**:`internal/logic/sms/send.go:66-73` 的 `is_gen_code=false` 分支应删除或改为"仅允许复用 Redis 中已有的码";`AliyunSender` 中 `:107-109` 的参数合并应排除 `code` 键(`if key == "code" { continue }`)。 +7. **验证码随机源**:`internal/logic/sms/send.go:161-176` 改用 `crypto/rand` 生成数字,删除 `rand.Seed`;同时把 `:55-59` 对 `config.Spec.Code.Length` 的静默改写改为"返回配置错误"。 +8. **配置校验前移**:`internal/config/config.go:70` 的 `conf.NotNil` 加上 `Databases` 与 `Code`;或在 `impl.NewImpl`(`internal/impl/impl.go:20`)入口显式判空并打印缺失项名,替代运行期 panic。 +9. **聚合 provider 键名对齐**:把 `pkgs/all/etc/default_dev.yaml:99-112` 的 `SMTP.default` / `SMS.default` 改成代码实际使用的键(`qq`、`aliyun`,需要时再加 `tencent`),或反过来让 `mail/send.go:31`、`impl/provider.go:31-49` 支持 `default` 作为回退键。两条路选一条,保证聚合部署可用。 +10. **邮件客户端修复**:`internal/logic/mail/send.go:89-94` 改为 `if client != nil { client.Close() }` 或直接不关(`conn` 已可关);`:146-150` 只保留一个 `writer.Close()`;成功路径补 `client.Quit()`(参考 `cmd/cli/main.go:120`)。邮件头改为 `net/mail` + `mime.QEncoding` 生成,`subject` 做 CRLF 过滤。 +11. **日志脱敏**:删除 `internal/logic/sms/send.go:96,146-147` 的 `fmt.Println`(或改为只打条数/不打印号码),手机号按 `138****8000` 形式记录。 +12. **生产配置与凭据**:`etc/sender_prod.yaml:7,10` 改为真实生产库/Redis;`:38-40` 修正 Endpoint 并接入真实凭据(走环境变量而非入库);轮换 `etc/sender_dev.yaml:40` 中的 AccessKeyId。 +13. **文档与测试**:改写 `README.md:149-155,170,352,379,63-103` 与 `UPGRADE_SUMMARY.md:19-31,60-68` 中与实现不符的内容(路径、端口、渠道、Docker 文件);把 `test/grpc/*_test.go` 的 `Provider` 改成已实现的 `qq`;至少为 `GenValidateCode`(长度与字符集)、`VerifyPhone`、`ValidateEmail`、`Send` 的 `is_gen_code` 两分支各补一个表驱动用例(可用 `miniredis` 或直接把 Redis 操作收敛为可替换的客户端变量,注意不要为此新建抽象层,复用 `impl.RedisService` 现有赋值点即可)。 + +> 本报告只列出与现有实现直接相关的修复项,不引入新的分层、抽象封装或 DTO/VO 改造。 diff --git a/docs/wallet.md b/docs/wallet.md new file mode 100644 index 0000000..3014468 --- /dev/null +++ b/docs/wallet.md @@ -0,0 +1,347 @@ +# wallet(钱包与支付接入)代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | `module/finance/wallet`(module 路径 `bsm/full/module/finance/wallet`) | +| 服务域 | 资金与支付 | +| 审计日期 | 2026-09-22 | +| 代码规模 | 手写 Go 45 个文件 / 2382 行;生成代码 `pb/` 13 个文件 / 10075 行;`proto/` 5 个文件 / 684 行;`etc/` 3 个 yaml + 1 个 supervisor conf;`*_test.go` 0 个 | +| 入口 | 独立入口 `cmd/main/main.go`(gRPC + grpc-gateway 单进程,端口 12238/12239);聚合入口 `pkgs/all`、`pkgs/ecmall`(经 `service.Expose`);`cmd/cli/main.go` 为占位 CLI | +| 对外协议 | gRPC(4 个 service / 23 个 RPC)+ grpc-gateway REST(`POST /{proto包名}.{服务}/{方法}`);`proto/*.proto` 中**没有任何 `google.api.http` 注解**,路径由 `protoc-gen-slc` 生成 | +| 结论摘要 | 分层与目录照模板齐备,但**资金链路端到端不通**:唯一入账函数 `ChargeWallet` 全仓库无调用点,支付宝分支必然 panic,微信/支付宝共 7 个 RPC 为 TODO;`Payment.Callback` 只需用户 JWT 即可把支付单置为成功;提现只校验不扣减、余额扣减为读后写且不写流水、支付密码可被直接覆盖。生产/测试配置因字段名不匹配而无法加载。 | + +## 1. 服务定位与职责 + +面向 C 端用户的钱包主体:用户钱包(余额、可提现余额、支付密码、绑定的微信/支付宝标识)、银行卡绑定、交易流水、充值/订单支付下单、提现申请,以及对微信支付(APIv3)与支付宝的渠道接入。 + +它是**资金账户与支付下单**服务。不负责订单主体生命周期(订单金额从 `order_summary` 表直接读取,`internal/models/query.go:104`)、不负责结算清算、也没有对账/退款实现(`wallet_refund` 表只有模型与迁移注册,无任何代码引用)。 + +## 2. 代码结构与入口 + +| 路径 | 职责 | +| --- | --- | +| `cmd/main/main.go` | 独立进程入口:`config.New("Wallet")` → `impl.NewImpl()` → `server.New(nil)` → `service.New` → `srv.Use(models.InitData)` → `srv.Start()` | +| `cmd/cli/main.go` | 占位 CLI,只打印 `Hello World!` / `Success` | +| `internal/config/config.go` | `SrvConfig`(Base、Databases、MicroService、Rpc、Gateway、APM、Etcd、WeChat、Alipay、Wallet、QrCodeSavePath)与校验 | +| `internal/impl/impl.go` | 初始化 Memory、Redis、DB、Etcd 四个全局实例 | +| `internal/excode/ex.go` | 钱包业务错误码(3801~3821) | +| `internal/server/{new,payment_server,basic_server,wechat_server,alipay_server}.go` | gRPC 服务注册与 1:1 转发到 logic 层(`protoc-gen-slc` 生成) | +| `internal/logic/basic/{wallet,get_wallet,set_pay_password,bind_payment_id,transactions,add_bank_card,get_bank_card,rm_bank_card,apply_cash}.go` | 钱包/密码/银行卡/流水/提现 | +| `internal/logic/payment/{hello,way,get,by_order,by_charge,callback}.go` | 支付网关列表、下单、支付详情、回调 | +| `internal/logic/wechat/{wechat,jsapi_pre_order,app_pre_order,native_pre_order,transfer,wx_callback}.go` | 微信 APIv3 客户端与下单/回调 | +| `internal/logic/alipay/{alipay,wap_pay,page_pay,app_pay,transfer}.go` | 支付宝客户端与支付/转账 | +| `internal/models/{wallet_basic,wallet_payment,wallet_record,wallet_apply_cash,wallet_bank,wallet_refund,query}.go` | 6 张表的模型 + GORM 自动迁移注册 + `query.go` 中的全部数据访问函数 | +| `service/{expose,dependencies}.go` | 聚合宿主注入用:`Expose` 负责注册 gRPC/gateway handler,`Dependencies` 支持外部覆盖 Redis/Etcd/DB/Cache | +| `proto/*.proto` | 4 个 service 契约(无 HTTP 注解) | +| `etc/{wallet_dev,wallet_prod,wallet_test}.yaml`、`etc/supervisor.bsm-finance-wallet.conf` | 配置与进程守护配置 | + +依赖注入:`service.Expose`(`service/expose.go:22-43`)先 `applyDependencies` 覆盖 `internal/impl` 中的全局对象,再注册 4 个 gRPC service 与 4 组 gateway handler。独立入口不走该路径,直接使用 `internal/impl` 自行初始化的对象。 + +## 3. 接口清单 + +全部 RPC 的 HTTP 映射均为 `POST /{proto包名}.{服务}/{方法}`,路径由生成代码固定(`pb/wallet.pb.gw.go:600-607`、`pb/payment.pb.gw.go:472-477`、`pb/wechat.pb.gw.go:408-412`、`pb/alipay.pb.gw.go:344-347`)。 + +| 方法 | 路径 | 功能 | 鉴权 | 实现位置 | +| --- | --- | --- | --- | --- | +| POST | `/wallet.Basic/GetWallet` | 钱包信息 + 六类收支统计 | JWT | `logic/basic/get_wallet.go:18` | +| POST | `/wallet.Basic/SetPayPassword` | 设置/覆盖支付密码 | JWT | `logic/basic/set_pay_password.go:17` | +| POST | `/wallet.Basic/BindPaymentId` | 绑定微信/支付宝账户标识 | JWT | `logic/basic/bind_payment_id.go:17` | +| POST | `/wallet.Basic/Transactions` | 交易流水分页查询 | JWT | `logic/basic/transactions.go:15` | +| POST | `/wallet.Basic/AddBankCard` | 添加银行卡 | JWT | `logic/basic/add_bank_card.go:18` | +| POST | `/wallet.Basic/GetBankCard` | 银行卡列表 | JWT | `logic/basic/get_bank_card.go:16` | +| POST | `/wallet.Basic/RmBankCard` | 删除银行卡 | JWT | `logic/basic/rm_bank_card.go:17` | +| POST | `/wallet.Basic/ApplyCash` | 提现申请 | JWT | `logic/basic/apply_cash.go:19` | +| POST | `/wallet.Payment/Hello` | 支付服务健康探测 | JWT | `logic/payment/hello.go:13` | +| POST | `/wallet.Payment/Way` | 平台可用支付方式列表 | JWT | `logic/payment/way.go:14` | +| POST | `/wallet.Payment/Get` | 支付单详情 | JWT(按 passport 过滤) | `logic/payment/get.go:13` | +| POST | `/wallet.Payment/ByOrder` | 订单支付下单 | JWT | `logic/payment/by_order.go:21` | +| POST | `/wallet.Payment/ByCharge` | 充值下单 | JWT | `logic/payment/by_charge.go:20` | +| POST | `/wallet.Payment/Callback` | 更新支付单结果与状态 | JWT(仅按 passport 过滤) | `logic/payment/callback.go:15` | +| POST | `/wallet.Wechat/JsapiPreOrder` | 微信 JSAPI 预下单 | **无**(未调用 `ParseMetaCtx`) | `logic/wechat/jsapi_pre_order.go:10` | +| POST | `/wallet.Wechat/AppPreOrder` | 微信 APP 下单 | JWT | **占位** `logic/wechat/app_pre_order.go:11` | +| POST | `/wallet.Wechat/NativePreOrder` | 微信 Native 下单 | JWT | **占位** `logic/wechat/native_pre_order.go:11` | +| POST | `/wallet.Wechat/Transfer` | 微信转账到零钱 | JWT | **占位** `logic/wechat/transfer.go:11` | +| POST | `/wallet.Wechat/WxCallback` | 微信支付回调 | 无 JWT,使用微信平台验签 | `logic/wechat/wx_callback.go:15`(业务逻辑为 TODO) | +| POST | `/wallet.Alipay/WapPay` | 支付宝手机网站支付 | JWT | **占位** `logic/alipay/wap_pay.go:11` | +| POST | `/wallet.Alipay/PagePay` | 支付宝电脑网站支付 | JWT | **占位** `logic/alipay/page_pay.go:11` | +| POST | `/wallet.Alipay/AppPay` | 支付宝 APP 支付 | JWT | **占位** `logic/alipay/app_pay.go:11` | +| POST | `/wallet.Alipay/Transfer` | 支付宝转账 | JWT | **占位** `logic/alipay/transfer.go:11` | + +> 鉴权方式:除 `Wechat.WxCallback` 外,所有 logic 入口均调用 `service.ParseMetaCtx(ctx, nil)`(`git.apinb.com/bsm-sdk/core/service/meta.go:19-49`),要求 metadata 中携带可解析的 JWT;没有做角色/私有 IP 校验(`opts` 恒为 `nil`)。 + +### 声明但未实现 / 空转的接口 + +| 类别 | 接口 | 证据 | +| --- | --- | --- | +| TODO 占位(只做鉴权,随后直接 `return`) | `Wechat.AppPreOrder` | `internal/logic/wechat/app_pre_order.go:18-22` | +| 同上 | `Wechat.NativePreOrder` | `internal/logic/wechat/native_pre_order.go:18-22` | +| 同上 | `Wechat.Transfer` | `internal/logic/wechat/transfer.go:18-22` | +| 同上 | `Alipay.WapPay` | `internal/logic/alipay/wap_pay.go:18-22` | +| 同上 | `Alipay.PagePay` | `internal/logic/alipay/page_pay.go:18-22` | +| 同上 | `Alipay.AppPay` | `internal/logic/alipay/app_pay.go:18-22` | +| 同上 | `Alipay.Transfer` | `internal/logic/alipay/transfer.go:18-22` | +| 有实现但业务逻辑未落地 | `Wechat.WxCallback`(验签+解密后原样返回) | `internal/logic/wechat/wx_callback.go:38` 注释 `// Todo: 业务逻辑` | +| 有实现但入账函数无人调用 | 充值入账 `models.ChargeWallet` | 定义于 `internal/models/query.go:135`,全仓库唯一引用为该定义本身(无调用点) | + +> 上述 7 个占位 RPC 的 Go 写法是 `func(...) (reply *XxxReply, err error) { ...; return }`,即返回 `(nil, nil)`。gRPC 侧会回一个空消息,grpc-gateway 侧对 nil 响应做序列化时会失败(客户端表现为 500/空响应),不是"返回空对象"。 + +## 4. 数据模型与表 + +模型统一在 `init()` 中通过 `database.AppendMigrate` 注册自动迁移(`internal/models/*.go` 的 `init()`)。公共字段来自 SDK:`gorm.Model` = `id/created_at/updated_at/deleted_at`;`types.Std_Identity` = `identity varchar(36) uniqueIndex`;`types.Std_Passport` = `passport_id`、`passport_identity varchar(36) index`;`types.Std_Status` = `status int64 default 0 index`。 + +### 表 `wallet_basic`(`internal/models/wallet_basic.go:14-24`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `id` | uint | PK | 自增主键 | +| `created_at`/`updated_at`/`deleted_at` | timestamp | index(deleted_at) | 软删除 | +| `identity` | varchar(36) | uniqueIndex | 钱包唯一码 | +| `passport_id` | uint | index | 用户 ID | +| `passport_identity` | varchar(36) | index(**非唯一**) | 用户唯一标识 | +| `alipay_id` | varchar(64) | 默认 `''` | 绑定的支付宝 ID | +| `wxpay_id` | varchar(64) | 默认 `''` | 绑定的微信支付 ID | +| `pay_password` | varchar(255) | 默认 `''` | 支付密码(HMAC-SHA256 十六进制串) | +| `balance` | int64 | 默认 0 | 余额,单位:分 | +| `withdrawal_balance` | int64 | 默认 0 | 可提现余额,单位:分 | +| `status` | int64 | index, 默认 0 | 状态(代码中把 `-1` 视为禁用,`logic/basic/wallet.go:31`) | + +> `passport_identity` 没有唯一约束,而懒创建路径(`internal/models/query.go:76-92`)在记录不存在时新建钱包 → 同一用户可能持有多条钱包记录。 + +### 表 `wallet_payment`(`internal/models/wallet_payment.go:16-31`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 支付单唯一码 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 下单人 | +| `wallet_identity` | varchar(36) | not null | 钱包唯一码 | +| `order_no` | varchar(255) | not null,**无唯一索引** | 订单号(充值单为随机串) | +| `trade_no` | varchar(255) | 默认 `''` | 第三方交易流水号;**全仓库无写入点** | +| `type` | int8 | 默认 0 | 1 充值 / 2 电商订单 | +| `pay_channel` | int8 | 默认 0 | 1 微信 / 2 支付宝 / 3 钱包余额 | +| `pay_type` | varchar(255) | not null | JSAPI/APP/MINI/NATIVE/WAP 等 | +| `amount` | int64 | 默认 0 | 金额,单位:分 | +| `args` | varchar(255) | 默认 `''` | 相关参数;无写入点 | +| `remark` | varchar(255) | 默认 `''` | 备注;无写入点 | +| `callback_msg` | varchar(255) | 默认 `''` | 回调信息 | +| `status` | int64 | index, 默认 0 | -3 手动取消 / -2 超时 / -1 失败 / 0 创建 / 1 支付中 / 2 成功 | + +### 表 `wallet_record`(`internal/models/wallet_record.go:16-32`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 流水唯一码 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 用户 | +| `wallet_identity` | varchar(36) | not null | 钱包唯一码 | +| `trans_type` | int8 | index | -1 支出 / 1 收入 | +| `in_trade_no` | varchar(50) | not null,无唯一索引 | 内部交易流水号 | +| `out_trade_no` | varchar(50) | 默认 `''` | 外部交易流水号 | +| `money` | int64 unsigned | 默认 0 | 金额,单位:分 | +| `fee` | int64 unsigned | 默认 0 | 手续费,单位:分 | +| `pay_channel` | int8 | 默认 0 | 1 微信 / 2 支付宝 / 3 银行卡 | +| `pay_type` | varchar(255) | not null | 支付类型 | +| `trade_type` | int8 | 默认 0 | 1 充值 / 2 提现 / 3 消费 / 4 退款 / 5 收益 | +| `ymd` | int32 | index, not null | 年月日(用于当日统计) | +| `ym` | int32 | index, not null | 年月(用于当月统计) | +| `remark` | varchar(255) | 默认 `''` | 备注 | + +### 表 `wallet_apply_cash`(`internal/models/wallet_apply_cash.go:16-28`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 提现单唯一码 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 申请人 | +| `wallet_identity` | varchar(36) | not null | 钱包唯一码 | +| `cash_no` | varchar(255) | 默认 `''` | 内部交易流水号;**无写入点**(`logic/basic/apply_cash.go:39-47` 未设置) | +| `trade_no` | varchar(255) | 默认 `''` | 第三方流水号;无写入点 | +| `remark` | varchar(255) | 默认 `''` | 备注 | +| `amount` | int64 | 默认 0 | 提现金额,单位:分 | +| `channel` | int8 | 默认 0 | 1 微信 / 2 支付宝 / 3 银行卡 | +| `status` | int8 | 默认 0 | -1 失败 / 0 待处理 / 1 成功 | +| `call_back_msg` | varchar(255) | 默认 `''` | 提现回调信息 | + +### 表 `wallet_bank`(`internal/models/wallet_bank.go:16-29`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 银行卡记录唯一码 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 归属人 | +| `wallet_identity` | varchar(36) | not null | 钱包唯一码 | +| `card_no` | varchar(255) | not null,**明文、无唯一约束** | 银行卡号 | +| `bank_name` / `bank` | varchar(255)/(64) | not null | 银行名 / 所属行 | +| `card_owner` | varchar(255) | not null | 持卡人姓名 | +| `id_card` | varchar(255) | not null,**明文** | 持卡人身份证号 | +| `phone` | varchar(20) | not null | 银行预留手机号 | +| `bind_id` | varchar(20) | 默认 `''` | 绑卡返回的 bind_id(无写入点) | +| `bank_type` | varchar(20) | 默认 `''` | DC 储蓄卡 / CC 信用卡 | + +### 表 `wallet_refund`(`internal/models/wallet_refund.go:16-25`) + +| 字段 | 类型 | 键/约束 | 说明 | +| --- | --- | --- | --- | +| `identity` | varchar(36) | uniqueIndex | 退款单唯一码 | +| `passport_id` / `passport_identity` | uint / varchar(36) | index | 用户 | +| `wallet_identity` | varchar(36) | not null | 钱包唯一码 | +| `order_identity` | varchar(64) | not null | 订单唯一码 | +| `reason` / `order_info` / `result` | text | 默认 `''` | 退款理由 / 订单信息 / 处理结果(均无写入点) | + +> 该表只在迁移注册中被引用,**没有任何业务代码使用**(退款链路未实现)。 + +### 直接访问的外部表 + +| 表 | 位置 | 用途 | +| --- | --- | --- | +| `order_summary` | `internal/models/query.go:104` | 按 `order_no` 读取 `trans_price` 作为订单支付金额 | +| `passport_account` | `internal/models/query.go:24` | `InitData` 中按硬编码 `account='demo'` 查默认用户 | + +## 5. 核心流程 + +### 5.1 创建支付单 → 渠道下单 → 回调 → 流水 + +```mermaid +flowchart TD + A["POST /wallet.Payment/ByCharge 或 ByOrder
service.ParseMetaCtx JWT"] --> B["GetWalletByPassportIdentity
不存在则懒创建钱包"] + B --> C{"in.PayChannel"} + C -->|"1 微信"| D["wechat.NewWechat 读私钥文件
GetResponse 统一下单"] + C -->|"2 支付宝"| E["alipay.NewAlipay
SetBody -> TradeWapPay 或 TradeAppPay"] + C -->|"3 钱包余额"| F["basic.NewWallet
校验状态 支付密码 余额"] + F --> G["TradeConsum 读到的余额减去金额后回写"] + D --> H["models.CreatePaymentRecord
写 wallet_payment status=0"] + E --> H + G --> H + H --> I["渠道异步通知"] + I --> J["wallet.Wechat/WxCallback
VerifySignByPKMap 验签 + 解密"] + J --> K["业务逻辑 TODO 原样返回 SUCCESS"] + I --> L["wallet.Payment/Callback
用户 JWT 直接指定成功或失败"] + L --> M["UpsetWalletPaymentByIdentity
只更新 status 与 callback_msg"] + K --> N["不落库 不改状态"] + M --> O["入账缺失"] + O --> P["ChargeWallet 无调用点
wallet_basic.balance 不增加"] + P --> Q["wallet_record 不写流水"] +``` + +### 5.2 提现申请 + +```mermaid +flowchart TD + A["POST /wallet.Basic/ApplyCash
service.ParseMetaCtx JWT"] --> B["校验 amount 与 channel 非 0"] + B --> C["GetWalletByPassportIdentity 读取余额"] + C --> D{"in.Amount 大于 myWallet.Balance"} + D -->|是| E["返回 ErrBalanceNotEnough"] + D -->|否| F["Create wallet_apply_cash status=0"] + F --> G["返回 OK"] + G -.-> H["不扣减 balance 不冻结 不出款"] + H -.-> I["不写 wallet_record 流水"] + I -.-> J["无幂等键 cash_no 未写
重复提交按次数叠加提现单"] +``` + +## 6. 审计发现 + +### 6.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/payment/callback.go:16-31` | **支付结果回调由客户端自证**。接口只做 JWT 解析,随后用调用方给的 `in.CallbackStatus` 直接决定 `status = 2`(成功)或 `-1`(失败),只用 `auth.Identity` 作为 `passport_identity` 过滤条件定位支付单。没有渠道验签、没有金额核对、没有 `trade_no` 校验、没有状态机约束、没有幂等。任何持有有效 JWT 且知道(或猜到)自己支付单 `identity` 的调用方,都能把自己的待支付单标记为"支付成功"。 | +| **高** | `etc/wallet_test.yaml:33` + `internal/logic/payment/callback.go:16` | `wallet.payment.Callback` 被列入匿名清单(`etc/wallet_dev.yaml:40` 同类),而实现要求 JWT(`meta.go:21-29`)。渠道服务端回调不可能携带用户 JWT,因此该接口作为真实回调**必定失败**;若网关按匿名放行,则等于把"支付成功"开关完全开放。配置与实现互相矛盾,两条路都不可接受。 | +| **高** | `internal/logic/wechat/jsapi_pre_order.go:10-27` | `Wechat.JsapiPreOrder` **完全没有鉴权**(函数体内无 `ParseMetaCtx`),且 `amount` 由请求方直接给出并原样作为微信下单金额(`internal/logic/wechat/wechat.go:77-101` 的 `Set("total", amount)`)。任何人都可匿名发起任意金额的微信下单。 | +| **高** | `internal/logic/basic/set_pay_password.go:23-29` | 修改支付密码**不校验旧密码**、不校验密码强度与长度(只判 `in.Password == ""`),直接 `Update("pay_password", ...)`。持有任意有效 JWT 即可覆盖支付密码,进而配合 `PayChannel=3` 的钱包扣款(`logic/payment/by_order.go:139-146`)完成盗刷。 | +| **高** | `internal/logic/basic/wallet.go:67-69` | 支付密码使用**单轮 HMAC-SHA256**(`git.apinb.com/bsm-sdk/core/utils/crypto.go:19-25`),key 为 `passportIdentity + config.Spec.Wallet.PublicKey`,结果以十六进制串明文存库(`wallet_basic.pay_password`)。无迭代、无随机盐、无失败次数限制;6 位数字密码(示例 `test/basic.http:22` 即 `123456`)离线穷举仅 10^6 次 HMAC。 | +| **高** | `etc/wallet_prod.yaml:56`、`etc/wallet_dev.yaml:73` | 微信支付凭据**明文硬编码并提交入库**:`APIV3Key: MakeSupplyChainFinance1123581321`、`MchID: 1670561983`、`SerialNo: 209F629E63E56D2366361969B51FBA0ADCF69F55`、`AppID: wx9a2f163207219776`。APIv3 密钥是微信支付回调报文解密密钥,泄露即可解密全部交易通知。 | +| **高** | `internal/logic/basic/apply_cash.go:19-53` | 提现申请不要求支付密码,也不校验提现渠道是否已绑定对应账户(`Channel` 只判非 0),任意 JWT 即可为任意渠道生成提现单。 | +| 中 | `internal/logic/basic/bind_payment_id.go:23-36` | `PayType` 只判 `== 0`,传 3 等非法值时 `filed` 保持空串,最终执行 `Update("", in.AuthCode)`(目标列名非法);同时把前端传入的 `auth_code` **直接当作 openid 落库**,没有走 `code2session` 换取并校验 openid,可绑定他人账号标识。 | +| 中 | `internal/logic/basic/add_bank_card.go:34-47`、`internal/logic/basic/get_bank_card.go:37-47` | 银行卡号与身份证号明文存储、并以 `BankNumber`/`IdCard` 原样回显;`add_bank_card.go:36` 的 `Where("card_no = ?", in.CardNo)` **未限定 passport**,用于给任意卡号填充 `bank_name`/`bank`/`bank_type`,可据此探测某卡号是否已被其他用户绑定(存在性侧信道)。 | +| 中 | `internal/logic/wechat/wechat.go:68`、`internal/logic/alipay/alipay.go:27` | 生产代码中开启 gopay 调试开关 `client.DebugSwitch = gopay.DebugOn`,会把渠道请求/响应(含金额、out_trade_no、openid)写入进程日志。 | + +### 6.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `internal/logic/payment/by_order.go:37-49`、`internal/logic/payment/by_charge.go:30-44` | **空指针 panic**。`myWallet.Identity` 在 `wErr` 判空之前被求值(付款记录结构体字面量里直接引用),而 `GetWalletByPassportIdentity` 在非 `ErrRecordNotFound` 的错误上返回 `(nil, err)`(`internal/models/query.go:88`)→ 查询一失败就在构造结构体时崩溃。 | +| **高** | `internal/logic/basic/wallet.go:53-63` | **余额扣减为读后写,且无事务/无行锁/无余额条件**:`"balance": srv.Body.Balance - amount` 用的是 `NewWallet` 时刻读到的旧值,UPDATE 语句只有 `WHERE identity = ?`(`wallet.go:58`)。并发两笔消费都能通过 `wallet.go:42` 的余额校验,最终写入同一个余额值 → 双花/丢更新。`withdrawal_balance` 同样被硬减,可能变为负数。 | +| **高** | `internal/logic/basic/apply_cash.go:35-49` | **提现只校验不扣减**。校验 `in.Amount > myWallet.Balance` 后仅插入一条 `wallet_apply_cash`,既不扣 `balance` 也不冻结,且 `cash_no` 留空、无幂等键、无重复提交防护。余额 100 元可连续提交 N 张 100 元提现单。 | +| **高** | `internal/models/query.go:135-165` | `ChargeWallet` 是**唯一的钱包入账函数**,但全仓库无调用点(仅有定义)。其内部还叠加三个缺陷:余额加法为读后写(`query.go:143`,与 6.2 第 2 条同型);余额更新与流水写入不在同一事务(`query.go:146` / `query.go:161`);`CreateTradeRecord` 的返回值被直接丢弃(`query.go:161`)。 | +| **高** | 全链路 | **充值/支付回调不会改变余额**(对应 6.3)。`ByCharge` 只写 `wallet_payment`(`by_charge.go:129`),`WxCallback` 不落库(`wx_callback.go:38`),`Payment.Callback` 只改 `status`/`callback_msg`(`internal/models/query.go:119-122`),`ChargeWallet` 无人调用 → `wallet_basic.balance` 永不增加。 | +| **高** | `internal/logic/payment/by_charge.go:26-28` | 金额只判 `in.Amount == 0`,**负数金额可以通过**(如 -100),也没有最小/最大金额限制。 | +| 中 | `internal/logic/payment/callback.go:25-31` | 无状态机约束:任意状态可直接置为 `2` 或 `-1`,无时间窗、无幂等(重复回调只是覆盖 `callback_msg`);`wallet_payment.trade_no` 字段全仓库无写入点,因此回调后也拿不到渠道流水号,无法对账。 | +| 中 | `internal/models/query.go:168-197` | `FindWalletRecords` 中 4 处 `tx.Where(...)` 未回写(`query.go:174`、`177`、`182`、`185`),时间范围与收支/交易类型过滤**实际不生效**;返回的 `total` 用的是 `res.RowsAffected`(`query.go:196`),即当页条数而非总条数,却被 `logic/basic/transactions.go:33` 直接作为 `Total` 返回。 | +| 中 | `internal/logic/basic/transactions.go:21-26` | `if in.PageSize <= 1 { in.PageSize = 20 }`:`page_size = 1` 被强制放大为 20;且 `page_size` 无上限,配合 `query.go:189` 的 `Limit` 可一次拉取全表流水。 | +| 中 | `internal/logic/basic/get_wallet.go:36-73` | 六个统计查询共用同一个 `totalMoney` 变量,每次 `Scan` 前不清零,且全部忽略 `.Error`。任一次 `sum()` 返回 NULL 或查询失败时,会把上一次的统计结果当作本次结果返回(例如"当日支出"显示成"当日收入"的金额)。 | +| 中 | `internal/logic/basic/get_wallet.go:41,47,53,59` | 当日/当月统计按 `ymd`/`ym` 过滤,但唯一的流水写入点(`internal/models/query.go:151-158`)**从不写 `Ymd`/`Ym`**(落库为 0)→ 当日/当月收支统计恒为 0。 | +| 中 | `internal/logic/alipay/alipay.go:46-49` + `alipay.go:52-60` | **必然 panic**。`NewAlipay` 返回的 `AliPay.Body` 为 nil(构造时只赋 `Client`),而 `SetBody` 第一句就是 `srv.Body.Set(...)`(`alipay.go:53`)。go-pay v1.5.123 的 `BodyMap.Set` 是值接收者(`gopay/body_map.go:32`),经 nil 指针调用会解引用空指针;已在同版本 gopay 上最小复现(`panic: runtime error: invalid memory address or nil pointer dereference`)。`ByOrder`/`ByCharge` 的 `PayChannel == 2` 分支必然触发(`by_order.go:118`、`by_charge.go:102`)。 | +| 中 | `internal/logic/alipay/alipay.go:23-24,48` | `ctx` 字段被注释掉、**从未赋值**,`TradeWapPay`/`TradeAppPay` 拿到 nil context(`alipay.go:63`、`alipay.go:68`)→ 即便修好 `Body`,渠道调用仍会以 `nil Context` 失败。 | +| 中 | `internal/logic/alipay/alipay.go:55-57` | 无论 WAP 还是 APP 都写死 `product_code = QUICK_WAP_WAY`(APP 应为 `QUICK_MSECURITY_PAY`);`total_amount` 用 `amount/100` 整数除法,非整元金额被截断(10050 分 → 100 元),且未按支付宝要求传两位小数字符串。 | +| 中 | `internal/logic/wechat/jsapi_pre_order.go:22`、`internal/logic/wechat/wx_callback.go:26` | `wx, _ := NewWechat()` 丢弃错误。`NewWechat` 在读不到私钥文件时返回 `(nil, err)`(`wechat.go:48-51`),下一行访问 `wx.Client` 即 panic。 | +| 中 | `internal/logic/wechat/wechat.go:77-102`、`146-173`、`internal/logic/payment/by_order.go:95` | `GetResponse` 与 `NativePaySign` 的语义与用途不符:`GetResponse` 内部固定调用 `V3TransactionJsapi`(`wechat.go:96`),`NativePaySign` 实际调用 `V3TransactionNative`(`wechat.go:164`)却接收一个 `expire` 参数;`by_order.go:95` 调用时该位置传空串,导致 `Set("time_expire", "")` 被发给微信。 | +| 中 | `internal/logic/payment/by_order.go:152-155`、`by_charge.go:129-132` | 先向渠道下单、再写本地支付记录。写库失败时渠道侧订单已生成(孤儿单),且无幂等键,重复调用会为同一 `order_no` 生成多条 `wallet_payment`(`order_no` 无唯一索引)。 | +| 中 | `internal/models/query.go:76-92` | 钱包懒创建:`impl.DBService.Create(wallet)` 的错误被忽略(`query.go:85`),失败时仍返回一个 Identity 有值但库中不存在的钱包,后续所有 `UpdateColumns` 静默影响 0 行;且 `passport_identity` 无唯一约束,并发下可为同一用户创建多条钱包,余额分裂。 | +| 中 | `internal/logic/basic/bind_payment_id.go:27-36` | 变量名 `filed`(应为 field)拼写错误;`PayType` 不在 {1,2} 时目标列为空串(见 6.1)。 | +| 低 | `internal/models/query.go:102-108` | `GetTransPriceByOrderNo` 忽略 `Scan` 的 `.Error`,以 `amount == 0` 代替错误判断,"0 元订单"与"订单不存在"无法区分;同时直接读 `order_summary`(跨模块直连表)。 | +| 低 | `internal/models/query.go:119` | 函数名 `UpsetWalletPaymentByIdentity` 拼写错误(应为 Upsert),且实现是 `Updates`,语义为"更新"而非"更新或插入"。 | + +### 6.3 未完成实现 + +| 类别 | 内容 | 证据 | +| --- | --- | --- | +| TODO 占位 RPC(7 个) | `Wechat.AppPreOrder`、`Wechat.NativePreOrder`、`Wechat.Transfer`、`Alipay.WapPay`、`Alipay.PagePay`、`Alipay.AppPay`、`Alipay.Transfer` | `internal/logic/wechat/{app_pre_order,native_pre_order,transfer}.go:18-22`、`internal/logic/alipay/{wap_pay,page_pay,app_pay,transfer}.go:18-22` | +| 回调业务逻辑未落地 | `WxCallback` 验签+解密后 `// Todo: 业务逻辑`,直接返回 `SUCCESS`;不入库、不改支付状态、不入账 | `internal/logic/wechat/wx_callback.go:38-40` | +| 入账链路断裂 | `models.ChargeWallet`(唯一加余额的函数)无任何调用点 | 定义 `internal/models/query.go:135`;全仓库引用仅此一处 | +| 退款链路未实现 | `WalletRefund` 模型只注册迁移,无任何业务代码引用 | `internal/models/wallet_refund.go:27-29` | +| 启动依赖 Demo 数据 | `InitData` 按硬编码 `account='demo'` 查 `passport_account`,查不到即返回 error,而 `srv.Use` 会 panic(SDK `service/service.go:133-138`)→ 生产库中无 demo 账号时独立入口启动失败 | `internal/models/query.go:24-30`、`cmd/main/main.go:47` | +| 支付单不可回填渠道流水 | `wallet_payment.trade_no`、`wallet_apply_cash.cash_no`、`wallet_bank.bind_id`、`wallet_payment.args/remark` 均无写入点 | `internal/models/wallet_payment.go:22,27-28`、`internal/models/wallet_apply_cash.go:21-22`、`internal/models/wallet_bank.go:26` | +| 文档与实现不符 | README 声称存在 `Makefile`、`Dockerfile`、`swagger/`、`scripts/` 目录以及 `go run cmd/main/main.go migrate` 迁移子命令,实际目录中均不存在,`main.go:53-55` 也只有 `Run()` | `README.md:53,66,92-93,269` | + +### 6.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `etc/wallet_prod.yaml:1-4`、`etc/wallet_test.yaml:1-4` | 配置文件使用 `Name`/`ListenOn`/`Dsn`,而 `SrvConfig` 只识别 `Service`/`Port`/`Databases`(`internal/config/config.go:16-18`)。SDK 在解析前先检查文件是否含子串 `"Service:"`(`git.apinb.com/bsm-sdk/core/conf/new.go:58-60`),这两个文件都不含 → `log.Fatalln("ERROR: Service Not Nil")` 直接退出。三个配置中**只有 `wallet_dev.yaml` 能被加载**。 | +| **高** | `etc/wallet_dev.yaml`(无 `Wallet:`/`Alipay:` 段)+ `internal/config/config.go:25-27` | `config.Spec.Wallet`、`config.Spec.Alipay` 保持 nil,而 `internal/logic/payment/way.go:22,29,36` 与 `internal/logic/alipay/alipay.go:53,59` 直接解引用 → 扣款方式列表与支付宝下单均 panic。 | +| **高** | `internal/impl/impl.go:24`、`internal/config/config.go:83` | `with.Databases(config.Spec.Databases, nil)`,但 `conf.NotNil` 只校验 `Service`/`Cache`,**未校验 `Databases`**;`Databases` 缺失时 SDK 直接 `panic("No Database Source Found !")`(`git.apinb.com/bsm-sdk/core/with/databases.go:13-15`)。 | +| 中 | `internal/server/new.go:16-30`、`cmd/main/main.go:39` | `Server.Mux` 声明后从未赋值,独立入口把 nil 传给 `GatewayMux`;而 grpc-gateway 的 `RegisterXxxHandlerServer` 只在 `service/expose.go:30-41` 调用(仅聚合入口触发)→ 独立进程启动 HTTP 网关时 handler 为 nil(落到 `DefaultServeMux`),所有 REST 路径 404。 | +| 中 | `internal/impl/impl.go:22-23,25`、`service/dependencies.go:29-31` | `RedisService`、`MemorySerice` 被初始化/注入,但全模块无任何使用点(死代码);`EtcdService` 仅在微服务注册时被 SDK 使用。 | +| 中 | `internal/logic/wechat/wechat.go:21-29,41-74` | `mchID`、`SerialNumber`、`mchAPIv3Key`、`ApiClientKey`、`AppId` 等为**包级可变变量**,每次 `NewWechat` 都被重写且无同步;并发下单时存在数据竞争(`go test -race` 会报)。 | +| 低 | `internal/excode/ex.go:10-16` | 定义的 `ErrPassportId`/`ErrAuthCode`/`ErrOrderNo`/`ErrAmount`/`ErrIdentityArgument`/`ErrPasswordArgument` 中,`ErrVerifySign`(签名验证失败)全模块无使用点——与"回调不验签"的实际实现一致。 | +| 低 | `internal/logic/basic/get_wallet.go:74` | 残留注释 `// remove useless fmt.Sprint side-effect-free call`。 | +| 低 | 全模块 | 0 个 `*_test.go`;`test/` 下仅有 5 个 `.http` 手测样例与一行说明的 `readme.md`。 | +| 低 | `README.md:444-491` | README 中的建表 DDL 与真实模型不一致:`wallet_payment` DDL 缺 `wallet_identity`/`trade_no`/`type`/`pay_channel`/`args`/`remark`;`wallet_record` DDL 缺 `fee`/`pay_type`/`ymd`/`ym`/`remark`/`passport_id`。 | + +## 7. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| W1 | 高 | `Payment.Callback` 凭用户 JWT 即可把支付单置为成功(无验签/无金额核对/无幂等) | 资金安全、订单履约 | +| W2 | 高 | 支付回调匿名清单与 JWT 实现矛盾,真实渠道回调不可用 | 支付链路可用性 | +| W3 | 高 | 充值入账链路断裂:`ChargeWallet` 无调用点,回调不增加余额 | 核心功能不可用 | +| W4 | 高 | 钱包余额扣减为读后写、无事务/条件更新 → 并发双花 | 资金安全 | +| W5 | 高 | 提现只校验不扣减、无幂等键 → 重复提交超额提现 | 资金安全 | +| W6 | 高 | 支付密码可被任意 JWT 直接覆盖;密码仅单轮 HMAC-SHA256 | 账户与资金安全 | +| W7 | 高 | 支付宝分支 `Body` 为 nil → `PayChannel=2` 必然 panic | 接口可用性 | +| W8 | 高 | `ByOrder`/`ByCharge` 在 `wErr` 判空前解引用 `myWallet.Identity` → 空指针 panic | 接口可用性 | +| W9 | 高 | `JsapiPreOrder` 无鉴权且金额由请求方指定 | 资金安全、接口滥用 | +| W10 | 高 | 微信 APIv3 密钥、商户号明文硬编码入库 | 渠道凭据泄露 | +| W11 | 高 | 余额/提现/消费全链路不写 `wallet_record` 流水 | 对账与可追溯性 | +| W12 | 高 | 生产与测试配置字段名不匹配,服务无法启动(独立入口) | 部署可用性 | +| W13 | 高 | `wallet_dev.yaml` 缺 `Wallet`/`Alipay` 段 → `Way` 等方法 nil 解引用 | 运行期崩溃 | +| W14 | 高 | `Databases` 未做非空校验,缺失即 panic | 启动崩溃 | +| W15 | 中 | 交易流水查询过滤失效、`Total` 语义错误、分页无上限 | 功能不正确 + 全表拉取 | +| W16 | 中 | `GetWallet` 六个统计共用变量且忽略错误;`ymd`/`ym` 从未写入 → 统计结果错误或恒为 0 | 数据展示错误 | +| W17 | 中 | 7 个 RPC 为 TODO 占位,返回 `(nil, nil)` | 功能缺失、网关异常 | +| W18 | 中 | 银行卡号/身份证明文存储与回显;按卡号跨用户探测 | 隐私与合规 | +| W19 | 中 | 独立入口 HTTP 网关 `Mux` 为 nil(handler 从未注册) | 接口不可用 | +| W20 | 中 | 渠道 client 包级变量并发写、`NewWechat` 错误被丢弃 | 并发崩溃 | +| W21 | 低 | 死代码(Redis/Memory 未用、`ErrVerifySign` 未用)、无测试、README 与实现不符 | 可维护性 | + +## 8. 修复建议(务实项) + +1. **回调改为渠道验签入口**(W1/W2/W3):`Payment.Callback` 不应接受客户端提交的 `CallbackStatus`。把 `status` 的来源改为渠道回调(微信走 `WxCallback` 的验签+解密结果,支付宝走其异步通知验签),并在回调内完成:按 `out_trade_no` 反查支付单 → 用渠道返回的金额与 `wallet_payment.amount` 比对 → 校验当前 `status` 后置为 `2`(幂等:`WHERE status IN (0,1)`)→ 在同一事务内调用 `ChargeWallet` 入账并写 `wallet_record`(补上 `Ymd`/`Ym`)。`Payment.Callback` 至多保留为内部/管理端接口,并从匿名清单中移除。 +2. **钱包扣减改成条件更新**(W4/W5):`TradeConsum` 用一条语句完成 `UPDATE wallet_basic SET balance = balance - ?, withdrawal_balance = withdrawal_balance - ? WHERE identity = ? AND balance >= ? AND status <> -1`,按 `RowsAffected == 0` 判定余额不足;`ApplyCash` 在同一事务内先扣减/冻结余额再插提现单,并给 `wallet_apply_cash` 加上请求幂等键(写 `cash_no`,加唯一索引)。 +3. **支付密码**(W6):`SetPayPassword` 增加旧密码校验分支(首次设置与修改分开);把 `EncodePassword` 换成 SDK 或 `golang.org/x/crypto/bcrypt` 的慢哈希,并在钱包表增加失败次数/锁定字段。密码长度做最小校验(当前示例数据用的 `123456` 应被拒)。 +4. **补齐 `wallet_record` 流水**(W11):`TradeConsum`(消费)、`ApplyCash`(提现)、`ChargeWallet`(充值)三条路径都必须写流水,并把流水写入与余额变更放进同一事务;`TradeConsum` 目前只改余额不写流水。 +5. **修 panic 点**(W7/W8/W13):把 `models.WalletPayment{...}` 的构造移到 `wErr` 判断之后;`NewAlipay` 初始化 `Body`(`Body: &gopay.BodyMap{}`)并给 `ctx` 赋 `context.Background()`;对 `config.Spec.Wallet`/`Alipay`/`WeChat` 在 `config.New` 中做非空校验(与 `conf.NotNil` 一致的处理),或在 logic 入口显式判空返回错误。 +6. **微信下单**(W9/W20):`JsapiPreOrder` 补 `service.ParseMetaCtx`;金额改由服务端按业务单据决定,不接受请求方直接指定;`NewWechat` 的返回错误不得丢弃;把渠道密钥从包级变量改为 `WeChatPay` 结构体字段。 +7. **配置**(W12/W14):`wallet_prod.yaml`、`wallet_test.yaml` 按 `SrvConfig` 的字段名重写(`Service`/`Port`/`Databases`/`Cache`),补上 `Wallet`、`Alipay`、`MicroService`、`Gateway` 段;`config.New` 里补 `conf.NotNil(Spec.Databases)`;把 `APIV3Key`/`MchID`/`PrivateKey` 改为从环境变量注入,并立即轮换已泄露的 APIv3 密钥。 +8. **查询与统计**(W15/W16):`FindWalletRecords` 的 4 处 `tx.Where` 回写 `tx`,`total` 用独立的 `Count` 查询;`transactions` 给 `page_size` 设上限;`GetWallet` 的 6 个统计各自使用独立变量并在 `Scan` 前清零、检查 `.Error`;`CreateTradeRecord` 补写 `Ymd`/`Ym`。 +9. **敏感数据**(W18):`wallet_bank.card_no`/`id_card` 改为加密存储(或至少仅存掩码 + 密文),接口回显掩码;`add_bank_card.go:36` 的卡号查询加 `passport_identity` 条件,去掉存在性侧信道。 +10. **占位与死代码**(W17/W19/W21):7 个 TODO RPC 在实现前应显式返回 `Unimplemented` 错误而不是 `nil, nil`(避免网关侧空响应);`internal/server/new.go` 中给 `Mux` 赋 `gwRuntime.NewServeMux()` 并在独立入口注册 handler,或直接在独立入口改走 `service.Expose`;删除 `internal/impl` 中未使用的 Redis/Memory 初始化与 `ErrVerifySign`(若已按第 1 条接入验签则保留)。 +11. **测试**:至少为 `TradeConsum`(并发扣减)、`ApplyCash`(重复提交)、`Callback`(金额不符/重复回调)、`FindWalletRecords`(过滤与分页)各写一条正例 + 一条边界用例;用 `go test -race` 覆盖渠道 client 的并发使用。 + +> 本报告只列出与现有实现直接相关的修复项。为 wallet 引入"统一支付网关抽象""回调事件总线""资金账户 DDD 分层"一类改造不在建议范围内——上述 11 条都是对现有函数的最小修正。 diff --git a/docs/workspace-and-scripts.md b/docs/workspace-and-scripts.md new file mode 100644 index 0000000..bcccc64 --- /dev/null +++ b/docs/workspace-and-scripts.md @@ -0,0 +1,179 @@ +# 工作区与工程脚本代码审计报告 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | 工作区定义 `go.work`、`go.work.sum`;工程脚本 `scripts/**`(`verify-workspace.{sh,ps1}`、`build-all-linux.sh`、`generate-protobuf.sh`、`update-all.sh`、`api-docgen/**`) | +| 所属域 | 工程与构建基础设施(非业务代码) | +| 审计日期 | 2026-09-22 | +| 代码规模 | Shell 4 个 / 251 行;PowerShell 2 个 / 44 行(`verify-workspace.ps1` 34 + `api-docgen/generate.ps1` 10);`api-docgen/main.go` 300 行;`go.work` 26 行;`go.work.sum` 1205 行;全仓库 `go.sum` 21 个 | +| 工作区构成 | `go.work` 的 `use` 20 项 = `module/` 下 18 个模块 + `pkgs/all` + `pkgs/ecmall`(实测 `go list -m` 恰好返回这 20 个);`scripts/api-docgen` **不在工作区内** | +| 结论摘要 | 脚本整体遵循"先发现、再执行"的写法并普遍使用 `set -Eeuo pipefail`,`update-all.sh` 还做了模块空集合校验;但存在**三类结构性问题**:① `go.work` 与全部 20 个 `go.mod` 把 SDK 替换到仓库外的相对路径 `../../bsm-sdk/core`,两个仓库必须以固定名字并列检出,否则整个工作区无法解析;② `scripts/api-docgen` 是工作区外 module,**当前检出下直接构建失败**(`updates to go.mod needed`),依赖版本也落后于工作区(protobuf 1.36.11 vs 1.36.12 等),而 `update-all.sh`/`verify-workspace.*` 都不覆盖它;③ 校验脚本的 gofmt 门禁只覆盖 `pkgs module`(漏 `scripts/`)、且对"模块列表为空"不报错,构建脚本只覆盖 `module/`(聚合入口既无构建也无生产配置)。此外有若干 GNU 专属语法与 README 的 Linux/macOS 双平台声明不符,以及两处无确认的破坏性删除/批量改写。 | + +## 1. 定位与职责 + +本对象是整个仓库的**工程基础设施层**,不含业务逻辑: + +- `go.work` / `go.work.sum`:把 18 个业务模块与 2 个聚合入口组织成一个 Go workspace,并把私有 SDK `git.apinb.com/bsm-sdk/core` 指向工作区外的本地目录。 +- `scripts/verify-workspace.{sh,ps1}`:提交前的格式化与测试门禁(gofmt → 逐模块 `go vet ./...` → `go test ./...`)。 +- `scripts/build-all-linux.sh`:交叉编译 Linux/amd64 静态二进制并收集产物与生产配置。 +- `scripts/generate-protobuf.sh`:按模块重跑 protoc 生成 `pb/` 代码(go / go-grpc / grpc-gateway / slc)。 +- `scripts/update-all.sh`:全工作区依赖升级 + `go mod tidy` + `go work sync`。 +- `scripts/api-docgen`:独立的文档生成 module,从 protobuf descriptor 渲染 `wiki/api/NN-*.md`。 + +## 2. 代码结构与关键文件 + +| 路径 | 行数 | 职责与关键点 | +| --- | --- | --- | +| `go.work` | 26 | `go 1.27.1`;`use` 20 个模块(`go.work:3-24`);`replace git.apinb.com/bsm-sdk/core => ../../bsm-sdk/core`(`go.work:26`);无 `toolchain` 指令 | +| `go.work.sum` | 1205 | 工作区级校验和;`bsm-sdk/core` 只有 `v0.2.0/go.mod` 一项(`go.work.sum:89`),而 20 个 `go.mod` 全部 require `v0.2.1` | +| `scripts/verify-workspace.sh` | 28 | bash:gofmt 检查 → `mapfile` 取模块 → 逐模块 vet/test;`SKIP_VET=1` 可跳过 vet | +| `scripts/verify-workspace.ps1` | 34 | PowerShell 等价实现(`-SkipVet`),校验 vet/test 的 `$LASTEXITCODE` | +| `scripts/build-all-linux.sh` | 105 | 校验 go/go.work → 导出 `GOOS/GOARCH/CGO_ENABLED` → 收集 `module/` 下所有 `go.mod` → 逐个 `go build -trimpath` → 收集 `{basename}_prod.yaml` | +| `scripts/generate-protobuf.sh` | 61 | 工具预检 → 逐模块清空 `pb/` 旧生成物 → protoc 生成 → 可选 `protoc-gen-slc` | +| `scripts/update-all.sh` | 57 | 模块列表空校验 → 逐模块 `go get -u ./...`(失败则用 `genproto@latest` 重试)+ `go mod tidy` + `go get genproto@latest` → `go work sync` | +| `scripts/api-docgen/go.mod` | 51 | module 名为 `bsm/full/tools/api-docgen`(与目录 `scripts/api-docgen` 不一致);15 个模块 replace 到 `../../module/...`;**无 `bsm-sdk/core` replace** | +| `scripts/api-docgen/generate.sh` | 7 | `GOWORK=off` + `GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn` + `go run .` | +| `scripts/api-docgen/generate.ps1` | 10 | 同上(PowerShell),结束后清理这两个环境变量 | +| `scripts/api-docgen/main.go` | 300 | 15 个模块的 `pb` 空导入 → `protoregistry.GlobalFiles` → 渲染服务/方法/消息/枚举 → 输出 `wiki/api/NN-*.md` | +| `.gitignore` | 37 | `/.builds/`(构建产物,`build-all-linux.sh` 的工作目录)已忽略 | + +实测基线(本机 `go1.27.1 windows/amd64`,仓库声明 `go 1.27.1`): + +``` +go test -mod=readonly ./pkgs/all/... → ok(config/server 两个包,service 包 no test files) +go test -mod=readonly ./pkgs/ecmall/... → ok(config/server/service 三个包) +gofmt -l pkgs module → module\base\fts\internal\routers\register_test.go +gofmt -l scripts → scripts\api-docgen\main.go +GOWORK=off go build ./... (scripts/api-docgen) → go: updates to go.mod needed; to update it: go mod tidy +command -v protoc protoc-gen-go protoc-gen-go-grpc protoc-gen-grpc-gateway protoc-gen-slc → 全部 MISSING +``` + +## 3. 工作区解析与脚本调用流程 + +```mermaid +flowchart TD + A["开发者 / 提交前"] --> B["go work sync(README:11,16)"] + B --> C["scripts/verify-workspace.sh | .ps1
gofmt -l pkgs module → go list -m → 逐模块 vet + test"] + C --> D["scripts/build-all-linux.sh
find module/**/go.mod → go build(GOOS=linux)"] + E["依赖升级"] --> F["scripts/update-all.sh
go list -m(20 模块)→ go get -u + tidy → go work sync"] + G["接口变更"] --> H["scripts/generate-protobuf.sh
find module/**/go.mod → protoc 重生成 pb/"] + H --> I["scripts/api-docgen/generate.sh(GOWORK=off + go run .)
读 GlobalFiles 渲染 wiki/api/NN-*.md"] + W["go.work: use 20 项 + replace bsm-sdk/core → ../../bsm-sdk/core"] + W -->|"解析模块图"| C + W -->|"解析模块图"| F + W -.->|"api-docgen 不在 use 列表,GOWORK=off 独立解析"| I + X["仓库外:../bsm-sdk/core 必须以固定名字并列存在"] --> W +``` + +流程要点: + +1. **工作区边界**:`go.work:3-24` 的 20 项 = `module/` 下 18 个 + `pkgs/all` + `pkgs/ecmall`。`module/ec/supply`(只有 README)、`module/finance/bill`、`module/finance/loan`(空目录)不在其中。实测 `go list -m -f '{{.Path}}'` 返回 20 个模块,与 `use` 列表完全一致。 +2. **SDK 替换路径**:`go.work:26` 的 `../../bsm-sdk/core` 相对 `go.work` 所在目录解析为 `/bsm-sdk/core`;18 个模块 `go.mod` 里的 `../../../../bsm-sdk/core` 相对各自模块目录解析到**同一位置**(本机为 `D:\work\bsm-sdk\core`,实测存在)。两侧一致,但都依赖"两个仓库以固定名字并列检出"这一前提。 +3. **`api-docgen` 在工作区外**:它不在 `go.work` 的 `use` 列表里,因此在工作区生效时(默认 `GOWORK`)对它执行 `go list ./...` 会直接报 `pattern ./...: directory prefix . does not contain modules listed in go.work or their selected dependencies`;它只能靠 `generate.sh`/`generate.ps1` 里的 `GOWORK=off` 单独构建。这也是它**不被 `go list -m` 覆盖**(即不被两个校验脚本与 `update-all.sh` 触及)的直接原因。 +4. **`api-docgen` 的输出位置依赖 CWD**:`filepath.Abs(filepath.Join("..", ".."))`(`main.go:57`)把"仓库根"定义为**当前工作目录的上两级**。从 `scripts/api-docgen` 运行时正好是仓库根;若从仓库根执行 `go run ./scripts/api-docgen`,输出会落到仓库的**上一级**目录(本机为 `D:\work\wiki\api`)。 + +## 4. 工作区与脚本的安全/正确性要点 + +| 项 | 现状 | 证据 | +| --- | --- | --- | +| SDK 依赖来源 | 全部 20 个 `go.mod` 都 require `git.apinb.com/bsm-sdk/core v0.2.1` 且被 replace 为本地路径 → 构建不校验该模块的 sum,克隆任一仓库单独都构建不了 | 各 `go.mod:replace` 段;`go.work:26` | +| `go.work.sum` 与 `go.sum` 一致性 | 21 个 `go.sum` 中**没有任何** `git.apinb.com/bsm-sdk/core` 条目(因被 replace,无需);`go.work.sum:89` 只残留 `v0.2.0/go.mod` 的哈希,与 require 的 `v0.2.1` 不一致 | `grep -rn "bsm-sdk" go.work.sum module/*/*/go.sum pkgs/*/go.sum` | +| `go.sum` 规模差异 | `scripts/api-docgen/go.sum` 仅 42 行,其余 20 个为 134-491 行 | `wc -l` 全量统计 | +| 版本漂移(实测) | `api-docgen/go.mod` 固定的版本落后于工作区模块:`protobuf v1.36.11`(工作区 1.36.12)、`grpc v1.83.0`(1.83.2)、`x/net v0.57.0`(0.59.0)、`x/sys v0.47.0`(0.48.0)、`x/text v0.40.0`(0.42.0)、`genproto/googleapis/{api,rpc} 20260807`(20260911) | 在 `-mod=mod` 下 `go list` 会把上述 6 行改写为工作区版本并给 `go.sum` 增补 14 行(本次审计已还原,未提交任何改动) | +| 构建是否可用(实测) | `GOWORK=off go build ./...` 在 `scripts/api-docgen` 报 `updates to go.mod needed; to update it: go mod tidy` —— Go 1.16 起默认 `-mod=readonly`,因此 `generate.sh` 的 `go run .` 在当前检出上会以该错误退出 | `generate.sh:5-7`;实测输出 | +| 工具链可用性(实测) | 本机 `protoc`、`protoc-gen-go`、`protoc-gen-go-grpc`、`protoc-gen-grpc-gateway`、`protoc-gen-slc` 全部缺失;脚本在缺工具时按设计 `exit 1` | `generate-protobuf.sh:8-18`;`command -v` 输出 | +| 破坏性操作 1 | `build-all-linux.sh` 在 `trap cleanup EXIT` 中无条件 `rm -rf -- "${ENTRY_ROOT}"`(`${WORKSPACE_ROOT}/.builds/.entries`,已被 `.gitignore:2` 忽略) | `build-all-linux.sh:9,40-43` | +| 破坏性操作 2 | `generate-protobuf.sh` 先 `mkdir -p`,再 `find ... -delete` 删除 `pb/` 下的 `*.pb.go` / `*_grpc.pb.go` / `*.pb.gw.go`,**随后**才跑 protoc;protoc 失败时 `pb/` 只剩手工文件,无备份/回滚(只能靠 git) | `generate-protobuf.sh:31-33,39-47` | +| 批量改写 | `update-all.sh` 对 20 个模块执行 `go get -u ./...` + `go mod tidy`,最后 `go work sync`(会回写各模块 `go.mod`),全程无确认,仅在结尾提示"审阅后再提交" | `update-all.sh:33-54` | +| 注释与代码顺序矛盾 | 注释称"`go mod tidy` 之后会移除该 requirement",但代码顺序是 `go mod tidy`(:44)→ `go get genproto@latest`(:48),其后没有 tidy → 该 requirement 会留在最终结果里 | `update-all.sh:44-48` | +| gofmt 门禁覆盖面 | 只检查 `pkgs module` 两个目录,`scripts/` 不在其中;实测 `gofmt -l scripts` 报 `scripts/api-docgen/main.go`(该文件工作区行尾为 mixed) | `verify-workspace.sh:10`、`verify-workspace.ps1:10`;`git ls-files --eol scripts/api-docgen/main.go` | +| 行尾符 | 无 `.gitattributes`;本机 `core.autocrlf=true`。实测 958 个已跟踪 `.go` 文件中有 1 个工作区为 CRLF 的文件(`module/base/fts/internal/routers/register_test.go`),`gofmt -l pkgs module` 会报它 → `verify-workspace.ps1:11-13` 会直接 throw | `ls -a`(无 `.gitattributes`)、`git ls-files --eol`、`gofmt -l pkgs module` | +| 模块空集合 | `update-all.sh` 有校验(`:21-24`);两个 `verify-workspace` 脚本**没有**,`go list -m` 失败时循环体为空、脚本仍以 0 退出 | `verify-workspace.sh:16-26`、`verify-workspace.ps1:15-30` vs `update-all.sh:20-24` | +| 未执行 `git diff --check` | README 的提交约定要求"运行 workspace 验证脚本和 `git diff --check`",但两个脚本都不包含该检查 | `README.md:317`;两个脚本全文 | +| 平台声明 | README 标注 `verify-workspace.sh` 支持 Linux/macOS,但脚本用了 `mapfile`(bash 4+,macOS 自带 bash 3.2 无此内建)、`find -print0 \| sort -z`(GNU sort)、`(( config_count += 1 ))` 等 GNU 专属写法 | `README.md:14-17`;`verify-workspace.sh:16`、`build-all-linux.sh:30`、`generate-protobuf.sh:35,59`、`update-all.sh:20` | +| CI 集成 | 仓库**没有任何 CI 配置**(无 `.github/`、`.gitlab-ci.yml`、`Makefile`,`git ls-files` 中的 `*.yml/*.yaml` 全是模块业务配置)→ 全部脚本均为手工执行,`wiki/api/*.md` 由人工运行 `api-docgen` 后提交 | `git ls-files \| grep -iE "gitlab\|github\|jenkins\|Makefile"` 无命中;`wiki/api/` 18 个文件已入库 | +| 文档集不同步点 | `api-docgen/main.go:38-54` 只登记 15 个模块(无 fts/logs/mgt),而 `wiki/api/` 实际有 18 个文档(含 `00-overview.md`、`16-fts-rest.md`、`17-logs-rest.md`、`18-mgt-rest.md`)→ 后 4 个是手工维护,重跑生成不会更新它们 | `main.go:38-54`;`ls wiki/api/` | +| 冲突检测被静音 | `generate.sh:6` / `generate.ps1:3` 设置 `GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn`,恰好屏蔽了 README 明令禁止的问题("protobuf 全限定名称必须跨模块唯一");api-docgen 一次性导入 15 个模块的 `pb` 包,是最可能触发注册冲突的场景 | `generate.sh:6`、`generate.ps1:3`、`main.go:12-26`、`README.md:316` | + +## 5. 审计发现 + +### 5.1 安全 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **中** | `go.work:26` + 20 个 `go.mod` 的 `replace` | **依赖来源是仓库外相对路径,构建可复现性依赖目录布局**。`git.apinb.com/bsm-sdk/core` 被替换为 `../../bsm-sdk/core`(`go.work` 相对上级目录)与 `../../../../bsm-sdk/core`(各模块相对自身);两处最终指向同一目录(本机实测 `D:\work\bsm-sdk\core` 存在,因此当前**不是**坏路径),但意味着:单独克隆 `bsm-infra/full` 无法构建;同级目录被改名或 SDK 换成 git 依赖后,`go.sum` 中**没有任何** `bsm-sdk/core` 哈希(21 个 `go.sum` 全无命中,只有 `go.work.sum:89` 的 `v0.2.0/go.mod` 残留),无法校验完整性。这是唯一不能靠"改配置项"消除的结构性前提,本次只记录事实与影响,不建议改造依赖方式。 | +| **中** | `scripts/update-all.sh:33-49` | **对 20 个模块执行无确认的批量依赖升级**。`go get -u ./...` 会把全部依赖升到最新 minor/patch,随后 `go mod tidy` 与 `go work sync` 回写每个模块的 `go.mod`/`go.sum`。脚本没有任何 `--dry-run`、差异预览或交互确认,只有一个结尾提示(`:57`)。在"依赖升级会同时影响 20 个模块 + 2 个聚合入口 + 1 个工具 module"的仓库里,这是一次高爆炸半径的写操作。 | +| **中** | `scripts/generate-protobuf.sh:31-33` | **先删后生成,无备份**。`find "${output_dir}" -maxdepth 1 -type f \( -name '*.pb.go' -o -name '*_grpc.pb.go' -o -name '*.pb.gw.go' \) -delete` 在 protoc 之前执行;`set -e` 下 protoc 中途失败会留下"生成物已删、新代码未生成"的中间态。手工文件(4 个模块的 `pb/blocks_compat.go`)因不匹配删除模式而幸存(已实测存在),但该删除模式的**白名单性质**也意味着新增的生成物命名(如 `*.pb.validate.go`)不会被清理,会与旧文件长期共存。 | +| **低** | `scripts/build-all-linux.sh:40-43` | `trap cleanup EXIT` + `rm -rf -- "${ENTRY_ROOT}"` 是对 `.builds/.entries` 的无条件递归删除。目标目录在 `.gitignore:2` 覆盖范围内(`/.builds/`),爆炸半径可控;但脚本对"`ENTRY_ROOT` 为空或指向异常"没有任何防御(例如变量被环境覆盖时)。 | +| **低** | `README.md:286` 与仓库实际内容 | README 声明"仓库内 YAML 仅为结构示例,凭据使用 CHANGE_ME",但 `module/base/logs/etc/logs_dev.yaml:7` 是明文达梦连接串(含口令与内网 IP `172.21.138.165:5236`)。该文件属模块配置、不在本次逐文件范围,但作为"工作区凭据卫生"的声明与实际不符,记录于此。 | + +### 5.2 正确性与逻辑缺陷 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **高** | `scripts/api-docgen/generate.sh:5-7`、`generate.ps1:2-5` | **该工具在当前检出下无法运行**。`GOWORK=off` 后 `api-docgen/go.mod` 需要更新(实测 `go: updates to go.mod needed`;Go 1.16 起默认 `-mod=readonly`,不会自动改写),因此 `go run .` 直接失败。要跑通必须先 `go mod tidy`(会改写 `scripts/api-docgen/go.mod`/`go.sum`,进而产生版本漂移),或把该 module 纳入工作区。 | +| **高** | `scripts/api-docgen/go.mod:21-31` | **版本与工作区长期漂移**。实测在 `-mod=mod` 下会被改写 6 行:`protobuf 1.36.11→1.36.12`、`grpc 1.83.0→1.83.2`、`x/net 0.57.0→0.59.0`、`x/sys 0.47.0→0.48.0`、`x/text 0.40.0→0.42.0`、`genproto/googleapis/{api,rpc} 20260807→20260911`。漂移之所以必然:`update-all.sh` 用 `go list -m`(`update-all.sh:20`)取模块,而 `api-docgen` 不在 `go.work`,永远不会被升级。 | +| **中** | `scripts/verify-workspace.sh:16-26`、`verify-workspace.ps1:15-30` | **"模块列表为空"被静默当成成功**。sh 版用进程替换 `< <(go list -m ...)`,`go list` 失败不会触发 `set -e`(进程替换的退出码不参与判断),`mapfile` 得到空数组后循环体不执行,脚本以 0 退出并打印 `Workspace verification completed successfully.`;ps1 版同样不检查 `go list` 的退出码。对比 `update-all.sh:21-24` 明确做了空集合校验,三份脚本行为不一致。 | +| **中** | `scripts/verify-workspace.sh:10`、`verify-workspace.ps1:10` | **gofmt 门禁不覆盖 `scripts/`**,而 `gofmt -l scripts` 实测会报 `scripts/api-docgen/main.go`(工作区行尾为 mixed)→ 同一仓库里"被门禁覆盖的文件都干净、没被覆盖的文件不干净",门禁的语义变得不可预期。 | +| **中** | 无 `.gitattributes` + `core.autocrlf=true`(本机) | **行尾符会让 gofmt 门禁在本机直接失败**。实测 958 个已跟踪 `.go` 文件中有 1 个工作区为 CRLF(`module/base/fts/internal/routers/register_test.go`,索引为 LF、工作区为 CRLF),`gofmt -l pkgs module` 会输出该文件 → `verify-workspace.ps1:11-13` throw、`verify-workspace.sh:11-14` 退出 1。仓库没有 `.gitattributes` 固化行尾,因此该结果取决于检出环境。 | +| **中** | `scripts/update-all.sh:44-48` | **注释与代码顺序矛盾**。注释称"`go mod tidy` 之后会在不需要时移除这个 requirement",但代码是 `go mod tidy`(:44)**之后**才 `go get google.golang.org/genproto@latest`(:48),且没有后续 tidy → 与注释描述的效果相反,最终 `go.mod` 会保留这条额外 requirement。 | +| **中** | `scripts/build-all-linux.sh:7,30` | **构建范围只有 `module/`**。`MODULE_ROOT="${WORKSPACE_ROOT}/module"`,模块用 `find "${MODULE_ROOT}"` 收集 → `pkgs/all` 与 `pkgs/ecmall` **永远不被构建**,`${module_name}_prod.yaml` 的收集循环(:95-102)同样不覆盖 `pkgs/*`。聚合入口既没有生产配置(两个 `pkgs/*/etc/` 只有 `default_dev.yaml`),也不在任何脚本的构建路径里。 | +| **中** | `scripts/build-all-linux.sh:55-57` | **`cmd/main.go` 分支是死代码**。实测 `module/*/*/cmd/main.go` 与 `module/*/*/*/cmd/main.go` 均不存在(全部入口都是 `cmd/main/main.go`),该分支不会被触发;同时"找不到入口就 `exit 1`"(:58-61)会让任何新增的"无独立入口"模块直接中断整条构建,而不是跳过。 | +| **低** | `scripts/build-all-linux.sh:68,73-74` | **CRLF 处理不对称**。读取 `module` 名后显式去掉 `\r`(`:74`,说明作者遇到过 CRLF),但同一段里从 `${entry_file}` 里 `awk` 出来的 `package_name`(`:68`)没有同样处理;若 awk 未按文本模式剥离 `\r`,`"main\r" != "main"` 会走错分支,去合成一个 import 不存在包的 wrapper。当前 Git Bash 的 awk 文本模式掩盖了该问题。 | +| **低** | `scripts/api-docgen/main.go:57` | **输出目录由 CWD 决定**:`filepath.Abs(filepath.Join("..", ".."))` 把仓库根定义为 CWD 的上两级。只有从 `scripts/api-docgen` 目录运行时才写到 `wiki/api/`;从仓库根执行会在仓库**上一级**创建 `wiki/api` 并写入 15 个文档,不报错、不提示。 | +| **低** | `scripts/api-docgen/main.go:38-54` 与 `wiki/api/` | **生成的 15 个文件与手工维护的 4 个文件混放**:`wiki/api/` 有 18 个文档,其中 `00-overview.md`、`16-fts-rest.md`、`17-logs-rest.md`、`18-mgt-rest.md` 不在 `modules` 表内(fts/logs/mgt 无 proto),重跑生成只覆盖 01-15,剩余 4 个永远靠人工更新,没有任何校验提示它们已过期。 | +| **低** | `scripts/update-all.sh:20-24` 与 `verify-workspace.*` | 三个脚本都以 `go list -m` 作为"模块发现"的唯一来源,而该命令的结果**随 `GOWORK` 是否生效而不同**:工作区生效时返回 20 个模块(不含 `api-docgen`),`GOWORK=off` 时只返回当前模块。脚本没有显式声明期望(`update-all.sh` 依赖 `go.work` 的存在检查 `:15-18`,两个 `verify-workspace` 则完全没有该检查)。 | + +### 5.3 未完成/不一致 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **中** | `scripts/api-docgen/go.mod:1` | **module 路径与目录不一致**:module 名是 `bsm/full/tools/api-docgen`,物理位置是 `scripts/api-docgen`,README 与相关文档也都写 `scripts/api-docgen`。包路径里不存在 `tools/` 目录,属命名遗留;`grep`/`go list` 之外的人工检索容易被误导。 | +| **中** | 全体脚本 | **没有任何 CI 接入**。仓库无 `.github/`、无 `.gitlab-ci.yml`、无 `Makefile`(`git ls-files` 校验),`README.md:290-299` 的"常用脚本"表全部靠人工执行;因此"门禁"实际只在使用者主动运行时生效,`wiki/api/*.md` 与 `go.mod` 漂移都只能靠人发现(本次审计的 `api-docgen` 构建失败正是这种情况)。 | +| **中** | `scripts/api-docgen/generate.sh:6`、`generate.ps1:3` | **静音了项目明令禁止的冲突**。`GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn` 会把 protobuf 全限定名重复注册的致命错误降级为警告,而 `README.md:316` 的开发约定是"protobuf 全限定名称必须跨模块唯一",`main.go:12-26` 又恰好一次性导入 15 个模块的 `pb` 包——这是最应该暴露该冲突的地方,却被显式关掉了。 | +| **低** | `README.md:282` | **文档把 SDK 相对路径描述为"准备开发环境时需保证该 SDK 路径存在",但没有给出目录布局要求**(必须以 `bsm-sdk/core` 与 `bsm-infra/full` 的名字并列)。`go.work:26` 与 20 处 `go.mod` replace 是硬约束,README 未量化。 | +| **低** | `go.work.sum:89` | 残留 `bsm-sdk/core v0.2.0/go.mod` 而 require 已是 `v0.2.1`:因该模块被 replace 为本地路径,构建不使用该 sum(因此**不会**造成构建失败),但说明 `go.work.sum` 未随 SDK 版本升级同步,读数会误导排查者。 | +| **低** | `scripts/generate-protobuf.sh:9-11` | `GENERATE_SLC` 默认 `1`,因此默认路径依赖 `protoc-gen-slc` 这个**仓库外、未随仓库提供**的插件;本机实测 5 个 proto 工具全部缺失,脚本会停在工具预检(行为正确但说明该脚本对环境的隐式要求很高:protoc + 4 个插件 + 正确的 `--proto_path`)。 | + +### 5.4 健壮性与可维护性 + +| 级别 | 位置 | 问题 | +| --- | --- | --- | +| **中** | `verify-workspace.sh:16,24`、`build-all-linux.sh:30`、`generate-protobuf.sh:35,59`、`update-all.sh:20` | **GNU 专属语法与 README 的双平台声明不符**:`mapfile`/`mapfile -d ''`(bash 4+)、`sort -z`、`find -print0 \| sort -z` 在 macOS 默认环境(bash 3.2 + BSD sort)不可用,而 README 明确把 `verify-workspace.sh` 标为"Linux/macOS"。Build/generate/update 三个脚本文件名未标平台,但同样受影响。 | +| **低** | `scripts/generate-protobuf.sh` 全文 | 不清空则重复生成的行为未定义;生成后没有 `gofmt`/`go build` 校验,也没有"生成物与 proto 是否一致"的检查,`pb/` 与 `proto/` 脱节只能等编译或运行期暴露。 | +| **低** | `scripts/build-all-linux.sh:95-102` | 生产配置收集只按 `{basename}_prod.yaml` 命名约定匹配(实测 18 个模块**全部**存在该文件,故当前收集数为 18);一旦某模块改了命名规范,脚本会**静默跳过**(`[[ -f ... ]] \|\| continue`),只靠最后的计数打印(`:104`)间接暴露。 | +| **低** | `scripts/update-all.sh:35-43` | `go get -u ./...` 失败后的重试逻辑只针对 genproto 一种失败原因(注释里说明了),但实现是"任何失败都重试一次 genproto 路径",网络/私有源类失败会被误判并重复执行一次全量 `go get -u`,时间成本翻倍且错误信息被覆盖。 | +| **低** | `scripts/verify-workspace.sh:23-24` | 逐模块 `go vet`/`go test` 各自在子 shell `( cd -- "${module_dir}" )` 中执行,失败后 `set -e` 会中断在**当前模块**,但不打印"已完成/未完成哪些模块"的汇总;20 个模块的遍历进度只能从 `==> ${module_dir}` 行推断。 | +| **低** | `scripts/api-docgen/main.go:296-300` | `must()` 用 `panic` 处理所有错误(含建目录、写文件失败);作为一次性工具可接受,但失败时只输出 panic 栈、不指明是哪个模块/哪个输出文件。 | +| **低** | 模块级 `go.sum` 与 `go.work.sum` 的维护 | `update-all.sh` 结束时执行 `go work sync`(`:54`)会同步工作区构建列表到各模块 `go.mod`,但没有类似的"校验所有 `go.sum` 是否齐备"的步骤;`api-docgen` 的 42 行 `go.sum` 就这样长期缺席于工作区的一致性维护之外。 | + +## 6. 风险汇总 + +| 编号 | 级别 | 问题 | 影响面 | +| --- | --- | --- | --- | +| W1 | 高 | `api-docgen` 在当前检出下无法构建(`updates to go.mod needed`),且不在工作区与任何脚本的覆盖范围内 | 文档生成能力失效(`wiki/api` 只能手工维护) | +| W2 | 高 | `api-docgen` 依赖版本与工作区长期漂移(protobuf/grpc/x-net/x-sys/x-text/genproto 共 6 处) | 生成结果与运行时 protobuf 版本不一致、排查成本 | +| W3 | 中 | `go.work` 与 20 处 `go.mod` 把 SDK 指向仓库外固定相对路径,且 `go.sum` 无该校验和 | 构建可复现性、独立克隆不可用 | +| W4 | 中 | `update-all.sh` 无确认地批量升级 20 个模块依赖并回写 `go.mod`/`go.sum` | 依赖升级的可控性 | +| W5 | 中 | `generate-protobuf.sh` 先删生成物再生成,失败无回滚 | 生成中断后的仓库中间态 | +| W6 | 中 | `verify-workspace.*` 对"模块列表为空"静默成功、gofmt 不覆盖 `scripts/`、无 `.gitattributes` 导致 CRLF 文件必然失败 | 门禁可信度 | +| W7 | 中 | 无任何 CI 接入,所有脚本与文档生成全靠人工 | 门禁的实际效力 | +| W8 | 中 | `GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn` 屏蔽了 README 明令禁止的跨模块注册冲突 | 跨模块 protobuf 命名约束失效 | +| W9 | 中 | `build-all-linux.sh` 只覆盖 `module/`,聚合入口无构建路径(叠加其无 prod 配置) | 聚合入口的发布路径 | +| W10 | 中 | GNU 专属语法与 README 的 macOS 支持声明不符 | 跨平台可用性 | +| W11 | 低 | `update-all.sh` 注释与代码顺序矛盾、genproto 重试逻辑过宽、构建脚本 `cmd/main.go` 死分支与 CRLF 不对称、`api-docgen` 输出目录依赖 CWD、`wiki/api` 生成物与手工文件混放、`go.work.sum` 残留旧版本 | 可维护性 | + +## 7. 修复建议(务实项) + +1. **让 `api-docgen` 能跑、且不再漂移**(W1、W2):二选一——① 把 `scripts/api-docgen` 加入 `go.work` 的 `use` 列表并删掉 `generate.sh`/`generate.ps1` 里的 `GOWORK=off`(这样它会被 `go list -m` 覆盖,自动纳入 `update-all.sh` 与 `verify-workspace.*`);或 ② 保持独立 module,但把这些版本对齐到工作区当前值并加上"先 `go mod tidy` 再 `go run .`"的显式步骤。推荐 ①,改动最小且一次性消除漂移。 +2. **修门禁的三个洞**(W6):`verify-workspace.sh|ps1` 的 gofmt 目标从 `pkgs module` 扩为 `pkgs module scripts`;在两个脚本的 `go list -m` 之后加"模块数为 0 则报错退出"(照抄 `update-all.sh:21-24`);补一个 `.gitattributes` 把 `*.go`、`*.sh`、`*.ps1`、`*.psm1` 固定为 `text eol=lf`,消除 CRLF 造成的 gofmt 假阳性。 +3. **给 `go.work` 的 SDK 路径加一道自检**(W3):在 `verify-workspace.*` 开头加一条"`go list -m git.apinb.com/bsm-sdk/core` 失败则提示目录布局要求并退出"的检查,把"路径不存在"从 20 个模块的编译错误变成一条明确提示;同时把 README:282 的布局要求写成具体路径(`../bsm-sdk/core` 与 `../../../../bsm-sdk/core` 两个基准目录)。 +4. **给批量依赖升级加一道确认**(W4):`update-all.sh` 增加 `REQUIRE_CONFIRM`/`--yes` 之类的开关,默认先打印将改写的 `go.mod` 列表再要求确认;不需要 dry-run 框架,一个 `read -r` 即可。同时修正 `:44-48` 的注释与代码顺序(把 `go get genproto@latest` 放到 `go mod tidy` 之前,或删掉注释里"tidy 会移除"的说法)。 +5. **protoc 生成改为"先生成到临时目录、成功后替换"**(W5):把 `--go_out` 等输出到一个临时目录,全部成功后整体替换 `pb/`;或者至少把 `find ... -delete` 移到 protoc 成功之后(当前顺序是删除在前)。顺带把删除模式从"后缀白名单"改为"仅保留非生成文件的显式白名单",避免新增生成物残留。 +6. **扩展构建范围**(W9):`build-all-linux.sh` 增加对 `pkgs/` 的扫描(聚合入口产出 `all`/`ecmall` 两个二进制并收集 `default_prod.yaml`);同时删掉不可能命中的 `cmd/main.go` 分支(`:55-57`),把"找不到入口"从 `exit 1` 改为可配置的跳过,并统一 `package_name` 与 `module_name` 的 `\r` 处理。 +7. **`api-docgen` 的 CWD 依赖与文档集**(W11):把 `main.go:57` 的根目录改为基于 `go list -m -f '{{.Dir}}'` 或可配置的输出目录参数(不引入新依赖,一次 `flag.String` 即可);在生成的目录里用文件头注释标注"本文件由 api-docgen 生成",把手工维护的 00/16/17/18 与生成物在文件名或注释上区分开。 +8. **恢复注册冲突检测**(W8):把 `GOLANG_PROTOBUF_REGISTRATION_CONFLICT=warn` 去掉(恢复默认的 fail-fast),让 `README.md:316` 的约定真正生效;若确有合法的重复注册,应在源头修正命名而不是全局降级。 +9. **跨平台兼容**(W10):要么把 README 的平台声明改为"Linux + Git Bash/WSL",要么把 `mapfile`/`sort -z` 替换为 POSIX 写法(`while IFS= read -r` + `find ... -print | sort`);后者改动集中在 4 个脚本、不超过十几行。 +10. **补最小 CI**(W7):即使不引入完整流水线,也可以在仓库内加一个"提交前执行 `scripts/verify-workspace.sh`"的钩子说明或最简 CI 任务(运行同一个脚本即可,无需新增脚本)。这一步只是让已有门禁真正生效,不新增框架或抽象层。 + +> 本报告只列与现有脚本/工作区定义直接相关的修复项,不引入新的构建系统、包管理抽象或 CI 框架。审计中"引入统一 Makefile/任务编排器""把脚本重写为 Go 工具""给工作区引入私有 module proxy"一类改造不在此列。 diff --git a/go.work b/go.work index ca184b1..96a6383 100644 --- a/go.work +++ b/go.work @@ -1,4 +1,4 @@ -go 1.26.5 +go 1.27.1 use ( ./module/base/ads diff --git a/go.work.sum b/go.work.sum index c4d9dae..bd8f029 100644 --- a/go.work.sum +++ b/go.work.sum @@ -37,6 +37,7 @@ cloud.google.com/go/bigquery v1.7.0/go.mod h1://okPTzCYNXSlb24MZs83e2Do+h+VXtc4g cloud.google.com/go/bigquery v1.8.0/go.mod h1:J5hqkt3O0uAFnINi6JXValWIb1v0goeZM77hZzJN/fQ= cloud.google.com/go/bigquery v1.79.0/go.mod h1:QTt5tgZxqqvZs3dOZKpvriGqy+CdvY9LyetirFZRPOE= cloud.google.com/go/bigquery v1.83.0/go.mod h1:cc0XscySNQNuHBxuZSg5yyxFsg/ZHAfViAG49gJbWew= +cloud.google.com/go/bigquery v1.84.0/go.mod h1:IIY7YrpcbEIhaA/n5geZN5oQb11nQ6RhhkYdcgmv9Vs= cloud.google.com/go/cloudtasks v1.19.0/go.mod h1:8q8wNubq0jFvXW5Pz8P3O7QWJBXOmfrY918FqTgIqHA= cloud.google.com/go/compute v1.6.1/go.mod h1:g85FgpzFvNULZ+S8AYq87axRKuf2Kh7deLqV/jJ3thU= cloud.google.com/go/compute v1.23.4/go.mod h1:/EJMj55asU6kAFnuZET8zqgwgJ9FvXWXOkkfQZa4ioI= @@ -70,6 +71,7 @@ cloud.google.com/go/securitycenter v1.46.0/go.mod h1:7mAlzsCsKlEVmciAFORl431laDG cloud.google.com/go/servicedirectory v1.17.0/go.mod h1:CtgjXS1idj3s9Q6tB68021Rzk8Q6decV6+ldXC1BoBk= cloud.google.com/go/spanner v1.94.0/go.mod h1:Z2+83J5oVDmd1n5ntVMmjEuiNoXOpAyNeG7y1tuEHk0= cloud.google.com/go/spanner v1.95.0/go.mod h1:Z2+83J5oVDmd1n5ntVMmjEuiNoXOpAyNeG7y1tuEHk0= +cloud.google.com/go/spanner v1.95.1/go.mod h1:Z2+83J5oVDmd1n5ntVMmjEuiNoXOpAyNeG7y1tuEHk0= cloud.google.com/go/storage v1.0.0/go.mod h1:IhtSnM/ZTZV8YYJWCY8RULGVqBDmpoyjwiyrjsg+URw= cloud.google.com/go/storage v1.5.0/go.mod h1:tpKbwo567HUNpVclU5sGELwQWBDZ8gh0ZeosJ0Rtdos= cloud.google.com/go/storage v1.6.0/go.mod h1:N7U0C8pVQ/+NIKOBQyamJIeKQKkZ+mxpohlUTyfDhBk= @@ -110,6 +112,7 @@ github.com/Djarvur/go-err113 v0.1.1/go.mod h1:IaWJdYFLg76t2ihfflPZnM1LIQszWOsFDh github.com/FishGoddess/cachego v0.6.1/go.mod h1:VLSMwWRlRPazjGYer8pq+4aDrIe1Otj3Yy9HAb0eo3c= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.31.0/go.mod h1:P4WPRUkOhJC13W//jWpyfJNDAIpvRbAUIYLX/4jtlE0= github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.33.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= +github.com/GoogleCloudPlatform/opentelemetry-operations-go/detectors/gcp v1.34.0/go.mod h1:pJTkW8hEUIIi3Pf65lPZOnn4Y81yCllX6IWk2jNXdkM= github.com/IBM/sarama v1.43.1/go.mod h1:GG5q1RURtDNPz8xxJs3mgX6Ytak8Z9eLhAkJPObe2xE= github.com/Joker/jade v1.1.3/go.mod h1:T+2WLyt7VH6Lp0TRxQrUYEs64nRc83wkMQrfeIQKduM= github.com/Masterminds/semver/v3 v3.5.0/go.mod h1:4V+yj/TJE1HU9XfppCwVMZq3I84lprf4nC11bSS5beM= @@ -182,6 +185,7 @@ github.com/butuzov/ireturn v0.4.1/go.mod h1:q+DXKzTDV5guNuXLnIab9fKXizTn2miZHLhx github.com/butuzov/mirror v1.3.0/go.mod h1:AEij0Z8YMALaq4yQj9CPPVYOyJQyiexpQEQgihajRfI= github.com/bytedance/gopkg v0.1.3/go.mod h1:576VvJ+eJgyCzdjS+c4+77QF3p7ubbtiKARP3TxducM= github.com/bytedance/sonic v1.15.0/go.mod h1:tFkWrPz0/CUCLEF4ri4UkHekCIcdnkqXw9VduqpJh0k= +github.com/bytedance/sonic v1.15.2/go.mod h1:mT2NbXunuaEbnZ+mRIX/vYqKISmgEuHFDI4UzmKx2SA= github.com/bytedance/sonic/loader v0.5.0/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/bytedance/sonic/loader v0.5.1/go.mod h1:AR4NYCk5DdzZizZ5djGqQ92eEhCCcdf5x77udYiSJRo= github.com/campoy/embedmd v1.0.0/go.mod h1:oxyr9RCiSXg0M3VJ3ks0UGfp98BpSSGr0kpiX3MzVl8= @@ -263,6 +267,7 @@ github.com/fzipp/gocyclo v0.6.0/go.mod h1:rXPyn8fnlpa0R2csP/31uerbiVBugk5whMdlya github.com/gabriel-vasile/mimetype v1.4.12/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/gabriel-vasile/mimetype v1.4.13/go.mod h1:d+9Oxyo1wTzWdyVUPMmXFvp4F9tea18J8ufA774AB3s= github.com/ghostiam/protogetter v0.3.20/go.mod h1:FjIu5Yfs6FT391m+Fjp3fbAYJ6rkL/J6ySpZBfnODuI= +github.com/gin-contrib/sse v1.1.1/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/globalsign/mgo v0.0.0-20181015135952-eeefdecb41b8/go.mod h1:xkRDCp4j0OGD1HRkm4kmhM+pmpv3AKq5SU7GMg4oO/Q= github.com/go-critic/go-critic v0.14.3/go.mod h1:xwntfW6SYAd7h1OqDzmN6hBX/JxsEKl5up/Y2bsxgVQ= github.com/go-gl/glfw v0.0.0-20190409004039-e6da0acd62b1/go.mod h1:vR7hzQXu2zJy9AVAgeJqvqgH9Q5CA+iKCZ2gyEVpxRU= @@ -281,24 +286,35 @@ github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbV github.com/go-logr/logr v1.4.3/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= github.com/go-openapi/analysis v0.25.5/go.mod h1:d3UGtQC5uq5Kqqqis2VH09Km/v3vwsWrYkbp4gdm+Rc= github.com/go-openapi/errors v0.22.8/go.mod h1:BuUoHcYrU6E7V9gfj1I5wLQqgtIHnup/alXZ8KdgQ0w= +github.com/go-openapi/jsonpointer v1.0.0/go.mod h1:Z3rw7dWu1p9IgitXCFamSlA5lmDiklEB6vkaxcNZW5Y= +github.com/go-openapi/jsonreference v1.0.0/go.mod h1:jtwdyGbJk0Xhe5Y+rwtglQP6Sb1WZST4rT32LWB+sv0= github.com/go-openapi/loads v0.25.0/go.mod h1:JFBw4SIB9+PTIFHDfcXuSSy5h6aWzjtUCrPYyx3qWU8= github.com/go-openapi/runtime v0.33.0/go.mod h1:+rsupH3+TFKqmFysqkmgBOTxpVJV8eV+j9myvvea2Xw= github.com/go-openapi/runtime/server-middleware v0.30.0/go.mod h1:OYNT/TxNvB/VK5oe4htM2jDTwlEXuejVJmu0DVZfAMs= +github.com/go-openapi/spec v0.22.9/go.mod h1:b/mNUYIOQOyIiUzUzXEE8xzyZqf93KvM9hQGP91yfl0= github.com/go-openapi/strfmt v0.27.0/go.mod h1:s/qhDqfY72irigXUGJmtgid2Rm+3tnz3k8hZaRmvWYc= github.com/go-openapi/swag v0.28.0/go.mod h1:4qYnT3Cqr1p1VknOdPo70evN4rgQnAg6jwApHyxSGIg= github.com/go-openapi/swag/cmdutils v0.28.0/go.mod h1:Sm1MVFMkF6guJJ+pQqHnQA3N0j9qALV3NxzDSv6bETM= github.com/go-openapi/swag/conv v0.27.3/go.mod h1:nPRmN6jgNme99hpf+nM0auDZGALWIqlwhisKPK/bQhQ= +github.com/go-openapi/swag/conv v0.28.0/go.mod h1:mbUE+mzctnhxi864m0Q07SpN8OowD9JhxmxuYvZZD/k= github.com/go-openapi/swag/fileutils v0.28.0/go.mod h1:VvJFZLTZS0AI854gEQz5tk7dBESdLjiNUMSZ/th2ry8= github.com/go-openapi/swag/jsonutils v0.27.3/go.mod h1:qiDCoQvzkMxrV3G8FLEdIU5L+EFYc0zcDOHWT3Yofvo= +github.com/go-openapi/swag/jsonutils v0.28.0/go.mod h1:CYM3WlTUcagR2ZoHdz54di/cbBqt82tuxuXgAjxw+mg= github.com/go-openapi/swag/loading v0.27.3/go.mod h1:rJ0NeaKsF4CVPnMGjPQl7JlSHzvD0bc2DKXLss1hiuE= +github.com/go-openapi/swag/loading v0.28.0/go.mod h1:rXB0QiQX5mMveXEA7ouM4KiiM9jVJe4K6BVbwhD1M4k= github.com/go-openapi/swag/mangling v0.28.0/go.mod h1:jtBE2+V+3pILxOR7Vgce+Cwp6A2PgZbvVqfNntbVs0w= github.com/go-openapi/swag/netutils v0.28.0/go.mod h1:J+WYyFMLtvtCGqa6jLv+YNUmIKI3ZRQRrvfNDMoQoEQ= github.com/go-openapi/swag/pools v0.27.3/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= +github.com/go-openapi/swag/pools v0.28.0/go.mod h1:kVQefhSK5RWuRe7BXsL8htgBPAMpN7HDGpGEknqugeE= github.com/go-openapi/swag/stringutils v0.27.3/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= +github.com/go-openapi/swag/stringutils v0.28.0/go.mod h1:lzRN95CxXmA03XcDWHLOb6nOMcxCqR5rGY0lOgsfRoM= github.com/go-openapi/swag/typeutils v0.27.3/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= +github.com/go-openapi/swag/typeutils v0.28.0/go.mod h1:Srm0xFNRZ1Y+vCxJclo5qzx8aj+1pAKda/YfFPrG0dQ= github.com/go-openapi/swag/yamlutils v0.27.3/go.mod h1:6JYBGj8sw/NawMllyZY+cTA8Mzk2etS3ZBASdcyPsiU= +github.com/go-openapi/swag/yamlutils v0.28.0/go.mod h1:x0q/yndZHEgk9Rx3DyDqzFUmHy55KTvIZldvF2dTJXs= github.com/go-openapi/validate v0.26.1/go.mod h1:B8UMgXiQiwwQWIbmuROlwJZDPGlikPuh7iHV1vPX9Oo= github.com/go-playground/validator/v10 v10.30.1/go.mod h1:oSuBIQzuJxL//3MelwSLD5hc2Tu889bF0Idm9Dg26cM= +github.com/go-playground/validator/v10 v10.30.3/go.mod h1:4Axh7oCNGcoGkqLoE4YWt6n20mcEIsPRlB7vPk3lpyc= github.com/go-quicktest/qt v1.101.0/go.mod h1:14Bz/f7NwaXPtdYEgzsx46kqSxVwTbzVZsDC26tQJow= github.com/go-sql-driver/mysql v1.8.1/go.mod h1:wEBSXgmK//2ZFJyE+qWnIsVGmvmEKlqwuVSjsCm7DZg= github.com/go-stack/stack v1.8.0/go.mod h1:v0f6uXyyMGvRgIKkXu+yp6POWl0qKG85gN/melR3HDY= @@ -398,6 +414,7 @@ github.com/google/shlex v0.0.0-20191202100458-e7afc7fbc510/go.mod h1:pupxD2MaaD3 github.com/googleapis/enterprise-certificate-proxy v0.3.3/go.mod h1:YKe7cfqYXjKGpGvmSg28/fFvhNzinZQm8DGnaburhGA= github.com/googleapis/enterprise-certificate-proxy v0.3.11/go.mod h1:RFV7MUdlb7AgEq2v7FmMCfeSMCllAzWxFgRdusoGks8= github.com/googleapis/enterprise-certificate-proxy v0.3.14/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= +github.com/googleapis/enterprise-certificate-proxy v0.3.15/go.mod h1:vqVt9yG9480NtzREnTlmGSBmFrA+bzb0yl0TxoBQXOg= github.com/googleapis/enterprise-certificate-proxy v0.3.17/go.mod h1:rSEsBUemEBZEexP2y6jPp16LUmUbjmSbcPMQizR0o4k= github.com/googleapis/gax-go/v2 v2.0.4/go.mod h1:0Wqv26UfaUD9n4G6kQubkQ+KchISgw+vpHVxEJEs9eg= github.com/googleapis/gax-go/v2 v2.0.5/go.mod h1:DWXyrwAJ9X0FpwwEdw+IPEYBICEFu5mhpdKc/us6bOk= @@ -455,6 +472,7 @@ github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLf github.com/invopop/jsonschema v0.13.0/go.mod h1:ffZ5Km5SWWRAIN6wbDXItl95euhFz2uON45H2qjYt+0= github.com/iris-contrib/schema v0.0.6/go.mod h1:iYszG0IOsuIsfzjymw1kMzTL8YQcCWlm65f3wX8J5iA= github.com/jackc/pgx/v5 v5.6.0/go.mod h1:DNZ/vlrUnhWCoFGxHAG8U2ljioxukquj7utPDgtQdTw= +github.com/jackc/pgx/v5 v5.10.0/go.mod h1:mal1tBGAFfLHvZzaYh77YS/eC6IX9OWbRV1QIIM0Jn4= github.com/jawher/mow.cli v1.2.0/go.mod h1:y+pcA3jBAdo/GIZx/0rFjw/K2bVEODP9rfZOfaiq8Ko= github.com/jcmturner/aescts/v2 v2.0.0/go.mod h1:AiaICIRyfYg35RUkr8yESTqvSy7csK90qZ5xfvvsoNs= github.com/jcmturner/dnsutils/v2 v2.0.0/go.mod h1:b0TnjGOvI/n42bZa+hmXL+kFJZsFT7G4t3HTlQ184QM= @@ -599,6 +617,7 @@ github.com/pkg/errors v0.8.0/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINE github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/planetscale/vtprotobuf v0.6.1-0.20240319094008-0393e58bdf10/go.mod h1:t/avpk3KcrXxUnYOhZhMXJlSEyie6gQbtLq5NM3loB8= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/prometheus/client_golang v0.9.1/go.mod h1:7SWBe2y4D6OKWSNQJUaRYU/AaXPKyh/dDVn+NZz0KFw= github.com/prometheus/client_golang v1.0.0/go.mod h1:db9x61etRT2tGnBNRi70OPL5FsnadC4Ky3P0J6CfImo= @@ -680,6 +699,7 @@ github.com/spf13/pflag v1.0.10/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3A github.com/spf13/viper v1.12.0/go.mod h1:b6COn30jlNxbm/V2IqWiNWkJ+vZNiMNksliPCiuKtSI= github.com/spiffe/go-spiffe/v2 v2.6.0/go.mod h1:gm2SeUoMZEtpnzPNs2Csc0D/gX33k1xIx7lEzqblHEs= github.com/spiffe/go-spiffe/v2 v2.7.0/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= +github.com/spiffe/go-spiffe/v2 v2.8.1/go.mod h1:47Q0Q9/AqGha8QLHp+kxpH4Wca7X7EnOtlIJy3mxZ3U= github.com/spkg/bom v0.0.0-20160624110644-59b7046e48ad/go.mod h1:qLr4V1qq6nMqFKkMo8ZTx3f+BZEkzsRUY10Xsm2mwU0= github.com/ssgreg/nlreturn/v2 v2.2.1/go.mod h1:E/iiPB78hV7Szg2YfRgyIrk1AD6JVMTRkkxBiELzh2I= github.com/stbenjam/no-sprintf-host-port v0.3.1/go.mod h1:ODbZesTCHMVKthBHskvUUexdcNHAQRXk9NpSsL8p/HQ= @@ -754,6 +774,7 @@ go.mongodb.org/mongo-driver v1.17.3/go.mod h1:Hy04i7O2kC4RS06ZrhPRqj/u4DTYkFDAAc go.mongodb.org/mongo-driver v1.17.9 h1:IexDdCuuNJ3BHrELgBlyaH9p60JXAvdzWR128q+U5tU= go.mongodb.org/mongo-driver v1.17.9/go.mod h1:LlOhpH5NUEfhxcAwG0UEkMqwYcc4JU18gtCdGudk/tQ= go.mongodb.org/mongo-driver/v2 v2.5.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= +go.mongodb.org/mongo-driver/v2 v2.8.0/go.mod h1:yOI9kBsufol30iFsl1slpdq1I0eHPzybRWdyYUs8K/0= go.opencensus.io v0.21.0/go.mod h1:mSImk1erAIZhrmZN+AvHh14ztQfjbGwt4TtuofqLduU= go.opencensus.io v0.22.0/go.mod h1:+kGneAE2xo2IficOXnaByMWTGM9T73dGwxeWcUqIpI8= go.opencensus.io v0.22.2/go.mod h1:yxeiOL68Rb0Xd1ddK5vPZ/oVn4vY4Ynel7k9FzqtOIw= @@ -768,6 +789,7 @@ go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.6 go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.70.0/go.mod h1:DqEFwLumhzMBDQv9PcWbyoDxHI/4lAk6CM4nJBH39sc= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.61.0/go.mod h1:UHB22Z8QsdRDrnAtX4PntOl36ajSxcdUMt1sF7Y6E7Q= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.67.0/go.mod h1:C2NGBr+kAB4bk3xtMXfZ94gqFDtg/GkI7e9zqGh5Beg= +go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.69.0/go.mod h1:z9+yiacE0IHRqM4qFfkbt/JYlmYXgss8GY/jXoNuPJI= go.opentelemetry.io/contrib/instrumentation/net/http/otelhttp v0.70.0/go.mod h1:085m8qbm4hgc8rZWGDEa4vmyyo2c3nPxUslYUKUIU04= go.opentelemetry.io/otel v1.43.0/go.mod h1:JuG+u74mvjvcm8vj8pI5XiHy1zDeoCS2LB1spIq7Ay0= go.opentelemetry.io/otel v1.44.0/go.mod h1:BMgjTHL9WPRlRjL2oZCBTL4whCGtXch2H4BhOPIAyYc= @@ -810,7 +832,9 @@ golang.org/x/crypto v0.48.0/go.mod h1:r0kV5h3qnFPlQnBSrULhlsRfryS2pmewsg+XfMgkVo golang.org/x/crypto v0.49.0/go.mod h1:ErX4dUh2UM+CFYiXZRTcMpEcN8b/1gxEuv3nODoYtCA= golang.org/x/crypto v0.51.0/go.mod h1:8AdwkbraGNABw2kOX6YFPs3WM22XqI4EXEd8g+x7Oc8= golang.org/x/crypto v0.53.0/go.mod h1:DNLU434OwVakk9PzuwV8w62mAJpRJL3vsgcfp4Qnsio= +golang.org/x/crypto v0.54.0/go.mod h1:KWL8ny2AZdGR2cWmzeHrp2azQPGogOv+HeQaVEXC2dk= golang.org/x/crypto v0.55.0/go.mod h1:uq0V9dE/fzQuJtbnL+2EhWOE63vo164FY8xqEnV9xis= +golang.org/x/crypto v0.56.0/go.mod h1:OMW5y6CY9l38uPLmxU6l6pwcXp1obtLo3e6gT7gQR2I= golang.org/x/exp v0.0.0-20190306152737-a1d7652674e8/go.mod h1:CJ0aWSM057203Lf6IL+f9T1iT9GByDxfZKAQTCR3kQA= golang.org/x/exp v0.0.0-20190510132918-efd6b22b2522/go.mod h1:ZjyILWgesfNpC6sMxTJOJm9Kp84zZh5NQWvqDGG3Qr8= golang.org/x/exp v0.0.0-20190829153037-c13cbed26979/go.mod h1:86+5VVa7VpoJ4kLfm080zCjGlMRFzhUhsZKEZO7MGek= @@ -851,6 +875,7 @@ golang.org/x/mod v0.18.0/go.mod h1:hTbmBsO62+eylJbnUtE2MGJUyE7QWk4xUqPFrRgJ+7c= golang.org/x/mod v0.21.0/go.mod h1:6SkKJ3Xj0I0BrPOZoBy3bdMptDDU9oJrpohJ3eWZ1fY= golang.org/x/mod v0.27.0/go.mod h1:rWI627Fq0DEoudcK+MBkNkCe0EetEaDSwJJkCcjpazc= golang.org/x/mod v0.35.0/go.mod h1:+GwiRhIInF8wPm+4AoT6L0FA1QWAad3OMdTRx4tFYlU= +golang.org/x/mod v0.37.0/go.mod h1:m8S8VeM9r4dzDwjrKO0a1sZP3YjeMamRRlD+fmR2Q/0= golang.org/x/mod v0.39.0/go.mod h1:bvIbwjQ0HUFFf5AKukeeYQG4ZBUG9yxQbR9aEweIwYY= golang.org/x/net v0.0.0-20181114220301-adae6a3d119a/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= golang.org/x/net v0.0.0-20190108225652-1e06a53dbb7e/go.mod h1:mL1N/T3taQHkDXs73rZJwtUhF3w3ftmwwsq0BUmARs4= @@ -886,6 +911,7 @@ golang.org/x/net v0.53.0/go.mod h1:JvMuJH7rrdiCfbeHoo3fCQU24Lf5JJwT9W3sJFulfgs= golang.org/x/net v0.54.0/go.mod h1:Sj4oj8jK6XmHpBZU/zWHw3BV3abl4Kvi+Ut7cQcY+cQ= golang.org/x/net v0.55.0/go.mod h1:L5U2KuzuOe1lY7Z+aWVIKK6qEeJXnXV9yzGA+WCHJww= golang.org/x/net v0.56.0/go.mod h1:D3Ku6r+V6JROoZK144D2XfMHFcMq/0zSfLelVTCFKec= +golang.org/x/net v0.57.0/go.mod h1:KpXc8iv+r3XplLAG/f7Jsf9RPszJzdR0f58q9vGOuEU= golang.org/x/net v0.58.0/go.mod h1:YwCddHnFlT7eLQqVprV19OnhLGtc5xOKgE0RyqgfWAU= golang.org/x/oauth2 v0.0.0-20190226205417-e64efc72b421/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= golang.org/x/oauth2 v0.0.0-20190604053449-0f29369cfe45/go.mod h1:gOpvHmFTYa4IltrdGE7lF6nIHvwfUNPOp7c8zoXwtLw= @@ -910,6 +936,7 @@ golang.org/x/sync v0.16.0/go.mod h1:1dzgHSNfp02xaA81J2MS99Qcpr2w7fw1gpm99rleRqA= golang.org/x/sync v0.17.0/go.mod h1:9KTHXmSnoGruLpwFjVSX0lNNA75CykiMECbovNTZqGI= golang.org/x/sync v0.20.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sync v0.21.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= +golang.org/x/sync v0.22.0/go.mod h1:9xrNwdLfx4jkKbNva9FpL6vEN7evnE43NNNJQ2LF3+0= golang.org/x/sys v0.0.0-20180905080454-ebe1bf3edb33/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20181116152217-5ac8a444bdc5/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190312061237-fead79001313/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= @@ -963,6 +990,7 @@ golang.org/x/sys v0.43.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.44.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.45.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/sys v0.46.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= +golang.org/x/sys v0.47.0/go.mod h1:4GL1E5IUh+htKOUEOaiffhrAeqysfVGipDYzABqnCmw= golang.org/x/telemetry v0.0.0-20260409153401-be6f6cb8b1fa/go.mod h1:kHjTxDEnAu6/Nl9lDkzjWpR+bmKfxeiRuSDlsMb70gE= golang.org/x/telemetry v0.0.0-20260508192327-42602be52be6/go.mod h1:Eqhaxk/wZsWEH8CRxLwj6xzEJbz7k1EFGqx7nyCoabE= golang.org/x/telemetry v0.0.0-20260625142307-59b4966ccb57/go.mod h1:3AWMyWHS+caVoiEXpiq6+tzKA40J4vQT3MYr80ZtQpc= @@ -987,6 +1015,7 @@ golang.org/x/text v0.36.0/go.mod h1:NIdBknypM8iqVmPiuco0Dh6P5Jcdk8lJL0CUebqK164= golang.org/x/text v0.37.0/go.mod h1:a5sjxXGs9hsn/AJVwuElvCAo9v8QYLzvavO5z2PiM38= golang.org/x/text v0.38.0/go.mod h1:YXZt3QhHUKYT53r2lLKFIVi6Ao1jdzrTR/KQ09qyxF4= golang.org/x/text v0.39.0/go.mod h1:3UwRclnC2g0TU9x8PZiyfOajCd1zaUNHF9cvqcQZ+ZM= +golang.org/x/text v0.40.0/go.mod h1:hpnzDAfGV753zIKo+wk3u1bVKCGPbrnF7+7LBF/UHVY= golang.org/x/text v0.41.0/go.mod h1:jvf1O8ajNzZqhSrQBPbutR/EB83Cc0CFrezNQIwbb5M= golang.org/x/time v0.0.0-20181108054448-85acf8d2951c/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= golang.org/x/time v0.0.0-20190308202827-9d24e82272b4/go.mod h1:tRJNPiyCQ0inRvYxbN9jk5I+vvW/OXSQhTDSoE431IQ= @@ -1072,6 +1101,7 @@ google.golang.org/api v0.81.0/go.mod h1:FA6Mb/bZxj706H2j+j2d6mHEEaHBmbbWnkfvmorO google.golang.org/api v0.196.0/go.mod h1:g9IL21uGkYgvQ5BZg6BAtoGJQIm8r6EgaAbpNey5wBE= google.golang.org/api v0.264.0/go.mod h1:fAU1xtNNisHgOF5JooAs8rRaTkl2rT3uaoNGo9NS3R8= google.golang.org/api v0.274.0/go.mod h1:JbAt7mF+XVmWu6xNP8/+CTiGH30ofmCmk9nM8d8fHew= +google.golang.org/api v0.278.0/go.mod h1:B9TqLBwJqVjp1mtt7WeoQwWRwvu/400y5lETOql+giQ= google.golang.org/api v0.287.1/go.mod h1:lM2kYRzYUCBY91P9h6VF1PYmvhxii3O5hji37qRvIcY= google.golang.org/appengine v1.5.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/appengine v1.6.1/go.mod h1:i06prIuMbXzDqacNJfV5OdTW448YApPu5ww/cMBSeb0= @@ -1120,8 +1150,10 @@ google.golang.org/genproto/googleapis/api v0.0.0-20251202230838-ff82c1b0f217/go. google.golang.org/genproto/googleapis/api v0.0.0-20260414002931-afd174a4e478/go.mod h1:C6ADNqOxbgdUUeRTU+LCHDPB9ttAMCTff6auwCVa4uc= google.golang.org/genproto/googleapis/api v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:q4lMZS6kskjT5HvCPrnnypcDPVJqT/f4nfxmkE7gryY= google.golang.org/genproto/googleapis/api v0.0.0-20260630182238-925bb5da69e7/go.mod h1:KqHwBx2upmfa1XSi1WuRvC+2VGCLtooKkfmyvRbUmqA= +google.golang.org/genproto/googleapis/api v0.0.0-20260706201446-f0a921348800/go.mod h1:FPk7EXUKMtImne7AmknoYjT4QXqKIzzRbeQIXzLk6fQ= google.golang.org/genproto/googleapis/api v0.0.0-20260803160001-6ac0973c030d/go.mod h1:K/+WGbmBY7aNW1HDw1fJnKYo10i0DkAX6pows00dLig= google.golang.org/genproto/googleapis/api v0.0.0-20260904194346-d0f1323225a4/go.mod h1:fJ2lYaWjqNknJyQBOCd0fA3HnEElJqGplH71a2txi+g= +google.golang.org/genproto/googleapis/api v0.0.0-20260918162117-cecb64721679/go.mod h1:52bV8FLAQ9Qmcqaq9ECLmuEHZthk+6OPV45aKBBrsNw= google.golang.org/genproto/googleapis/bytestream v0.0.0-20260630182238-925bb5da69e7/go.mod h1:6TABGosqSqU2l1+fJ3jdvOYPPVryeKybxYF0cCZkTBE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250825161204-c5933d9347a5/go.mod h1:M4/wBTSeyLxupu3W3tJtOgB14jILAS/XWPSSa3TAlJc= google.golang.org/genproto/googleapis/rpc v0.0.0-20251202230838-ff82c1b0f217/go.mod h1:7i2o+ce6H/6BluujYR+kqX3GKH+dChPTQU19wjRPiGk= @@ -1131,10 +1163,12 @@ google.golang.org/genproto/googleapis/rpc v0.0.0-20260523011958-0a33c5d7ca68/go. google.golang.org/genproto/googleapis/rpc v0.0.0-20260526163538-3dc84a4a5aaa/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260610212136-7ab31c22f7ad/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260630182238-925bb5da69e7/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260706201446-f0a921348800/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260729162451-8efbd57d26e0/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260803160001-6ac0973c030d/go.mod h1:4Hqkh8ycfw05ld/3BWL7rJOSfebL2Q+DVDeRgYgxUU8= google.golang.org/genproto/googleapis/rpc v0.0.0-20260825221802-da73d73af1c5/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= google.golang.org/genproto/googleapis/rpc v0.0.0-20260904194346-d0f1323225a4/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260918162117-cecb64721679/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.20.1/go.mod h1:10oTOabMzJvdu6/UiuZezV6QK5dSlG84ov/aaiqXj38= google.golang.org/grpc v1.21.1/go.mod h1:oYelfM1adQP15Ek0mdvEgi9Df8B9CZIaU1084ijfRaM= google.golang.org/grpc v1.26.0/go.mod h1:qbnxyOmOxrQa7FizSgH+ReBfzJrCY1pSN7KXBS8abTk= @@ -1148,6 +1182,7 @@ google.golang.org/grpc v1.80.0/go.mod h1:ho/dLnxwi3EDJA4Zghp7k2Ec1+c2jqup0bFkw07 google.golang.org/grpc v1.81.0/go.mod h1:xGH9GfzOyMTGIOXBJmXt+BX/V0kcdQbdcuwQ/zNw42I= google.golang.org/grpc v1.82.0/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= google.golang.org/grpc v1.82.1/go.mod h1:yzTZ1TB1Z3SG+LIYaI+WiE8D5+PZ3ArnrSp8zF3+/ZA= +google.golang.org/grpc v1.83.0/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc v1.83.1/go.mod h1:kDyl6SKsiHKt0uylY5gtn5cEjkrIOhQOGDgIc4JGwzQ= google.golang.org/grpc/examples v0.0.0-20250407062114-b368379ef8f6/go.mod h1:6ytKWczdvnpnO+m+JiG9NjEDzR1FJfsnmJdG7B8QVZ8= google.golang.org/protobuf v1.22.0/go.mod h1:EGpADcykh3NcUnDUJcl1+ZksZNG86OlYog2l/sGQquU= @@ -1161,6 +1196,7 @@ google.golang.org/protobuf v1.36.1/go.mod h1:9fA7Ob0pmnwhb644+1+CVWFRbNajQ6iRojt google.golang.org/protobuf v1.36.6/go.mod h1:jduwjTPXsFjZGTmRluh+L6NjiWu7pchiJ2/5YcXBHnY= google.golang.org/protobuf v1.36.8/go.mod h1:fuxRtAxBytpl4zzqUh6/eyUujkJdNiuEkXntxiD/uRU= google.golang.org/protobuf v1.36.10/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= +google.golang.org/protobuf v1.36.11/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/alecthomas/kingpin.v2 v2.2.6/go.mod h1:FMv+mEhP44yOT+4EoQTLFTRgOQ1FBLkstjWtayDeSgw= gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= diff --git a/module/base/ads/go.mod b/module/base/ads/go.mod index 6c8168f..037af13 100644 --- a/module/base/ads/go.mod +++ b/module/base/ads/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/ads -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -18,7 +18,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -48,7 +48,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -57,13 +57,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/ads/go.sum b/module/base/ads/go.sum index 89b481e..7dd2176 100644 --- a/module/base/ads/go.sum +++ b/module/base/ads/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -120,14 +100,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -140,8 +118,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/cloud/go.mod b/module/base/cloud/go.mod index 0bc867b..77d351d 100644 --- a/module/base/cloud/go.mod +++ b/module/base/cloud/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/cloud -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -18,7 +18,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -48,7 +48,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -57,13 +57,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/cloud/go.sum b/module/base/cloud/go.sum index 89b481e..7dd2176 100644 --- a/module/base/cloud/go.sum +++ b/module/base/cloud/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -120,14 +100,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -140,8 +118,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/cms/go.mod b/module/base/cms/go.mod index 63f3189..01fb11b 100644 --- a/module/base/cms/go.mod +++ b/module/base/cms/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/cms -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-slc @@ -12,35 +12,25 @@ tool ( ) require ( + git.apinb.com/bsm-sdk/core v0.2.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible + go.etcd.io/etcd/client/v3 v3.7.1 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + gorm.io/gorm v1.31.2 ) require ( filippo.io/edwards25519 v1.2.0 // indirect git.apinb.com/bsm-tools/protoc-gen-slc v0.0.13 // indirect git.apinb.com/bsm-tools/protoc-gen-ts v0.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect + github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.41.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect - gorm.io/driver/mysql v1.6.0 // indirect -) - -require ( - git.apinb.com/bsm-sdk/core v0.2.1 - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -50,18 +40,24 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.etcd.io/etcd/client/v3 v3.7.1 + go.uber.org/atomic v1.12.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect - gorm.io/gorm v1.31.2 + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/cms/go.sum b/module/base/cms/go.sum index 92df17c..27911dc 100644 --- a/module/base/cms/go.sum +++ b/module/base/cms/go.sum @@ -15,12 +15,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -59,8 +53,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -70,8 +62,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -80,20 +72,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -114,14 +94,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -134,8 +112,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/feedback/go.mod b/module/base/feedback/go.mod index dfc2a56..281ea49 100644 --- a/module/base/feedback/go.mod +++ b/module/base/feedback/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/feedback -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -18,7 +18,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -48,7 +48,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -57,13 +57,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/feedback/go.sum b/module/base/feedback/go.sum index 89b481e..7dd2176 100644 --- a/module/base/feedback/go.sum +++ b/module/base/feedback/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -120,14 +100,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -140,8 +118,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/fts/README.md b/module/base/fts/README.md index 3ffb8c6..3bcbe61 100644 --- a/module/base/fts/README.md +++ b/module/base/fts/README.md @@ -1,10 +1,10 @@ # FTS - 文件传输服务 -基于Go 1.26.5和Gin框架的现代化文件传输微服务 +基于go 1.27.1和Gin框架的现代化文件传输微服务 ## 特性 -- 🚀 **现代化架构**: 使用Go 1.26.5最新特性 +- 🚀 **现代化架构**: 使用go 1.27.1最新特性 - 📁 **多存储支持**: 支持本地存储和MinIO对象存储 - 🔧 **配置灵活**: 支持YAML配置文件,环境变量覆盖 - 🏥 **健康检查**: 完整的健康检查和监控端点 @@ -17,7 +17,7 @@ ### 环境要求 -- Go 1.26.5 +- go 1.27.1 - PostgreSQL / MySQL (可选) - Redis (可选) - MinIO (可选,用于对象存储) diff --git a/module/base/fts/go.mod b/module/base/fts/go.mod index e81619e..c383ede 100644 --- a/module/base/fts/go.mod +++ b/module/base/fts/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/fts -go 1.26.5 +go 1.27.1 require ( git.apinb.com/bsm-sdk/core v0.2.1 @@ -14,46 +14,27 @@ require ( require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/bytedance/gopkg v0.1.4 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/go-sql-driver/mysql v1.10.1 // indirect - github.com/goccy/go-yaml v1.19.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect - github.com/klauspost/compress v1.20.0 // indirect - github.com/klauspost/crc32 v1.3.0 // indirect - github.com/minio/crc64nvme v1.1.1 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect - github.com/rs/xid v1.6.0 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - github.com/tinylib/msgp v1.6.4 // indirect - github.com/zeebo/xxh3 v1.1.0 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - google.golang.org/grpc v1.83.2 // indirect - gopkg.in/ini.v1 v1.67.3 // indirect -) - -require ( github.com/bytedance/sonic v1.15.4 // indirect github.com/bytedance/sonic/loader v0.5.2 // indirect github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/cloudwego/base64x v0.1.7 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect - github.com/gin-contrib/cors v1.7.8 // indirect + github.com/gin-contrib/cors v1.7.9 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.4 // indirect + github.com/go-playground/universal-translator v0.18.2 // indirect + github.com/go-playground/validator/v10 v10.30.5 // indirect + github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/goccy/go-json v0.10.6 // indirect + github.com/goccy/go-yaml v1.19.2 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.11.0 // indirect @@ -61,37 +42,52 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.20.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/klauspost/crc32 v1.3.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect github.com/mattn/go-isatty v0.0.24 // indirect + github.com/minio/crc64nvme v1.1.1 // indirect github.com/minio/md5-simd v1.1.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect + github.com/philhofer/fwd v1.2.0 // indirect + github.com/quic-go/qpack v0.6.0 // indirect + github.com/quic-go/quic-go v0.63.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/shirou/gopsutil v3.21.11+incompatible // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + github.com/tinylib/msgp v1.6.4 // indirect github.com/tklauser/go-sysconf v0.4.0 // indirect github.com/tklauser/numcpus v0.12.0 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect github.com/yusufpapurcu/wmi v1.2.4 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect + go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect golang.org/x/arch v0.31.0 // indirect golang.org/x/crypto v0.57.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc v1.84.0 // indirect google.golang.org/protobuf v1.36.12 // indirect + gopkg.in/ini.v1 v1.67.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/fts/go.sum b/module/base/fts/go.sum index a73f3b7..c80dee9 100644 --- a/module/base/fts/go.sum +++ b/module/base/fts/go.sum @@ -22,20 +22,16 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc= -github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI= +github.com/gin-contrib/cors v1.7.9 h1:69rz5YU6PW7XlQ4VPXacl77ldFiUvR0E0bmEqmbq86w= +github.com/gin-contrib/cors v1.7.9/go.mod h1:KTqiTA5HzlWPgVz7p4tolzAuptzGO7DEJn/CbklG0nU= github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo= github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= @@ -43,10 +39,10 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= +github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= +github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= +github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= @@ -120,8 +116,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -169,20 +165,8 @@ go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -209,14 +193,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -229,8 +211,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/initial/go.mod b/module/base/initial/go.mod index 6d309a5..bf4ee6d 100644 --- a/module/base/initial/go.mod +++ b/module/base/initial/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/initial -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -14,8 +14,13 @@ tool ( ) require ( + git.apinb.com/bsm-sdk/core v0.2.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible + go.etcd.io/etcd/client/v3 v3.7.1 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + gorm.io/gorm v1.31.2 ) require ( @@ -23,29 +28,13 @@ require ( git.apinb.com/bsm-tools/protoc-gen-markdown v0.0.0-20250907131213-6c1694ce6bcb // indirect git.apinb.com/bsm-tools/protoc-gen-slc v0.0.13 // indirect git.apinb.com/bsm-tools/protoc-gen-ts v0.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect + github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/pepabo/protoc-gen-go-client v0.3.0 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.41.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect - gorm.io/driver/mysql v1.6.0 // indirect -) - -require ( - git.apinb.com/bsm-sdk/core v0.2.1 - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -54,19 +43,26 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect + github.com/pepabo/protoc-gen-go-client v0.3.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.etcd.io/etcd/client/v3 v3.7.1 + go.uber.org/atomic v1.12.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect - gorm.io/gorm v1.31.2 + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/initial/go.sum b/module/base/initial/go.sum index 89b481e..7dd2176 100644 --- a/module/base/initial/go.sum +++ b/module/base/initial/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -120,14 +100,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -140,8 +118,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/logs/go.mod b/module/base/logs/go.mod index c85ba02..5470a7d 100644 --- a/module/base/logs/go.mod +++ b/module/base/logs/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/logs -go 1.26.5 +go 1.27.1 require ( git.apinb.com/bsm-sdk/core v0.2.1 @@ -22,8 +22,8 @@ require ( github.com/gabriel-vasile/mimetype v1.4.15 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.4 // indirect + github.com/go-playground/universal-translator v0.18.2 // indirect + github.com/go-playground/validator/v10 v10.30.5 // indirect github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect @@ -45,7 +45,7 @@ require ( github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect + github.com/quic-go/quic-go v0.63.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect @@ -53,7 +53,7 @@ require ( go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/arch v0.31.0 // indirect @@ -62,14 +62,13 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc v1.84.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/logs/go.sum b/module/base/logs/go.sum index 1c1916b..f9d41a5 100644 --- a/module/base/logs/go.sum +++ b/module/base/logs/go.sum @@ -28,18 +28,14 @@ github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= +github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= +github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= +github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= @@ -98,8 +94,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -132,20 +128,8 @@ go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -170,14 +154,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -188,8 +170,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/mgt/README.md b/module/base/mgt/README.md index 5215cd8..a6894b3 100644 --- a/module/base/mgt/README.md +++ b/module/base/mgt/README.md @@ -51,7 +51,7 @@ ### 核心框架 -- **Go 1.26.5** - 编程语言 +- **go 1.27.1** - 编程语言 - **Gin** - Web 框架 - **GORM** - ORM 框架 - **PostgreSQL/MySQL** - 数据库 @@ -125,7 +125,7 @@ mgt/ ### 环境要求 -- **Go 1.26.5** - [安装指南](https://golang.org/doc/install) +- **go 1.27.1** - [安装指南](https://golang.org/doc/install) - **PostgreSQL 12+** 或 **MySQL 8.0+** - **Redis 6.0+** diff --git a/module/base/mgt/go.mod b/module/base/mgt/go.mod index 5e9ca1e..591c7fc 100644 --- a/module/base/mgt/go.mod +++ b/module/base/mgt/go.mod @@ -1,27 +1,22 @@ module bsm/full/module/base/mgt -go 1.26.5 +go 1.27.1 require ( - github.com/gin-contrib/sessions v1.1.1 + git.apinb.com/bsm-sdk/core v0.2.1 + github.com/gin-contrib/sessions v1.1.2 github.com/gin-gonic/gin v1.12.0 - github.com/go-playground/validator/v10 v10.30.4 + github.com/go-playground/validator/v10 v10.30.5 github.com/patrickmn/go-cache v2.1.0+incompatible + github.com/redis/go-redis/v9 v9.22.0 go.etcd.io/etcd/client/v3 v3.7.1 golang.org/x/crypto v0.57.0 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.2 ) -require ( - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 // indirect -) - require ( filippo.io/edwards25519 v1.2.0 // indirect - git.apinb.com/bsm-sdk/core v0.2.1 github.com/bytedance/gopkg v0.1.4 // indirect github.com/bytedance/sonic v1.15.4 // indirect github.com/bytedance/sonic/loader v0.5.2 // indirect @@ -30,10 +25,10 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect - github.com/gin-contrib/cors v1.7.8 // indirect + github.com/gin-contrib/cors v1.7.9 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect + github.com/go-playground/universal-translator v0.18.2 // indirect github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect @@ -59,14 +54,14 @@ require ( github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect - github.com/redis/go-redis/v9 v9.22.0 + github.com/quic-go/quic-go v0.63.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/arch v0.31.0 // indirect @@ -74,11 +69,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc v1.84.0 // indirect google.golang.org/protobuf v1.36.12 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/mgt/go.sum b/module/base/mgt/go.sum index 5b05b80..271e5e2 100644 --- a/module/base/mgt/go.sum +++ b/module/base/mgt/go.sum @@ -24,26 +24,22 @@ github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1 github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc= -github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI= -github.com/gin-contrib/sessions v1.1.1 h1:/3ORwmsZ0h/Kcc9fekFrSSc6QXEifMscBLcS8lyQfqQ= -github.com/gin-contrib/sessions v1.1.1/go.mod h1:h8ilnqzj7fPELlu4/rbFcZW/0SNYMXhm4X/KQv0i1G8= +github.com/gin-contrib/cors v1.7.9 h1:69rz5YU6PW7XlQ4VPXacl77ldFiUvR0E0bmEqmbq86w= +github.com/gin-contrib/cors v1.7.9/go.mod h1:KTqiTA5HzlWPgVz7p4tolzAuptzGO7DEJn/CbklG0nU= +github.com/gin-contrib/sessions v1.1.2 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow= +github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw= github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo= github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lYKSsYSR8s= github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= +github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= +github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= +github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= @@ -112,8 +108,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -146,20 +142,8 @@ go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -184,14 +168,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -202,8 +184,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/passport/README.md b/module/base/passport/README.md index 028b8c8..a4f7af0 100644 --- a/module/base/passport/README.md +++ b/module/base/passport/README.md @@ -18,7 +18,7 @@ ## 📋 系统要求 -- Go 1.26.5 或更高版本 +- go 1.27.1 或更高版本 - PostgreSQL 或 MySQL 数据库 - Redis 服务器 - Etcd(微服务模式需要) @@ -345,7 +345,7 @@ For support and questions: - **v1.0.0** - Initial release with core authentication features - **v1.1.0** - Added KYC integration and tag system - **v1.2.0** - Enhanced security and performance optimizations -- **Current** - Go 1.26.5 compatibility and bug fixes +- **Current** - go 1.27.1 compatibility and bug fixes ## 🚀 Roadmap diff --git a/module/base/passport/go.mod b/module/base/passport/go.mod index 34fb93d..551891f 100644 --- a/module/base/passport/go.mod +++ b/module/base/passport/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/passport -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -14,8 +14,14 @@ tool ( ) require ( + git.apinb.com/bsm-sdk/core v0.2.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible + go.etcd.io/etcd/client/v3 v3.7.1 + golang.org/x/crypto v0.57.0 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + gorm.io/gorm v1.31.2 ) require ( @@ -23,29 +29,13 @@ require ( git.apinb.com/bsm-tools/protoc-gen-markdown v0.0.0-20250907131213-6c1694ce6bcb // indirect git.apinb.com/bsm-tools/protoc-gen-slc v0.0.13 // indirect git.apinb.com/bsm-tools/protoc-gen-ts v0.0.2 // indirect + github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect + github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect - github.com/pepabo/protoc-gen-go-client v0.3.0 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect - go.uber.org/multierr v1.11.0 // indirect - go.uber.org/zap v1.28.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.41.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect - gorm.io/driver/mysql v1.6.0 // indirect -) - -require ( - git.apinb.com/bsm-sdk/core v0.2.1 - github.com/cespare/xxhash/v2 v2.3.0 // indirect - github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect @@ -54,20 +44,26 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect + github.com/pepabo/protoc-gen-go-client v0.3.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect + go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.etcd.io/etcd/client/v3 v3.7.1 - golang.org/x/crypto v0.57.0 + go.uber.org/atomic v1.12.0 // indirect + go.uber.org/multierr v1.11.0 // indirect + go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect - gorm.io/gorm v1.31.2 + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/passport/go.sum b/module/base/passport/go.sum index dc7668a..1264c3a 100644 --- a/module/base/passport/go.sum +++ b/module/base/passport/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -122,14 +102,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -142,8 +120,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/base/sender/README.md b/module/base/sender/README.md index e2f5417..94c7290 100644 --- a/module/base/sender/README.md +++ b/module/base/sender/README.md @@ -34,7 +34,7 @@ ### 环境要求 -- Go 1.26.5 +- go 1.27.1 - Redis 6.0+ - PostgreSQL 12+ - Etcd 3.5+ diff --git a/module/base/sender/go.mod b/module/base/sender/go.mod index 3b7dd19..ff15c19 100644 --- a/module/base/sender/go.mod +++ b/module/base/sender/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/base/sender -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -13,13 +13,6 @@ tool ( google.golang.org/protobuf/cmd/protoc-gen-go ) -require ( - github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 - google.golang.org/grpc v1.83.2 - google.golang.org/protobuf v1.36.12 - gorm.io/gorm v1.31.2 -) - require ( filippo.io/edwards25519 v1.2.0 // indirect git.apinb.com/bsm-tools/protoc-gen-markdown v0.0.0-20250907131213-6c1694ce6bcb // indirect @@ -55,7 +48,7 @@ require ( github.com/tjfoc/gmsm v1.4.1 // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -64,14 +57,13 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/ini.v1 v1.67.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) require ( @@ -80,11 +72,15 @@ require ( github.com/alibabacloud-go/dysmsapi-20180501/v2 v2.0.8 github.com/alibabacloud-go/tea v1.5.3 github.com/aliyun/credentials-go v1.4.13 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible github.com/spf13/cast v1.10.0 - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 go.etcd.io/etcd/client/v3 v3.7.1 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + gorm.io/gorm v1.31.2 ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/base/sender/go.sum b/module/base/sender/go.sum index 2149dcf..5c02dcb 100644 --- a/module/base/sender/go.sum +++ b/module/base/sender/go.sum @@ -77,8 +77,6 @@ github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= @@ -86,10 +84,6 @@ github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1m github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8= github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -165,8 +159,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/prometheus/client_model v0.0.0-20190812154241-14fe0d1b01d4/go.mod h1:xMI15A0UPsDsEKsMN9yxemIoYk6Tm2C1GtYGdfGttqA= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= @@ -190,11 +182,12 @@ github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/ github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU= github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.172/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 h1:CX2pX4NjA7JtbB0edCWYpRIhk/oO0JdhbdjkL6pnKAg= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 h1:ArbnBwggr9Rst5D3F28CIdIV5lo+DI58yrlUi2Cton8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 h1:8YBaibwP8YXcYP+4qdgoqIwNULYNt5/AyVP7t3J3hCA= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172/go.mod h1:eLllmEVIjbeQpcXHpd/68caL4/EgHBbHruRCmExcv+M= github.com/tjfoc/gmsm v1.3.2/go.mod h1:HaUcFuY0auTiaHB9MHFGCPx5IaLhTUd2atbCFBQXn9w= @@ -211,20 +204,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -361,18 +342,16 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= @@ -398,8 +377,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/ec/address/go.mod b/module/ec/address/go.mod index c1016f0..e368b67 100644 --- a/module/ec/address/go.mod +++ b/module/ec/address/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/ec/address -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-slc @@ -17,7 +17,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/pkg/v3 v3.7.1 go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.2 @@ -44,7 +44,7 @@ require ( github.com/redis/go-redis/v9 v9.22.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -53,12 +53,11 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/ec/address/go.sum b/module/ec/address/go.sum index 92df17c..27911dc 100644 --- a/module/ec/address/go.sum +++ b/module/ec/address/go.sum @@ -15,12 +15,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -59,8 +53,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -70,8 +62,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -80,20 +72,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -114,14 +94,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -134,8 +112,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/ec/mall/go.mod b/module/ec/mall/go.mod index 82fcd21..6af10d7 100644 --- a/module/ec/mall/go.mod +++ b/module/ec/mall/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/ec/mall -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-slc @@ -17,7 +17,7 @@ require ( github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 golang.org/x/crypto v0.57.0 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -44,7 +44,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -53,13 +53,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/ec/mall/go.sum b/module/ec/mall/go.sum index 34a6fc8..d3ff639 100644 --- a/module/ec/mall/go.sum +++ b/module/ec/mall/go.sum @@ -15,12 +15,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -59,8 +53,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -70,8 +62,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -80,20 +72,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -116,14 +96,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -136,8 +114,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/ec/market/go.mod b/module/ec/market/go.mod index 7c4f12e..728a9fa 100644 --- a/module/ec/market/go.mod +++ b/module/ec/market/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/ec/market -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-slc @@ -18,7 +18,7 @@ require ( go.etcd.io/etcd/client/pkg/v3 v3.7.1 go.etcd.io/etcd/client/v3 v3.7.1 golang.org/x/crypto v0.57.0 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/driver/mysql v1.6.0 gorm.io/gorm v1.31.2 @@ -45,7 +45,7 @@ require ( github.com/redis/go-redis/v9 v9.22.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -54,12 +54,11 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/ec/market/go.sum b/module/ec/market/go.sum index 34a6fc8..d3ff639 100644 --- a/module/ec/market/go.sum +++ b/module/ec/market/go.sum @@ -15,12 +15,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -59,8 +53,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -70,8 +62,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -80,20 +72,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -116,14 +96,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -136,8 +114,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/ec/order/go.mod b/module/ec/order/go.mod index 1be8867..a357bee 100644 --- a/module/ec/order/go.mod +++ b/module/ec/order/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/ec/order -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-slc @@ -13,30 +13,27 @@ tool ( require ( git.apinb.com/bsm-sdk/core v0.2.1 + github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible + go.etcd.io/etcd/client/pkg/v3 v3.7.1 + go.etcd.io/etcd/client/v3 v3.7.1 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 + gorm.io/driver/mysql v1.6.0 + gorm.io/gorm v1.31.2 ) require ( filippo.io/edwards25519 v1.2.0 // indirect git.apinb.com/bsm-tools/protoc-gen-slc v0.0.13 // indirect git.apinb.com/bsm-tools/protoc-gen-ts v0.0.2 // indirect - github.com/go-sql-driver/mysql v1.10.1 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - go.uber.org/atomic v1.11.0 // indirect - go.yaml.in/yaml/v3 v3.0.5 // indirect - golang.org/x/mod v0.41.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect -) - -require ( github.com/cespare/xxhash/v2 v2.3.0 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect + github.com/go-sql-driver/mysql v1.10.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/jackc/pgpassfile v1.0.0 // indirect github.com/jackc/pgservicefile v0.0.0-20240606120523-5a60cdf6a761 // indirect github.com/jackc/pgx/v5 v5.11.0 // indirect @@ -45,23 +42,22 @@ require ( github.com/jinzhu/now v1.1.5 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect - go.etcd.io/etcd/client/pkg/v3 v3.7.1 - go.etcd.io/etcd/client/v3 v3.7.1 + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect + go.yaml.in/yaml/v3 v3.0.5 // indirect + golang.org/x/mod v0.41.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/grpc v1.83.2 - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/mysql v1.6.0 - gorm.io/driver/postgres v1.6.2 // indirect - gorm.io/gorm v1.31.2 + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/ec/order/go.sum b/module/ec/order/go.sum index 92df17c..27911dc 100644 --- a/module/ec/order/go.sum +++ b/module/ec/order/go.sum @@ -15,12 +15,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -59,8 +53,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -70,8 +62,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -80,20 +72,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -114,14 +94,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -134,8 +112,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/finance/wallet/go.mod b/module/finance/wallet/go.mod index 96c7473..ce21cbb 100644 --- a/module/finance/wallet/go.mod +++ b/module/finance/wallet/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/finance/wallet -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -15,29 +15,15 @@ tool ( require ( git.apinb.com/bsm-sdk/core v0.2.1 + github.com/go-pay/gopay v1.5.123 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) -require ( - github.com/go-pay/crypto v0.0.3 // indirect - github.com/go-pay/errgroup v0.0.3 // indirect - github.com/go-pay/smap v0.0.2 // indirect - github.com/go-pay/util v0.0.4 // indirect - github.com/go-pay/xlog v0.0.3 // indirect - github.com/go-pay/xtime v0.0.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 // indirect - github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect - go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - gorm.io/driver/mysql v1.6.0 // indirect -) - require ( filippo.io/edwards25519 v1.2.0 // indirect git.apinb.com/bsm-tools/protoc-gen-markdown v0.0.0-20250907131213-6c1694ce6bcb // indirect @@ -47,8 +33,14 @@ require ( github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c // indirect - github.com/go-pay/gopay v1.5.123 + github.com/go-pay/crypto v0.0.3 // indirect + github.com/go-pay/errgroup v0.0.3 // indirect + github.com/go-pay/smap v0.0.2 // indirect + github.com/go-pay/util v0.0.4 // indirect + github.com/go-pay/xlog v0.0.3 // indirect + github.com/go-pay/xtime v0.0.2 // indirect github.com/go-sql-driver/mysql v1.10.1 // indirect + github.com/golang-jwt/jwt/v5 v5.3.1 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/jackc/pgpassfile v1.0.0 // indirect @@ -60,7 +52,10 @@ require ( github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/pepabo/protoc-gen-go-client v0.3.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect + go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -69,11 +64,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/mysql v1.6.0 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/finance/wallet/go.sum b/module/finance/wallet/go.sum index 4c4e36e..5161d1f 100644 --- a/module/finance/wallet/go.sum +++ b/module/finance/wallet/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pay/crypto v0.0.3 h1:PDYO7OduTBSSAujF/OLqMRFlcqcebEmAYNRKeTi1rdk= github.com/go-pay/crypto v0.0.3/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes= github.com/go-pay/errgroup v0.0.3 h1:DB4s8e8oWYDyETKQ1y1riMJ7y29zE1uIsMCSjEOFSbU= @@ -79,8 +73,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -90,8 +82,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -100,20 +92,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -134,14 +114,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -154,8 +132,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/social/feed/go.mod b/module/social/feed/go.mod index 612fc01..9a9c886 100644 --- a/module/social/feed/go.mod +++ b/module/social/feed/go.mod @@ -1,13 +1,13 @@ module bsm/full/module/social/feed -go 1.26.5 +go 1.27.1 require ( git.apinb.com/bsm-sdk/core v0.2.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -32,19 +32,18 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/social/feed/go.sum b/module/social/feed/go.sum index a9f1d15..1b6cf86 100644 --- a/module/social/feed/go.sum +++ b/module/social/feed/go.sum @@ -11,12 +11,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -55,8 +49,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -66,8 +58,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -76,20 +68,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -108,14 +88,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -126,8 +104,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/social/group/go.mod b/module/social/group/go.mod index 49ef2b8..eae7eac 100644 --- a/module/social/group/go.mod +++ b/module/social/group/go.mod @@ -1,13 +1,13 @@ module bsm/full/module/social/group -go 1.26.5 +go 1.27.1 require ( git.apinb.com/bsm-sdk/core v0.2.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -32,19 +32,18 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/social/group/go.sum b/module/social/group/go.sum index a9f1d15..1b6cf86 100644 --- a/module/social/group/go.sum +++ b/module/social/group/go.sum @@ -11,12 +11,6 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -55,8 +49,6 @@ github.com/patrickmn/go-cache v2.1.0+incompatible h1:HRMgzkcYKYpi3C8ajMPV8OFXaaR github.com/patrickmn/go-cache v2.1.0+incompatible/go.mod h1:3Qf8kWWT7OJRJbdiICTKqZju1ZixQ/KpMGzzAfe6+WQ= github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwpVEaDAr341tbn15RS4fCAsIst0qp7i8ex1o= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -66,8 +58,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -76,20 +68,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -108,14 +88,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= google.golang.org/protobuf v1.36.12/go.mod h1:HTf+CrKn2C3g5S8VImy6tdcUvCska2kB7j23XfzDpco= gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0= @@ -126,8 +104,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/module/social/relation/go.mod b/module/social/relation/go.mod index c9cd29c..be3533f 100644 --- a/module/social/relation/go.mod +++ b/module/social/relation/go.mod @@ -1,6 +1,6 @@ module bsm/full/module/social/relation -go 1.26.5 +go 1.27.1 tool ( git.apinb.com/bsm-tools/protoc-gen-markdown @@ -18,7 +18,7 @@ require ( github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) @@ -48,7 +48,7 @@ require ( github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect go.yaml.in/yaml/v3 v3.0.5 // indirect @@ -57,13 +57,12 @@ require ( golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) replace git.apinb.com/bsm-sdk/core => ../../../../../bsm-sdk/core diff --git a/module/social/relation/go.sum b/module/social/relation/go.sum index 89b481e..7dd2176 100644 --- a/module/social/relation/go.sum +++ b/module/social/relation/go.sum @@ -17,14 +17,8 @@ github.com/coreos/go-semver v0.3.1/go.mod h1:irMmmIw/7yzSRPWryHsK7EYSg09caPQL03V github.com/coreos/go-systemd/v22 v22.7.0 h1:LAEzFkke61DFROc7zNLX/WA2i5J8gYqe0rSj9KI28KA= github.com/coreos/go-systemd/v22 v22.7.0/go.mod h1:xNUYtjHu2EDXbsxz1i41wouACIwT7Ybq9o0BQhMwD0w= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= -github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c h1:+Zo5Ca9GH0RoeVZQKzFJcTLoAixx5s5Gq3pTIS+n354= github.com/ditashi/jsbeautifier-go v0.0.0-20141206144643-2520a8026a9c/go.mod h1:HJGU9ULdREjOcVGZVPB5s6zYmHi1RxzT71l2wQyLmnE= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/golang-jwt/jwt/v5 v5.3.1 h1:kYf81DTWFe7t+1VvL7eS+jKFVWaUnK9cB1qbwn63YCY= @@ -65,8 +59,6 @@ github.com/pborman/getopt v0.0.0-20170112200414-7148bc3a4c30/go.mod h1:85jBQOZwp github.com/pepabo/protoc-gen-go-client v0.3.0 h1:BUlzqqgMXWaEYzGXw1vqLIShP18JV7BMDm45OSLr/AU= github.com/pepabo/protoc-gen-go-client v0.3.0/go.mod h1:NvHW7/gsmZUK2UxafqzUzWD8zEMY0lMmm+2kLVxx8Jc= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= -github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -76,8 +68,8 @@ github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e/go.mod h1:XV66xRDq github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= -github.com/stretchr/testify v1.11.1 h1:7s2iGBzp5EwR7/aIZr8ao5+dra3wiQyKjjFuvgVKu7U= -github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD981Nm4B6U= +github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= +github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/zeebo/xxh3 v1.1.0 h1:s7DLGDK45Dyfg7++yxI0khrfwq9661w9EN78eP/UZVs= github.com/zeebo/xxh3 v1.1.0/go.mod h1:IisAie1LELR4xhVinxWS5+zf1lA4p0MW4T+w+W07F5s= go.etcd.io/etcd/api/v3 v3.7.1 h1:KJG0/DcWGfe3Y1otDf/fsBf0TSSgpxZ5RO/L8SFt73E= @@ -86,20 +78,8 @@ go.etcd.io/etcd/client/pkg/v3 v3.7.1 h1:rKYsj3pRkR0eK3yjT3XOgrhqfmIfj9pzNgxjh7mf go.etcd.io/etcd/client/pkg/v3 v3.7.1/go.mod h1:cnzZGIUzSfjEwLC6UBVsSXlEK1eepS/JUD7wE6PLRT0= go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0= @@ -120,14 +100,12 @@ golang.org/x/text v0.42.0 h1:JbOZXgfeCPU9gacVtYliJqOhD+zhrEqK4LfdpmlUZqI= golang.org/x/text v0.42.0/go.mod h1:ojzP1Z+2QtioaF8DTtO8K5q7JWVVYwZKenzujK0Zd0E= gonum.org/v1/gonum v0.17.0 h1:VbpOemQlsSMrYmn7T2OUvQ4dqxQXU+ouZFQsZOx50z4= gonum.org/v1/gonum v0.17.0/go.mod h1:El3tOrEuMpv2UdMrbNlKEh9vd86bmQ6vqIcDwxEOc1E= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2 h1:rgSNvqscFZ1JgV/4wH5GOsZFSFkR2Eua9As3KIr2LlM= google.golang.org/grpc/cmd/protoc-gen-go-grpc v1.6.2/go.mod h1:iMEtFwDlAhjDU9L5mY6U1XLwlIId/G3h+QcBHDIvrJ8= google.golang.org/protobuf v1.36.12 h1:pJOKDDOyeXErUroCihFAd5LQuwXBSpVnKGrj5o/fwxc= @@ -140,8 +118,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/pkgs/all/go.mod b/pkgs/all/go.mod index ad30c71..456aee8 100644 --- a/pkgs/all/go.mod +++ b/pkgs/all/go.mod @@ -1,6 +1,6 @@ module bsm/full/pkgs/all -go 1.26.5 +go 1.27.1 replace ( bsm/full/module/base/ads => ../../module/base/ads @@ -38,21 +38,18 @@ require ( bsm/full/module/ec/order v0.0.0 bsm/full/module/finance/wallet v0.0.0 git.apinb.com/bsm-sdk/core v0.2.1 - github.com/gin-contrib/sessions v1.1.1 + github.com/gin-contrib/sessions v1.1.2 github.com/gin-gonic/gin v1.12.0 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 go.yaml.in/yaml/v3 v3.0.5 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) -require ( - golang.org/x/net v0.59.0 // indirect - google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd // indirect -) - require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect @@ -70,9 +67,9 @@ require ( github.com/cloudwego/base64x v0.1.7 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect - github.com/dustin/go-humanize v1.0.1 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect - github.com/gin-contrib/cors v1.7.8 // indirect + github.com/gin-contrib/cors v1.7.9 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-pay/crypto v0.0.3 // indirect github.com/go-pay/errgroup v0.0.3 // indirect @@ -82,12 +79,11 @@ require ( github.com/go-pay/xlog v0.0.3 // indirect github.com/go-pay/xtime v0.0.2 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.4 // indirect + github.com/go-playground/universal-translator v0.18.2 // indirect + github.com/go-playground/validator/v10 v10.30.5 // indirect github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -115,12 +111,12 @@ require ( github.com/pelletier/go-toml/v2 v2.4.3 // indirect github.com/philhofer/fwd v1.2.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect + github.com/quic-go/quic-go v0.63.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 // indirect github.com/tinylib/msgp v1.6.4 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect @@ -130,19 +126,19 @@ require ( go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/arch v0.31.0 // indirect golang.org/x/crypto v0.57.0 // indirect + golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/ini.v1 v1.67.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) diff --git a/pkgs/all/go.sum b/pkgs/all/go.sum index fd2b5a4..7826ce6 100644 --- a/pkgs/all/go.sum +++ b/pkgs/all/go.sum @@ -81,8 +81,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -90,18 +90,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc= -github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI= -github.com/gin-contrib/sessions v1.1.1 h1:/3ORwmsZ0h/Kcc9fekFrSSc6QXEifMscBLcS8lyQfqQ= -github.com/gin-contrib/sessions v1.1.1/go.mod h1:h8ilnqzj7fPELlu4/rbFcZW/0SNYMXhm4X/KQv0i1G8= +github.com/gin-contrib/cors v1.7.9 h1:69rz5YU6PW7XlQ4VPXacl77ldFiUvR0E0bmEqmbq86w= +github.com/gin-contrib/cors v1.7.9/go.mod h1:KTqiTA5HzlWPgVz7p4tolzAuptzGO7DEJn/CbklG0nU= +github.com/gin-contrib/sessions v1.1.2 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow= +github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw= github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo= github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pay/crypto v0.0.3 h1:PDYO7OduTBSSAujF/OLqMRFlcqcebEmAYNRKeTi1rdk= github.com/go-pay/crypto v0.0.3/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes= github.com/go-pay/errgroup v0.0.3 h1:DB4s8e8oWYDyETKQ1y1riMJ7y29zE1uIsMCSjEOFSbU= @@ -120,10 +116,10 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= +github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= +github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= +github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= @@ -233,8 +229,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -264,8 +260,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.172/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 h1:CX2pX4NjA7JtbB0edCWYpRIhk/oO0JdhbdjkL6pnKAg= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 h1:ArbnBwggr9Rst5D3F28CIdIV5lo+DI58yrlUi2Cton8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 h1:8YBaibwP8YXcYP+4qdgoqIwNULYNt5/AyVP7t3J3hCA= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172/go.mod h1:eLllmEVIjbeQpcXHpd/68caL4/EgHBbHruRCmExcv+M= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= @@ -292,20 +288,8 @@ go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -446,18 +430,16 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd h1:s/EXdxMDrmT++u0VepJKO4U6dzOPJVRdHKmrazC1Qxg= -google.golang.org/genproto v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:JfZ1Hyx5mAJgX/ip0vEicmPvzE6rIrbacB7rHMfjD7Q= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -481,8 +463,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/pkgs/ecmall/go.mod b/pkgs/ecmall/go.mod index 956edb1..42612c9 100644 --- a/pkgs/ecmall/go.mod +++ b/pkgs/ecmall/go.mod @@ -1,6 +1,6 @@ module bsm/full/pkgs/ecmall -go 1.26.5 +go 1.27.1 replace ( bsm/full/module/base/ads => ../../module/base/ads @@ -36,31 +36,18 @@ require ( bsm/full/module/ec/order v0.0.0 bsm/full/module/finance/wallet v0.0.0 git.apinb.com/bsm-sdk/core v0.2.1 - github.com/gin-contrib/sessions v1.1.1 + github.com/gin-contrib/sessions v1.1.2 github.com/gin-gonic/gin v1.12.0 + github.com/golang-jwt/jwt/v5 v5.3.1 github.com/grpc-ecosystem/grpc-gateway/v2 v2.30.0 github.com/patrickmn/go-cache v2.1.0+incompatible go.etcd.io/etcd/client/v3 v3.7.1 go.yaml.in/yaml/v3 v3.0.5 - google.golang.org/grpc v1.83.2 + google.golang.org/grpc v1.84.0 + google.golang.org/protobuf v1.36.12 gorm.io/gorm v1.31.2 ) -require ( - github.com/dustin/go-humanize v1.0.1 // indirect - github.com/gin-contrib/cors v1.7.8 // indirect - github.com/klauspost/compress v1.20.0 // indirect - github.com/klauspost/crc32 v1.3.0 // indirect - github.com/minio/crc64nvme v1.1.1 // indirect - github.com/minio/md5-simd v1.1.2 // indirect - github.com/minio/minio-go/v7 v7.3.0 // indirect - github.com/philhofer/fwd v1.2.0 // indirect - github.com/rs/xid v1.6.0 // indirect - github.com/tinylib/msgp v1.6.4 // indirect - github.com/zeebo/xxh3 v1.1.0 // indirect - golang.org/x/net v0.59.0 // indirect -) - require ( filippo.io/edwards25519 v1.2.0 // indirect github.com/alibabacloud-go/alibabacloud-gateway-spi v0.0.5 // indirect @@ -78,7 +65,9 @@ require ( github.com/cloudwego/base64x v0.1.7 // indirect github.com/coreos/go-semver v0.3.1 // indirect github.com/coreos/go-systemd/v22 v22.7.0 // indirect + github.com/dustin/go-humanize v1.1.0 // indirect github.com/gabriel-vasile/mimetype v1.4.15 // indirect + github.com/gin-contrib/cors v1.7.9 // indirect github.com/gin-contrib/sse v1.1.2 // indirect github.com/go-pay/crypto v0.0.3 // indirect github.com/go-pay/errgroup v0.0.3 // indirect @@ -88,12 +77,11 @@ require ( github.com/go-pay/xlog v0.0.3 // indirect github.com/go-pay/xtime v0.0.2 // indirect github.com/go-playground/locales v0.14.1 // indirect - github.com/go-playground/universal-translator v0.18.1 // indirect - github.com/go-playground/validator/v10 v10.30.4 // indirect + github.com/go-playground/universal-translator v0.18.2 // indirect + github.com/go-playground/validator/v10 v10.30.5 // indirect github.com/go-sql-driver/mysql v1.10.1 // indirect github.com/goccy/go-json v0.10.6 // indirect github.com/goccy/go-yaml v1.19.2 // indirect - github.com/golang-jwt/jwt/v5 v5.3.1 github.com/golang/protobuf v1.5.4 // indirect github.com/google/uuid v1.6.0 // indirect github.com/gorilla/context v1.1.2 // indirect @@ -107,39 +95,48 @@ require ( github.com/jinzhu/inflection v1.0.0 // indirect github.com/jinzhu/now v1.1.5 // indirect github.com/json-iterator/go v1.1.12 // indirect + github.com/klauspost/compress v1.20.0 // indirect github.com/klauspost/cpuid/v2 v2.4.0 // indirect + github.com/klauspost/crc32 v1.3.0 // indirect github.com/leodido/go-urn v1.5.0 // indirect github.com/mattn/go-isatty v0.0.24 // indirect + github.com/minio/crc64nvme v1.1.1 // indirect + github.com/minio/md5-simd v1.1.2 // indirect + github.com/minio/minio-go/v7 v7.3.0 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/oklog/ulid/v2 v2.1.2 // indirect github.com/pelletier/go-toml/v2 v2.4.3 // indirect + github.com/philhofer/fwd v1.2.0 // indirect github.com/quic-go/qpack v0.6.0 // indirect - github.com/quic-go/quic-go v0.62.0 // indirect + github.com/quic-go/quic-go v0.63.0 // indirect github.com/redis/go-redis/v9 v9.22.0 // indirect + github.com/rs/xid v1.6.0 // indirect github.com/skip2/go-qrcode v0.0.0-20200617195104-da1b6568686e // indirect github.com/spf13/cast v1.10.0 // indirect - github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 // indirect + github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 // indirect github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 // indirect + github.com/tinylib/msgp v1.6.4 // indirect github.com/tjfoc/gmsm v1.4.1 // indirect github.com/twitchyliquid64/golang-asm v0.15.1 // indirect github.com/ugorji/go/codec v1.3.2 // indirect + github.com/zeebo/xxh3 v1.1.0 // indirect go.etcd.io/etcd/api/v3 v3.7.1 // indirect go.etcd.io/etcd/client/pkg/v3 v3.7.1 // indirect go.mongodb.org/mongo-driver/v2 v2.9.1 // indirect - go.uber.org/atomic v1.11.0 // indirect + go.uber.org/atomic v1.12.0 // indirect go.uber.org/multierr v1.11.0 // indirect go.uber.org/zap v1.28.0 // indirect golang.org/x/arch v0.31.0 // indirect golang.org/x/crypto v0.57.0 // indirect + golang.org/x/net v0.59.0 // indirect golang.org/x/sync v0.23.0 // indirect golang.org/x/sys v0.48.0 // indirect golang.org/x/text v0.42.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd // indirect - google.golang.org/protobuf v1.36.12 + google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 // indirect gopkg.in/ini.v1 v1.67.3 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect gorm.io/driver/mysql v1.6.0 // indirect - gorm.io/driver/postgres v1.6.2 // indirect + gorm.io/driver/postgres v1.6.3 // indirect ) diff --git a/pkgs/ecmall/go.sum b/pkgs/ecmall/go.sum index 6f81b9d..7826ce6 100644 --- a/pkgs/ecmall/go.sum +++ b/pkgs/ecmall/go.sum @@ -81,8 +81,8 @@ github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSs github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/dustin/go-humanize v1.0.1 h1:GzkhY7T5VNhEkwH0PVJgjz+fX1rhBrR7pRT3mDkpeCY= -github.com/dustin/go-humanize v1.0.1/go.mod h1:Mu1zIs6XwVuF/gI1OepvI0qD18qycQx+mFykh5fBlto= +github.com/dustin/go-humanize v1.1.0 h1:dbKTrvD0klcbBV/h4AWJdMuZogJACoMlvWIWZ5b2xWg= +github.com/dustin/go-humanize v1.1.0/go.mod h1:hc1CvRkJMsgxqjmjMQF3QNRAZBwY8AXBAzKYoSX9sFI= github.com/envoyproxy/go-control-plane v0.9.0/go.mod h1:YTl/9mNaCwkRvm6d1a2C3ymFceY/DCBVvsKhRF0iEA4= github.com/envoyproxy/go-control-plane v0.9.4/go.mod h1:6rpuAdCZL397s3pYoYcLgu1mIlRU8Am5FuJP05cCM98= github.com/envoyproxy/protoc-gen-validate v0.1.0/go.mod h1:iSmxcyjqTsJpI2R4NaDN7+kN2VEUnK/pcBlmesArF7c= @@ -90,18 +90,14 @@ github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHk github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0= github.com/gabriel-vasile/mimetype v1.4.15 h1:05iP/CYtZ/w455R/KZM6rZ5ieAdh99UPtd+d3YzLmaI= github.com/gabriel-vasile/mimetype v1.4.15/go.mod h1:azpTcoLcDZRNgFou5j+APrqQx9HqVPWa6ijYQIIVswQ= -github.com/gin-contrib/cors v1.7.8 h1:U0jjyXlWXMEx27hFE6hmdg/TOZviYgsOGGT+kLmUZrc= -github.com/gin-contrib/cors v1.7.8/go.mod h1:u3nLI2pP1IlJn7tbvL7iiubDO9quBZ8FXP9LxmqpsPI= -github.com/gin-contrib/sessions v1.1.1 h1:/3ORwmsZ0h/Kcc9fekFrSSc6QXEifMscBLcS8lyQfqQ= -github.com/gin-contrib/sessions v1.1.1/go.mod h1:h8ilnqzj7fPELlu4/rbFcZW/0SNYMXhm4X/KQv0i1G8= +github.com/gin-contrib/cors v1.7.9 h1:69rz5YU6PW7XlQ4VPXacl77ldFiUvR0E0bmEqmbq86w= +github.com/gin-contrib/cors v1.7.9/go.mod h1:KTqiTA5HzlWPgVz7p4tolzAuptzGO7DEJn/CbklG0nU= +github.com/gin-contrib/sessions v1.1.2 h1:utWRqOHT/kbqxxqF/mtutV9wfGLi1+uu5YuvWUhj/Ow= +github.com/gin-contrib/sessions v1.1.2/go.mod h1:qRxYwA/TYJw0wv28oFzs4gY/8G8z69id862SNgX9Gmw= github.com/gin-contrib/sse v1.1.2 h1:MU2fgl1RrdYTMcgJLtz2kJF+vPg3xrqaaKfUUU18tCo= github.com/gin-contrib/sse v1.1.2/go.mod h1:QXzuVkA0YO7o/gun03UI1Q+FTI8ZV/n5t03kIQAI89s= github.com/gin-gonic/gin v1.12.0 h1:b3YAbrZtnf8N//yjKeU2+MQsh2mY5htkZidOM7O0wG8= github.com/gin-gonic/gin v1.12.0/go.mod h1:VxccKfsSllpKshkBWgVgRniFFAzFb9csfngsqANjnLc= -github.com/go-logr/logr v1.4.4 h1:tG4xh9yMsRCAiodLVTxyrkzSZ9+o0L1Kg/+cPVcbP/8= -github.com/go-logr/logr v1.4.4/go.mod h1:9T104GzyrTigFIr8wt5mBrctHMim0Nb2HLGrmQ40KvY= -github.com/go-logr/stdr v1.2.2 h1:hSWxHoqTgW2S2qGc0LTAI563KZ5YKYRhT3MFKZMbjag= -github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre4VKE= github.com/go-pay/crypto v0.0.3 h1:PDYO7OduTBSSAujF/OLqMRFlcqcebEmAYNRKeTi1rdk= github.com/go-pay/crypto v0.0.3/go.mod h1:41oEIvHMKbNcYlWUlRWtsnC6+ASgh7u29z0gJXe5bes= github.com/go-pay/errgroup v0.0.3 h1:DB4s8e8oWYDyETKQ1y1riMJ7y29zE1uIsMCSjEOFSbU= @@ -120,10 +116,10 @@ github.com/go-playground/assert/v2 v2.2.0 h1:JvknZsQTYeFEAhQwI4qEt9cyV5ONwRHC+lY github.com/go-playground/assert/v2 v2.2.0/go.mod h1:VDjEfimB/XKnb+ZQfWdccd7VUvScMdVu0Titje2rxJ4= github.com/go-playground/locales v0.14.1 h1:EWaQ/wswjilfKLTECiXz7Rh+3BjFhfDFKv/oXslEjJA= github.com/go-playground/locales v0.14.1/go.mod h1:hxrqLVvrK65+Rwrd5Fc6F2O76J/NuW9t0sjnWqG1slY= -github.com/go-playground/universal-translator v0.18.1 h1:Bcnm0ZwsGyWbCzImXv+pAJnYK9S473LQFuzCbDbfSFY= -github.com/go-playground/universal-translator v0.18.1/go.mod h1:xekY+UJKNuX9WP91TpwSH2VMlDf28Uj24BCp08ZFTUY= -github.com/go-playground/validator/v10 v10.30.4 h1:9Rcod2ZPO6mOEG6b4GqyoHE/H6//Ze0RuhOo1hT1x0w= -github.com/go-playground/validator/v10 v10.30.4/go.mod h1:numpT+RPLE91R9oYWMY/R9zRgJBewr3IXHko4OISPpk= +github.com/go-playground/universal-translator v0.18.2 h1:LCsMLC9RzmbUMNUPVYD15dmcjwYAJhmX8mPZRW4rAVU= +github.com/go-playground/universal-translator v0.18.2/go.mod h1:67VZIMp5lQpDWlnStOct22q1bkdJGJqHghbOtmkawxk= +github.com/go-playground/validator/v10 v10.30.5 h1:YyCXvVShZbs2Sm3Mb53eNOlhRXctSOzW5QJAouCTZL4= +github.com/go-playground/validator/v10 v10.30.5/go.mod h1:wEqiaov48pXX1kjhc3Da8y0M0Dtg/BK7gurFBLgwFrQ= github.com/go-sql-driver/mysql v1.10.1 h1:arlSnNLq6a5yxGxV7qg9lF4j0C+KwD6NbQyKr9QL6ME= github.com/go-sql-driver/mysql v1.10.1/go.mod h1:M+cqaI7+xxXGG9swrdeUIoPG3Y3KCkF0pZej+SK+nWk= github.com/goccy/go-json v0.10.6 h1:p8HrPJzOakx/mn/bQtjgNjdTcN+/S6FcG2CTtQOrHVU= @@ -233,8 +229,8 @@ github.com/quic-go/go-ossfuzz-seeds v0.1.0 h1:APacT+iIaNF6fd8AGEiN3bT/Jtkd2jz4v4 github.com/quic-go/go-ossfuzz-seeds v0.1.0/go.mod h1:3IOHRbJIc+L6YKMwfDtJAM9Vj9k0YY4muhuyUYk5tbk= github.com/quic-go/qpack v0.6.0 h1:g7W+BMYynC1LbYLSqRt8PBg5Tgwxn214ZZR34VIOjz8= github.com/quic-go/qpack v0.6.0/go.mod h1:lUpLKChi8njB4ty2bFLX2x4gzDqXwUpaO1DP9qMDZII= -github.com/quic-go/quic-go v0.62.0 h1:ZHDjCk5OacATwGvs8PWE97CTvX7AqZiVoW7++ZOXTf8= -github.com/quic-go/quic-go v0.62.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= +github.com/quic-go/quic-go v0.63.0 h1:LIFGHI4PFUhhw2dDD1ARHdCff143ffMHwZtbnbuJ78A= +github.com/quic-go/quic-go v0.63.0/go.mod h1:RAro2j2yN9a9EiPACLHT9IB2NXCvGQmmo/alT0yYI0w= github.com/redis/go-redis/v9 v9.22.0 h1:laDvpYXTJtZLloinw1fA5Kqd6HAEH2XKxOkG/PDq2F0= github.com/redis/go-redis/v9 v9.22.0/go.mod h1:y2g0Wj8rQvuK0ELM+oxSudcLtC09JScs98I/X9gRWY4= github.com/rogpeppe/go-internal v1.14.1 h1:UQB4HGPB6osV0SQTLymcB4TgvyWu6ZyliaW0tI/otEQ= @@ -264,8 +260,8 @@ github.com/stretchr/testify v1.11.1/go.mod h1:wZwfW3scLgRK+23gO65QZefKpKQRnfz6sD github.com/stretchr/testify v1.12.1 h1:EuwCh5fleGS7H32xRwO3wRGT7DxrDhLAT6FF8MpWDWE= github.com/stretchr/testify v1.12.1/go.mod h1:MDEgiDPPsNp5cuIrHPPCyornHKgEVbtFUmoNlxoYthg= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.172/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178 h1:CX2pX4NjA7JtbB0edCWYpRIhk/oO0JdhbdjkL6pnKAg= -github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.178/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184 h1:ArbnBwggr9Rst5D3F28CIdIV5lo+DI58yrlUi2Cton8= +github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/common v1.3.184/go.mod h1:r5r4xbfxSaeR04b166HGsBa/R4U3SueirEUpXGuw+Q0= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172 h1:8YBaibwP8YXcYP+4qdgoqIwNULYNt5/AyVP7t3J3hCA= github.com/tencentcloud/tencentcloud-sdk-go/tencentcloud/sms v1.3.172/go.mod h1:eLllmEVIjbeQpcXHpd/68caL4/EgHBbHruRCmExcv+M= github.com/tinylib/msgp v1.6.4 h1:mOwYbyYDLPj35mkA2BjjYejgJk9BuHxDdvRnb6v2ZcQ= @@ -292,20 +288,8 @@ go.etcd.io/etcd/client/v3 v3.7.1 h1:0PEMMC0KuZmVIN+RAbdqfkZ45pYTgKVtmBEbRCvZFUg= go.etcd.io/etcd/client/v3 v3.7.1/go.mod h1:ffNqALa8tRCYhYo1F9oR489y23K39Gz+BSR3ApAGYq0= go.mongodb.org/mongo-driver/v2 v2.9.1 h1:jewiFs2m1/VOQp8qhFshX6hWZ+EAXDhZHXExAUMcOgQ= go.mongodb.org/mongo-driver/v2 v2.9.1/go.mod h1:SHKN0IWkKmEVGHLjXnni6s4wPKX4v86FTgOeJJFuXcA= -go.opentelemetry.io/auto/sdk v1.2.1 h1:jXsnJ4Lmnqd11kwkBV2LgLoFMZKizbCi5fNZ/ipaZ64= -go.opentelemetry.io/auto/sdk v1.2.1/go.mod h1:KRTj+aOaElaLi+wW1kO/DZRXwkF4C5xPbEe3ZiIhN7Y= -go.opentelemetry.io/otel v1.45.0 h1:pdrWmLHofpubmArBv1LgFSv1Z0Ie/ppdZzu+kUN5EeU= -go.opentelemetry.io/otel v1.45.0/go.mod h1:XZxIqPapzEYnhNSScF5DIqXhm/rYi0FzCe2XddAwZfQ= -go.opentelemetry.io/otel/metric v1.45.0 h1:7Eg1uH7CJ5cXv9is6tnBe1FI6rj1nwUdbFypRm3br/M= -go.opentelemetry.io/otel/metric v1.45.0/go.mod h1:HAPbm1nd3p1PmFH7v2dR+6BjXxw+Lq4a2+pndMAm08s= -go.opentelemetry.io/otel/sdk v1.45.0 h1:4VVSMgQ83dUgW2aoX5f6JgLvHwIvzcuLnF9lUdCSpCw= -go.opentelemetry.io/otel/sdk v1.45.0/go.mod h1:Sr40LgXV7DsKMMJMKOhUWOgMWTfAaqvm2kF0g7ilwuA= -go.opentelemetry.io/otel/sdk/metric v1.44.0 h1:3LlKgI+VjbVsjNRFZJZAJ30WjXC5VkNRks6si09iEfI= -go.opentelemetry.io/otel/sdk/metric v1.44.0/go.mod h1:5B5pMARnXxKhltooO4xUuCBorl65a4EpnTalObqOigA= -go.opentelemetry.io/otel/trace v1.45.0 h1:l/mP6Uv7oNO7/TblbhpbgMidxhq1uO/rPsikOyVhxag= -go.opentelemetry.io/otel/trace v1.45.0/go.mod h1:qoJJA2xNMnxRrdISU/kLtfUH2wNeQbiv+jhs/CxI8bc= -go.uber.org/atomic v1.11.0 h1:ZvwS0R+56ePWxUNi+Atn9dWONBPp/AUETXlHW0DxSjE= -go.uber.org/atomic v1.11.0/go.mod h1:LUxbIzbOniOlMKjJjyPfpl4v+PKK2cNJn91OQbhoJI0= +go.uber.org/atomic v1.12.0 h1:BvcXdFKuviU4fTL/f+SxdQ5qJX/Jix8pAkgdUcb3XOE= +go.uber.org/atomic v1.12.0/go.mod h1:I6c4cg+6HCxRjfjSsYtApoFILnpc0CGUdGkXVqbYVNk= go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= go.uber.org/mock v0.6.0 h1:hyF9dfmbgIX5EfOdasqLsWD6xqpNZlXblLB/Dbnwv3Y= @@ -446,16 +430,16 @@ google.golang.org/appengine v1.1.0/go.mod h1:EbEs0AVv82hx2wNQdGPgUI5lhzA/G0D9Ywl google.golang.org/appengine v1.4.0/go.mod h1:xpcJRLb0r/rnEns0DIKYYv+WjYCduHsrkT7/EB5XEv4= google.golang.org/genproto v0.0.0-20180817151627-c66870c02cf8/go.mod h1:JiN7NxoALGmiZfu7CAH4rXhgtRTLTxftemlI0sWmxmc= google.golang.org/genproto v0.0.0-20190819201941-24fa4b261c55/go.mod h1:DMBHOl98Agz4BDEuKkezgsaosCRResVns1a3J2ZsMNc= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd h1:jgCQeWccunvocKNK2RjhQHNiSjSeMMuBpQ/5pbP+2lA= -google.golang.org/genproto/googleapis/api v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:r4KD2hOq82JBWpTWkJ9NZLf6EwmRvAPIFmk7hPNtd+0= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd h1:29LZPNIJg+GEmJwZXruJJRwqy/wnWAYZXDtROToV/aQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20260911204522-f61a6ca850bd/go.mod h1:DjtHYE8FKJLivXcBEjGwndXfIC23G0VpXiXKqG179uA= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459 h1:GS9OIt/j7c8bvBjYNgnKQysVfmV7e4jM0H8ZK95G4t8= +google.golang.org/genproto/googleapis/api v0.0.0-20260921155816-b14227669459/go.mod h1:PX5/4vemwVoXtwEcRDWwcR1/r0qrosfx3qoVADMwnVE= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459 h1:b0xCahf3FK2m2Cv0p4vTozGPWncCvLfwV86UNg8xWU8= +google.golang.org/genproto/googleapis/rpc v0.0.0-20260921155816-b14227669459/go.mod h1:OaIUM3+LpYcK2GXM4FTmhWoIq371Owdr+Cc7/BsYHHc= google.golang.org/grpc v1.19.0/go.mod h1:mqu4LbDTu4XGKhr4mRzUsmM4RtVoemTSY81AxZiDr8c= google.golang.org/grpc v1.23.0/go.mod h1:Y5yQAOtifL1yxbo5wqy6BxZv8vAUGQwXBOALyacEbxg= google.golang.org/grpc v1.25.1/go.mod h1:c3i+UQWmh7LiEpx4sFZnkU36qjEYZ0imhYfXVyQciAY= google.golang.org/grpc v1.31.0/go.mod h1:N36X2cJ7JwdamYAgDz+s+rVMFjt3numwzf/HckM8pak= -google.golang.org/grpc v1.83.2 h1:EManeRomTObA0BU7I8vXgg/78uE5MJ9M8B39EX2WscU= -google.golang.org/grpc v1.83.2/go.mod h1:YPI1hK3kDked6iHvgX3tR0y+nX/qpMFKhPgFsokw1S8= +google.golang.org/grpc v1.84.0 h1:soMyaPJ8pAak5PIQ0DGBUir0XRo2fRoMqhNWMLlLxO0= +google.golang.org/grpc v1.84.0/go.mod h1:ljCht0DrxQrXBDRTZp52Qxh3Ffk8CdYm2sj4O2QN2C0= google.golang.org/protobuf v0.0.0-20200109180630-ec00e32a8dfd/go.mod h1:DFci5gLYBciE7Vtevhsrf46CRTquxDuWsQurQQe4oz8= google.golang.org/protobuf v0.0.0-20200221191635-4d8936d0db64/go.mod h1:kwYJMbMJ01Woi6D6+Kah6886xMZcty6N08ah7+eCXa0= google.golang.org/protobuf v0.0.0-20200228230310-ab0ca4ff8a60/go.mod h1:cfTl7dwQJ+fmap5saPgwCLgHXTUD7jkjRqWcaiX5VyM= @@ -479,8 +463,8 @@ gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA= gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM= gorm.io/driver/mysql v1.6.0 h1:eNbLmNTpPpTOVZi8MMxCi2aaIm0ZpInbORNXDwyLGvg= gorm.io/driver/mysql v1.6.0/go.mod h1:D/oCC2GWK3M/dqoLxnOlaNKmXz8WNTfcS9y5ovaSqKo= -gorm.io/driver/postgres v1.6.2 h1:BvXQ/cNUg63q5TFNg672DmDcowZSFrNLkkA3Xe6GXq4= -gorm.io/driver/postgres v1.6.2/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= +gorm.io/driver/postgres v1.6.3 h1:bAn6O2pUa8LtpWEvL5NFU4+52Tfx8Ut7IVaIacCLcI0= +gorm.io/driver/postgres v1.6.3/go.mod h1:0c4fQA44XhOklXDkgtuKqysHCycTa5i9e3EIpDGCwXk= gorm.io/driver/sqlite v1.6.0 h1:WHRRrIiulaPiPFmDcod6prc4l2VGVWHz80KspNsxSfQ= gorm.io/driver/sqlite v1.6.0/go.mod h1:AO9V1qIQddBESngQUKWL9yoH93HIeA1X6V633rBwyT8= gorm.io/gorm v1.31.2 h1:3o8FXNo9v9S858gil+3LlZA1LkCOzgb4g5BL64FgaCo= diff --git a/scripts/api-docgen/go.mod b/scripts/api-docgen/go.mod index fe1a32c..09c1e9f 100644 --- a/scripts/api-docgen/go.mod +++ b/scripts/api-docgen/go.mod @@ -1,6 +1,6 @@ module bsm/full/tools/api-docgen -go 1.26.5 +go 1.27.1 require ( bsm/full/module/base/ads v0.0.0 diff --git a/wiki/README.md b/wiki/README.md index e9781ec..1b6a90e 100644 --- a/wiki/README.md +++ b/wiki/README.md @@ -2,12 +2,18 @@ | 文档 | 内容 | | --- | --- | +| [产品与服务目录](产品与服务目录.md) | 服务域、服务清单、实现完成度、跨域耦合与运行拓扑 | +| [总体审计报告(2026-09-22)](audit-2026-09-22-总体审计报告.md) | 逐业务实现层审计汇总:P0/P1 风险台账、共性根因、修复优先级 | | [系统架构](architecture.md) | 模块边界、聚合入口、请求分流和依赖注入 | | [电商聚合开发上下文](../pkgs/ecmall/AGENT.md) | pkgs/ecmall 的注册表、配置、跨模块耦合、业务链路、缺口清单和开发指引 | | [开发与运维](operations.md) | 配置、启动、验证、构建、发布和排障 | -| [审计报告](audit-2026-08-10.md) | 已修复问题、剩余风险和后续优先级 | +| [审计报告(2026-08-10)](audit-2026-08-10.md) | 上一轮 workspace 层审计:已修复问题、剩余风险和后续优先级 | | [API 接入总览](api/00-overview.md) | gRPC、动态 HTTP RPC、grpc-gateway、鉴权和 JSON 规则 | +## 逐微服务代码审计 + +按微服务名称组织的代码审计报告位于 [`../docs/`](../docs/README.md)(21 篇:18 个业务服务 + 2 个聚合入口 + 工作区与脚本)。索引见 [docs/README.md](../docs/README.md)。 + ## 客户端 API 文档 | 领域 | 文档 | diff --git a/wiki/audit-2026-09-22-总体审计报告.md b/wiki/audit-2026-09-22-总体审计报告.md new file mode 100644 index 0000000..ea6a104 --- /dev/null +++ b/wiki/audit-2026-09-22-总体审计报告.md @@ -0,0 +1,152 @@ +# 总体审计报告(2026-09-22) + +> 本报告是产品级汇总,汇总逐微服务审计结论。逐服务明细见 [`docs/`](../docs/README.md)(21 篇)。 + +## 一、审计范围与方法 + +| 项 | 内容 | +| --- | --- | +| 审计对象 | BSM Infra Full 后端 Go workspace,`go 1.27.1` | +| 覆盖模块 | `module/` 18 个业务服务 + `pkgs/all`、`pkgs/ecmall` 2 个聚合入口 + `scripts` 与工作区配置 | +| 代码量 | `module/` 903 个 `.go`(177 个 `.http`、66 个 `.proto`、58 个 `.yaml`)、`pkgs/` 54 个 `.go` | +| 方法 | 只读静态分析:逐文件读 `cmd/ internal/ service/ proto/ etc/`,生成代码 `pb/` 仅看接口签名 | +| 输出 | 每服务一份 `docs/<服务名>.md`;本文与《产品与服务目录》为产品级汇总 | + +**审计约定**:结论均带 `文件:行号`;无法确认处以【信息不足】标注;修复建议只给最小、直接动作,**不含过度设计/过度封装方案**。 + +## 二、结论摘要 + +架构组织清晰(服务域划分、`service.Expose` 注入、聚合入口只做注册表取舍),但**实现质量与完整性存在系统性缺口**:本仓库当前状态更接近"可编译的接口骨架",而非可上线的生产系统。 + +- **认证与鉴权**:至少 5 个模块的验证码校验形同虚设(passport、mall);多处接口只校验"是否登录"而不校验"是否有权操作该资源"(address、cms、group、relation、feed、mall、market、feedback、order); +- **资金链路**:wallet 支付回调不验签、提现不扣减、余额扣减读后写,order 模拟支付/发货生产可达且优惠券计算写反,**存在直接资金损失路径**; +- **数据正确性**:多处引用模型中不存在的列名(`relation_id`/`creator_id`/`createor_id`/`enabled` 等)与错误键名,导致接口恒失败或静默返回空; +- **上线阻断**:聚合入口无生产配置、JWT/session 密钥默认值可直接启动、首批部署不建表不播种、`scripts/api-docgen` 无法构建; +- **测试**:全仓仅 21 个 `*_test.go`,base 多数模块、wallet、social 三服务**零单测**,上述缺陷几乎无自动化拦截。 + +## 三、严重问题台账 + +### P0 · 严重(认证绕过 / 资金损失 / 数据越权 / 上线即故障) + +| 编号 | 服务 | 问题 | 证据 | 影响 | +| --- | --- | --- | --- | --- | +| S1 | passport | 验证码登录/快捷登录/注册/找回**均不校验验证码** | `login/code.go:16-59`、`login/quick.go:20-64`、`register/code.go`、`forget/verify.go` | 任意手机号 + 任意 code 登录/批量建号,账号体系失守 | +| S2 | passport | 找回密码仅凭 `identity` 重置,且写**未加盐 MD5** | `forget/reset.go:17-41,31` | 任意账号接管;重置后密码无法通过 bcrypt 登录 | +| S3 | mgt | `AppKey` 登录**明文相等比对密码列** | `pub/login.go:147-159` | pass-the-hash,哈希泄露即可登录 | +| S4 | mgt | dev 配置注入默认管理员 `root/123456` | `etc/mgt_dev.yaml:16`、`internal/models/init_db.go:16` | 默认弱口令后台 | +| S5 | wallet | 支付回调**不验签**,用调用方传入状态置成功 | `internal/logic/payment/callback.go:16-31` | 客户端自证支付成功;渠道回调 `WxCallback` 反为 `// Todo` 不落库(`wechat/wx_callback.go:38`) | +| S6 | wallet | 提现**只校验余额不扣减**、无幂等键 | `internal/logic/basic/apply_cash.go:35-49` | 余额可重复开单超额提现;`ChargeWallet` 全仓无调用点→充值链路不通 | +| S7 | wallet | 余额扣减为读后写、`WHERE identity=?` 无条件更新 | `internal/logic/basic/wallet.go:53-63` | 并发双花 | +| S8 | order | **模拟支付/发货/收货生产可达**且硬编码支付参数 | `logic/summary/simulate_pay.go:25`、`proto/summary.proto` | 直接伪造支付并推进订单状态 | +| S9 | order | `OrderApprove` 完全无鉴权 | `logic/mgt/order_approve.go:17` | 匿名审批退款并改写库存 | +| S10 | order | 优惠券金额**做加法**加入订单总额 | `logic/summary/confirm.go:72` | 用户应付变高;归属/有效期/门槛不校验 | +| S11 | order | 扣库存不检查 `RowsAffected` | `logic/summary/submit.go:209-217` | 超卖 | +| S12 | mall | 员工验证码登录**不校验验证码** | `internal/logic/staff/login.go:58` | 任意手机号接管店铺账号 | +| S13 | mall | 店铺**支付/邮件凭据任意登录用户可读改** | `logic/store/get_payment.go`、`set_payment.go`、`get_email.go`、`set_email.go`、`get_setting.go` | 渠道凭据外泄/被篡改 | +| S14 | market | 审核**无鉴权**、开号/删号鉴权被整段注释 | `agency/approve.go:15-46`、`agency/create.go:20-23`、`agency/delete.go:17-21` | 自助开号 + 自助审核 + 匿名封停他人 | +| S15 | market | 登录令牌用 AES-CBC、鉴权用 HS256 JWT,互不兼容 | `agency/login.go:53` vs `ParseMetaCtx`;SDK `crypto/token/jwt.go:68` | 合法 token 过不了任何接口;解析失败后访问 nil Claims → panic 可打崩进程 | +| S16 | initial | `os` 参数**字符串拼进 ORDER BY** | `check/config_cache.go:21` | SQL 注入 | +| S17 | initial | 版本判断用字符串不相等即"有更新" | `check/updates.go:39` | 客户端版本高于服务端记录时被强制降级 | +| S18 | group | 入群审批**无角色校验**(丢弃调用者身份) | `member/join_do_handle.go:19,24-52` | 任意登录用户可拉人入群/拒绝申请 | +| S19 | group | `Modify` 重写群 `identity` 且 `UpdateColumns` 不过滤零值 | `basic/modify.go:33-35` | 群成员/申请立即脱钩,"群消失" | +| S20 | relation | 通过好友申请时**不与申请记录比对** | `friend/apply_do_pass.go:24-47` | 单方面把任意陌生人加为好友 | +| S21 | cms | 文章/评论 `Delete/Modify` **不校验归属** | `post/delete.go:29`、`modify_comment.go:33`、`delete_comment.go:24` | 任意登录用户删改他人内容 | +| S22 | address | `Get/Modify/Delete` **无 owner 约束** | `library/get.go:19,29`、`modify.go:39`、`delete.go:19,24` | 遍历自增 id 获取他人姓名/手机号/详细住址并可改删 | +| S23 | cloud | 空间 `KeyIdentifier` 全局唯一 + 写入字面量 `"default"` | `models/cloud_space.go:12`、`space/get.go:29-50` | 首名用户之后所有人拿不到空间 | +| S24 | cloud | 私人资料"加密"名不副实(key 由客户端传入、服务端不加密) | `logic/private/encrypt_data.go`、`create_private_data.go:54` | 服务端明文存"加密"数据 | +| S25 | ads / feedback / logs | 三个模块业务接口全挂**匿名组** | `logs/internal/routers/register.go:21-27`、`ads`、`feedback` | 可伪造写入、全量读取 | +| S26 | logs | `fetch` 键名不一致 + `level` 类型断言**双 panic**,分页参数失效,`create` IP 过滤写反 | `logic/log/fetch.go:42,45,33-53`、`create.go:21` | 接口 500 / 全表返回 / 防护失效 | +| S27 | feed | 动态**越权删改** + 私密动态泄露 | `post/remove.go:25`、`change.go:40`、`models/query.go:30-37,60-71` | 删除他人动态;`is_open=false` 内容仍被返回 | +| S28 | pkgs/all · ecmall | JWT 密钥默认值恰为 32 字节,直接通过长度校验 | `etc/default_dev.yaml:22`、`internal/config/config.go:66-69` | 可自行签发任意身份 token | +| S29 | pkgs/all · ecmall | grpc-gateway **404 与业务 NotFound 无法区分** | `internal/server/server.go:63-71` | 所有"记录不存在"分支的真实响应被 Gin 纯文本 404 覆盖 | +| S30 | sender | 腾讯云短信**空实现** `return nil,nil` | `internal/logic/sms/send.go:151-153` | 接口返回 200 + `null`,调用方误判成功 | + +### P1 · 高 + +| 编号 | 服务 | 问题 | 证据 | +| --- | --- | --- | --- | +| H1 | sender | 日发送量限制与短信黑名单**从未生效**(只有读、无写入点) | `sms/send.go:37,42-51` | +| H2 | sender | 验证码 `SetNX` 忽略返回值却发码;`Verify` 成功不删码可重放;用 `math/rand` | `sms/send.go:62-65,81,169`、`verify.go:28-32` | +| H3 | passport | 密码哈希用 `bcrypt.MinCost`(cost=4);Token 缓存键 `/TOKEN/` 与 `/token/` 不一致 | `register/do.go:30`、`account/set_password.go:35`、`vars/token.go:3` vs `register/do.go:84` | +| H4 | cloud | 容量配额只统计不限制;分享密码明文;`CreateShare` 不校验 `resource_id` 归属;根目录 `*ParentID` 空指针 panic | `space/*`、`logic/share/*`、`get_dir.go:91`、`get_dir_tree.go:97` | +| H5 | cms | 评论发布 `authName["name"].(string)` panic;赞踩无去重可刷负;`Search` 无分页上限 | `add_comment.go:35-40`、`query.go:346`、`incr_post_like.go:38` | +| H6 | address | `Fetch` 命名返回值未赋值恒返回空;默认地址唯一性无事务保障 | `library/fetch.go:23-40`、`create.go:41-45` | +| H7 | market | `Offset` 在归一化前计算、`Limit(0)` 致待审核列表恒空;三处 `Count` 结果被丢弃 | `agency/fetch.go:19,27-32`、`pending.go:31` | +| H8 | group | `creator_id`/`createor_id` 列不存在→核心接口恒返回 `ErrDB`、免验证入群永久失效 | `modify.go:35`、`do_quit.go:24`、`do_set_manager.go:25`、`models/query.go:39-43` | +| H9 | relation | `TagFetch` 必然 panic;`Fetch/ApplyFetch` 返回 `(nil,nil)`;7 个接口引用不存在的 `relation_id` 列;`Undo/DoIgnore` 无归属删除 | `tag_fetch.go:22`、`friend/fetch.go:47-49`、`delete.go:27` 等、`follow/undo.go:18,26`、`match/do_ignore.go:19,27` | +| H10 | feed | 分页写成 `Limit(int(page))` 且未回写;`DeleteComment` 是空函数;`action_op` 误写 `action_type` | `models/query.go:67,122-125,50` | +| H11 | wallet | 支付宝分支 `AliPay.Body` 为 nil 直接调用→必 panic;改支付密码不校验旧密码;微信 APIv3 密钥/商户号**明文入库**;`wallet_prod.yaml` 字段名不匹配无法启动 | `alipay/alipay.go:46-53`、`basic/set_pay_password.go:23-29`、`etc/wallet_prod.yaml:56` | +| H12 | order | 跨服务直连同库(`mall_product`/`mall_product_spec`/`mall_store`/`address_library`) | 8 个文件,散见 `logic/summary/*` | +| H13 | mall | 12 处写操作无店铺归属校验(商品/规格/图片/分类/广告/公告/运费/员工) | `product/item_modify.go:29`、`spec_delete.go:28` 等 | +| H14 | initial | `areas_cache.go` 查询模型不存在的列且 DB 错误被覆盖→静默空列表并缓存 30 分钟 | `areas_cache.go:33-35` | +| H15 | pkgs/all · ecmall | 无 prod 配置(`BSM_RuntimeMode=prod` 时 `log.Fatalf`);`build-all-linux.sh` 不覆盖聚合入口 | `pkgs/all/etc/` 仅 `default_dev.yaml`;`build-all-linux.sh:7,30` | +| H16 | pkgs/ecmall | 聚合路径不建表不播种,`Mgt.InitRootUser` 在聚合下不生效 | `service/*.go` 无 `NewImpl/AutoMigrate/models.New` 调用 | +| H17 | mock/order | `OrderModify` 占位但返回成功;social 三服务未接入聚合且 `Mux` 恒为 nil | `order/logic/mgt/order_modify.go`;`social/*/internal/server/new.go` | +| H18 | scripts | `scripts/api-docgen` 当前**无法构建**,且与工作区依赖版本长期漂移 6 处 | `scripts/api-docgen/generate.sh:5-7`;实测 `GOWORK=off go build ./...` 报 `updates to go.mod needed` | +| H19 | workspace | `go.work` 与 20 处 `go.mod` 把 SDK 指向**仓库外相对路径**且 `go.sum` 无校验和 | `go.work:26`、各 `go.mod` replace | +| H20 | scripts | `verify-workspace.*` 的 gofmt 门禁漏 `scripts/`、对空模块列表静默成功;缺 `.gitattributes` 致 CRLF 文件使门禁失败 | `verify-workspace.ps1/sh`;实测 `gofmt -l` 报 `module/base/fts/internal/routers/register_test.go` | + +### P2 · 中 + +- **feedback / market / order / mall 等多处列表接口无分页上限**(可全表扫描/拖库):`feedback` 各方法、`mgt/internal/tool/tool.go:5-14`、`mall`、`cms` 等。 +- **占位实现仍返回成功**:group `Basic.Disband`(`disband.go:26-31`)、order `OrderModify`、feed 4 类 Timeline + `Tag.PostList` + `Post.CommentList` + `Setting.Info/Rights`(9 个占位 RPC)、wallet 7 个占位 RPC、mall 入驻/授权/小程序码/运费、market `Data` 全 5 文件、cms `Site` 全系列、cloud 部分——统计见《产品与服务目录》。 +- **云解析/错误吞没**:passport `account/get.go:44` 未处理 error;relation `tag_member_fetch`;group `createor_id` `.Scan` 错误丢弃。 +- **GORM 链式未回写 `tx` / `First`+`Group` 语义不确定**:logs、feedback 等。 +- **调试残留**:`logs/routers/register.go:20` `fmt.Println`、feed 多处 `fmt.Print`。 +- **鉴权口径不一致**:模块 yaml 匿名白名单、聚合 `Authorization.Anonymous` 白名单与模块实际声明三者互不吻合(ads/feedback/passport 均出现)。 + +### P3 · 低 + +- 死代码与未用初始化:ads `RedisService/MemorySerice` 零引用;logs `RedisService/EtcdService` 未用、`Dependencies.Cache` 未应用;relation `follow.Do` 死代码。 +- `internal/excode/ex.go` 为空文件;多处 crate 级常量与配置注释不一致。 +- `test/grpc/main.go` 硬编码明文 JWT。 + +## 四、按服务域的问题分布 + +```mermaid +flowchart LR + A["认证与账号
passport · mgt · mall.staff"] --> A1["验证码不校验
默认密码
pass_hash"] + B["资金与订单
wallet · order"] --> B1["回调不验签
提现不扣减
优惠券算反
超卖"] + C["资源越权
address · cms · group · relation · feed · mall · market · feedback"] --> C1["只校验登录
不校验归属/角色"] + D["聚合与上线
pkgs/all · pkgs/ecmall · scripts"] --> D1["密钥默认值
404 分流
无 prod 配置
docgen 不可构建"] +``` + +## 五、共性根因 + +1. **鉴权只到"认证"不到"授权"**:大量写接口只判断调用者已登录,缺少"该资源是否属于调用者 / 调用者是否为管理员"的条件。越权类问题占本次 P0 的近一半,且分布在 9 个模块,属同一模式。 +2. **验证码体系形同虚设**:passport、mall 的登录/注册路径根本不调用验证码比对;sender 的码生成(`SetNX` 忽略返回值)、校验(成功不删码)、限额(无写入点)三处同时失效。**即使调用方正确接入,也无法形成有效校验**。 +3. **资金链路缺三要素**:无验签、无事务、无幂等。`wallet` 与 `order` 各自独立缺失,且模拟支付接口在生产可达。 +4. **配置与代码长期漂移**:yaml 字段名与 `SrvConfig` 不匹配(wallet/feed 需 `log.Fatalf`/`panic` 才能启动)、匿名白名单三处口径不一致、占位实现返回成功、生产配置缺失。这些在"能编译/能 dev 跑"阶段不会暴露。 +5. **列名/键名错误被吞没**:模型与实际表列不一致(`relation_id`/`creator_id`/`createor_id`/`enabled`/`show_town`/`sort_order`),叠加 `.Scan` 错误被丢弃、命名返回值 `reply` 未赋值,形成"接口 200 但返回空/报错被掩盖"的静默故障。 +6. **测试覆盖极低**:全仓 21 个 `*_test.go` 集中在 `fts`/`mgt`/`sender`/`mall`/`market`/`order`/`pkgs`;`base` 多数模块、`wallet`、`social` 三服务零单测。上述缺陷几乎没有任何自动化拦截。 + +## 六、修复优先级路线(务实) + +按"止血 → 阻断上线 → 修恒失败 → 补护栏"推进,全部为对现有函数/配置的最小改动: + +1. **资金与认证止血(最高优先)** + - wallet:回调按渠道验签、金额与服务端记录核对、加幂等键;提现改为事务内扣减/冻结;余额扣减改条件更新(`WHERE balance >= ?`)。 + - passport / mall:登录、注册、找回路径补回验证码比对;`Forget.Reset` 必须校验一次性令牌。 + - mgt:移除默认管理员密码,改必填环境变量;`AppKey` 登录改为哈希比对。 + - order:`Simulate*` 与 `OrderApprove` 加鉴权与生产开关(默认关闭);优惠券改为减法并校验归属/门槛/有效期;扣库存检查 `RowsAffected`。 +2. **越权批量化修复** + - 统一给"按主键操作"的写接口补条件:address/cms/group/relation/feed/mall/market/feedback 分别补 `passport_id`/`owner`/店铺归属/群管理员角色。 +3. **上线阻断项** + - pkgs:修复 404 分流判定(先读业务响应再回退);JWT/session 密钥去除可用默认值;补 prod 配置;聚合路径补建表/播种。 + - scripts:修复 `api-docgen` 构建、纳入工作区或固定版本、gofmt 门禁补齐,SDK 改为可解析的版本依赖或明确文档化本地前提。 +4. **恒失败接口修复** + - 修正不存在的列名/键名(group/relation/initial/feed/logs),修复命名返回值 `reply` 未赋值(address/relation),补齐 `main.go` 的 HTTP Mux 与聚合注册(social)。 +5. **补护栏** + - 优先为认证、权限、支付、订单状态机、库存、验证码写单测;CI 增加 workspace 验证、凭据扫描、proto 一致性与依赖漏洞扫描。 + +## 七、正向结论 + +- **密码哈希**:market、mall 的账号创建/登录/改密已从 MD5 加盐迁移到 bcrypt(历史 MD5 需通过重置流程迁移)。 +- **mgt 鉴权**:管理路由已正确挂载 `RequireAdmin`→`IsSuperAdmin`(按 DB 回查超级管理员角色),模块内未见绕过;其短信重置密码实现正确校验 Redis 验证码,与 passport 形成对比。 +- **cloud 数据隔离**:所有 DB 查询均为 GORM 参数化(无 SQL 拼接/注入),接口以 `passport_id = auth.ID` 隔离,未发现跨用户越权读取他人目录/相册/笔记/书签/私人资料。 +- **pkgs 动态 RPC**:具备 4 MiB 请求体上限、头部白名单透传、方法描述符缓存;两个聚合入口有针对鉴权/动态 RPC/配置/路由的单元测试。 +- **2026-08-10 审计的整改**:绝对路径 `replace`、样例固定 secret 已处理(本次替换为仓库外相对路径,可移植性问题转为 P1)。 + +## 八、与 2026-08-10 审计报告的关系 + +`wiki/audit-2026-08-10.md` 聚焦"workspace 结构、样例凭据、进程与端口防护",其列出的 P0/P1 多数已整改。本报告在其基础上**下沉到业务实现层**,发现的 P0 数量与严重度显著高于前次——原因是前次未逐接口审计业务逻辑。两份报告不冲突,应合并为同一整改清单跟踪。 diff --git a/wiki/产品与服务目录.md b/wiki/产品与服务目录.md new file mode 100644 index 0000000..2038398 --- /dev/null +++ b/wiki/产品与服务目录.md @@ -0,0 +1,109 @@ +# 产品与服务目录 + +> 产品级视图:服务域划分、服务清单、对外协议、实现完成度、跨域耦合与运行拓扑。 +> 逐服务审计明细见 [`docs/`](../docs/README.md);风险与优先级见 [`audit-2026-09-22-总体审计报告.md`](audit-2026-09-22-总体审计报告.md)。 + +## 一、产品定位 + +BSM Infra Full 是 BSM 后端服务的 Go workspace,按**四个服务域**组织,提供账号与身份、后台权限、内容与运营、文件与个人云空间、电商交易、资金支付、社交关系等能力。既有"每服务独立进程"的部署形态(`module/*/cmd/main`),也有"多服务单进程聚合"的部署形态(`pkgs/all` 全量、`pkgs/ecmall` 电商)。 + +## 二、服务域与服务清单 + +| 服务域 | 服务 | 模块路径 | 主要职责 | 对外协议 | 聚合归属 | +| --- | --- | --- | --- | --- | --- | +| 基础与平台 | passport | `module/base/passport` | 账号、密码/验证码登录、找回、KYC 核验 | gRPC + gateway | all、ecmall | +| 基础与平台 | mgt | `module/base/mgt` | 后台登录、组织/角色/权限/部门 | 原生 REST `/rest/mgt` | all、ecmall | +| 基础与平台 | initial | `module/base/initial` | 服务探测、客户端配置、版本、国家/区划/字典 | gRPC + gateway | all、ecmall | +| 基础与平台 | sender | `module/base/sender` | 短信、邮件、验证码生成与校验 | gRPC + gateway | all、ecmall | +| 基础与平台 | fts | `module/base/fts` | 文件上传(本地/MinIO)、上传配置 | 原生 REST `/rest/fts` | all、ecmall | +| 基础与平台 | cloud | `module/base/cloud` | 网盘、相册、笔记、书签、私人资料、分享、空间 | gRPC + gateway | 仅 all | +| 基础与平台 | cms | `module/base/cms` | 文章、页面、分类、标签、评论、赞踩 | gRPC + gateway | all、ecmall | +| 基础与平台 | ads | `module/base/ads` | 按广告位分发广告素材 | gRPC + gateway | all、ecmall | +| 基础与平台 | feedback | `module/base/feedback` | 意见反馈的增删改查与备注 | gRPC + gateway | all、ecmall | +| 基础与平台 | logs | `module/base/logs` | 业务操作日志写入与查询 | 原生 REST `/rest/logs` | all、ecmall | +| 电商与交易 | address | `module/ec/address` | 收货地址簿 | gRPC + gateway | all、ecmall | +| 电商与交易 | mall | `module/ec/mall` | 店铺、商品、规格、图片、分类、广告、公告、运费、员工 | gRPC + gateway | all、ecmall | +| 电商与交易 | market | `module/ec/market` | 代理商、供应商、审核 | gRPC + gateway | all、ecmall | +| 电商与交易 | order | `module/ec/order` | 购物车、下单、拆单、订单状态、优惠券 | gRPC + gateway | all、ecmall | +| 资金与支付 | wallet | `module/finance/wallet` | 钱包、流水、银行卡、提现、支付渠道、回调 | gRPC + gateway | all、ecmall | +| 社交 | relation | `module/social/relation` | 好友、标签、关注、匹配 | gRPC + gateway | **未接入** | +| 社交 | group | `module/social/group` | 群组、成员、入群申请 | gRPC + gateway | **未接入** | +| 社交 | feed | `module/social/feed` | 动态、标签、评论、互动计数、时间线 | gRPC + gateway | **未接入** | +| 聚合 | all | `pkgs/all` | 15 服务聚合宿主(gRPC 12000 / HTTP 12001) | gRPC + HTTP 多路 | — | +| 聚合 | ecmall | `pkgs/ecmall` | 14 服务电商聚合宿主(gRPC 12300 / HTTP 12301) | gRPC + HTTP 多路 | — | + +预留/空目录(不计入已实现服务):`module/ec/supply`(仅 README)、`module/finance/bill`、`module/finance/loan`(空目录)。 + +## 三、实现完成度 + +> "占位"指接口已声明但逻辑为空/返回成功/TODO。以下为本次核实结论,明细见对应 `docs/*.md`。 + +| 服务 | 完成度 | 已确认的占位/缺口 | +| --- | --- | --- | +| cloud | 方法齐全 | 全部 gRPC 方法均有实现;但容量配额只统计不限制、分享链路断裂、私人资料未真正加密 | +| passport | 方法齐全 | `Verify.JumioCallback` 为空 stub;`Forget.Verify` 为伪实现 | +| sender | 部分 | **腾讯云短信空实现**(`TencentSender`);邮件仅 `qq` 渠道 | +| cms | 部分 | `Site` 全系列占位(get/fetch/create/modify/delete 均为 TODO) | +| feed | 大量占位 | 4 类 Timeline(recommend/hot/friend/follow)、`Tag.PostList`、`Post.CommentList`、`Setting.Info`、`Setting.Rights` 共 9 处 | +| wallet | 大量占位 | 支付宝 page/app/wap 支付与转账、微信 APP/Native 下单与转账共 7 处 TODO;`WxCallback` 验签后无业务逻辑 | +| mall | 部分 | 入驻、授权、小程序码、按规格查询、商品批量操作、运费与禁运区域占位;员工验证码登录明确未实现 | +| market | 部分 | `Data` 概况/会员/订单统计 5 个文件全部占位 | +| order | 部分 | 管理端 `OrderModify` 占位;`SimulatePay/SimulateShipments/SimulateReceiving` 为模拟实现(生产可达,见总体报告 S8) | +| group | 部分 | `Basic.Disband` 占位但返回成功 | +| relation | 方法齐全 | 无占位函数,但存在遗留死代码 `follow.Do` | +| ads | 方法齐全 | 仅 1 个只读接口;README 宣称的 Redis/内存缓存**未实现** | +| initial / fts / logs / address / mgt / feedback | 方法齐全 | 无占位,但存在本报告所列的越权/鉴权/分页/键名等缺陷 | + +## 四、跨域耦合 + +| 耦合 | 说明 | 影响 | +| --- | --- | --- | +| order → mall / address 数据表 | `order` 直接读写 `mall_product`、`mall_product_spec`、`mall_store`、`address_library`(散落 8 个文件) | 任一表结构变更即运行期报错;订单侧可直接改商品库存 | +| passport ↔ sender | 验证码生成/校验依赖 sender;但 passport 登录/注册路径实际未调用校验 | 验证码形同虚设 | +| mall ↔ wallet / order | 店铺支付配置由 mall 维护、支付执行在 wallet、订单在 order | 店铺凭据当前可被任意登录用户读改 | +| cloud ↔ fts | cloud 维护文件/照片业务记录,fts 执行实际二进制上传 | 两者未自动组成完整上传链路 | +| 聚合入口 | `pkgs/all`、`pkgs/ecmall` 复用 `module/` 同一份代码,仅注册表与端口不同;两入口 `internal/server` 4 个文件逐字节相同 | 任一入口的修复需同步两份拷贝 | + +## 五、运行拓扑 + +```mermaid +flowchart TD + C["客户端 / 后台"] --> GA["pkgs/all
gRPC 12000 / HTTP 12001"] + C --> GE["pkgs/ecmall
gRPC 12300 / HTTP 12301"] + C --> S1["module/*/cmd/main
独立服务进程"] + GA --> M1["module/base/* 10"] + GA --> M2["module/ec/* 4"] + GA --> M3["module/finance/wallet"] + GE --> M4["module/base/* 9
不含 cloud"] + GE --> M2 + GE --> M3 + M1 --> DB[("共享 MySQL")] + M2 --> DB + M3 --> DB + M1 --> R[("共享 Redis / etcd")] + S2["module/social/*
未接入聚合"] --> DB +``` + +`pkgs/all` 与 `pkgs/ecmall` 的 HTTP 入口依次承载:grpc-gateway → 动态 RPC `/rpc/{package}/{service}/{method}` → 原生 REST `/rest/{module}/...`。请求先进入 grpc-gateway,返回 404 时回退 Gin(此回退判定存在缺陷,见总体报告 S29)。 + +## 六、配置与运行入口 + +| 入口 | 配置 | 端口 | 备注 | +| --- | --- | --- | --- | +| `pkgs/all` | `etc/default_.yaml` | gRPC 12000 / HTTP 12001 | **仅有 dev 配置**,prod 模式启动即 `log.Fatalf` | +| `pkgs/ecmall` | `etc/default_.yaml` | gRPC 12300 / HTTP 12301 | 同上 | +| 各业务模块 | `etc/_.yaml` | 各自 `Port` | 部分模块(wallet、feed)yaml 字段名与 `SrvConfig` 不匹配,需修配置才能启动 | + +服务启用由聚合 `Services` 列表或环境变量 `BSM_SERVICES` 控制(非空则覆盖;值为 `all` 时注册全部)。 + +## 七、文档地图 + +| 文档 | 位置 | 内容 | +| --- | --- | --- | +| 代码审计索引 | [`docs/README.md`](../docs/README.md) | 21 篇逐服务审计报告入口 | +| 总体审计报告 | [`audit-2026-09-22-总体审计报告.md`](audit-2026-09-22-总体审计报告.md) | 风险台账、共性根因、修复优先级 | +| 本文 | `产品与服务目录.md` | 服务域、清单、完成度、耦合、拓扑 | +| 系统架构 | [`architecture.md`](architecture.md) | 模块边界、聚合启动流程、依赖注入 | +| 开发与运维 | [`operations.md`](operations.md) | 配置、启动、验证、构建、发布、排障 | +| API 接入 | [`api/00-overview.md`](api/00-overview.md) | gRPC / 动态 RPC / gateway / 鉴权 | +| 历史审计 | [`audit-2026-08-10.md`](audit-2026-08-10.md) | 上一轮 workspace 层审计 |