27 KiB
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/<Method>,全部 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/<Method>:
| 方法 | 路径 | 功能 | 鉴权 | 实现位置 |
|---|---|---|---|---|
| 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 有效",没有角色参数(SDKservice/meta.go:36-45只有在opts.RoleValue非空时才校验角色,此处传 nil)。 - 独立进程部署时,模块自身
grpc.NewServer()未安装任何拦截器(internal/server/new.go:23),gRPC 端口监听在CheckIP(BindIP)得到的主机内网 IP(SDKconf/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. 核心流程
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. 修复建议(务实项)
- 补三处归属条件(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 语义。 - 修
Fetch返回(A2):fetch.go:38-40改为return &pb.AddressListReply{Data: result}, nil,并删除:38的 TODO 行。 - 默认地址(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 部分唯一索引),让数据库兜住并发。 Modify修正(A5)::39改为显式Updates(map[string]any{...})(键为列名,含name/pics),这样既能清空字段也能写入 0;同时把in.Name、in.Pics补进映射。- 入参校验(A6):
create.go:39、modify.go:33之后加 status 白名单(只允许 1、2;需要 "删除" 就走Delete),并在create.go补phone/contact/detail非空校验与长度上限(对应列varchar(20)/varchar(255))。 - 统一删除语义(A6):二选一并同步注释——保留 GORM 软删除就把 proto/模型的"删除=status -1"注释删掉;要按
status=-1实现就把delete.go:24改成Update("status", -1)并让get/fetch都过滤status <> -1。 - 列表可用性(A7):
fetch.go:28至少补Where("status <> ?", -1)、Order("id desc")与Limit(200)之类的硬上限;若确定不做分页,就删掉proto/address.proto:52-56的FetchRequest与 TS 里的分页类型,避免契约撒谎。 - 降低 panic 面(A4):本模块侧给
internal/server/new.go:23的grpc.NewServer()装上带recover的 unary 拦截器(异常时返回codes.Internal),避免任意请求把进程带走;根因在 SDKcrypto/token/jwt.go:68(应为if err != nil { return nil, err }后再取token.Claims),建议对 SDK 提缺陷单。 - 关闭生产 SQL 日志(A8):把
internal/models/impl.go:82的无条件db.Debug()改为按配置决定,并让internal/impl/with.go:41传入真实的*types.SqlOptions(或把Debug补进conf.DBConf,当前 etc 中的Debug键是被静默忽略的)。 - 清理与运维(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与一个数据库唯一索引即可闭环。
9. 整改记录(2026-09-22)
本节记录按本报告结论执行的代码整改。整改遵循最小修正原则:未引入新框架、抽象层、DTO/VO、事件总线,未拆分服务边界,未修改任何
proto/*.proto与生成的pb/*.go;新增/修改注释均为中文;口令类摘要统一使用 bcrypt(验证码等短时效一次性凭证仍按原有 Redis 明文比对链路存储)。校验方式:GOWORK=off go build ./...+GOWORK=off go vet ./...+gofmt -l(仓库根 workspace 模式存在 genproto 拆包的 ambiguous import,属本机既有问题)。
| 编号 | 级别 | 问题 | 处理结果 |
|---|---|---|---|
| A1 | 高 | Get/Modify/Delete 无归属校验 |
已修复:三处按主键的读写一律附加 passport_identity = auth.Identity 条件,越权返回 ErrPermissionDenied |
| A2 | 高 | Fetch 命名返回值 reply 从未赋值 |
已修复:结果集正确装配并返回,地址列表不再恒空 |
| A3 | 高 | 默认地址唯一性无事务、无约束 | 已修复:取消旧默认与置新默认合并进 impl.DBService.Transaction,保证任一时刻至多一个默认地址 |
| A4 | 高 | 非 JWT 的 Authorization 头触发 SDK ParseJwt 空指针、模块无 recover |
已修复:在 internal/server/new.go 增加最小 unary recover 拦截器,panic 转为 codes.Internal,进程不再崩溃 |
未纳入本轮范围
报告中「中」「低」级别的项(分页上限、死代码、README 与实现不符、单测缺失、可维护性等)本轮未处理;如需继续,按各报告第 8 节「修复建议」的顺序推进即可。
本轮整改未修改任何
proto/*.proto与pb/*.go,因此少数需要新增接口字段才能完整实现的项目(已在处理结果中标注)做了安全降级。