adult and update go.mod

This commit is contained in:
2026-09-22 18:53:53 +08:00
parent db931a7c40
commit 9f86366638
74 changed files with 5796 additions and 1018 deletions

186
docs/address.md Normal file
View File

@@ -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/<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 有效"**没有角色参数**SDK `service/meta.go:36-45` 只有在 `opts.RoleValue` 非空时才校验角色,此处传 nil
- 独立进程部署时,模块自身 `grpc.NewServer()` 未安装任何拦截器(`internal/server/new.go:23`gRPC 端口监听在 `CheckIP(BindIP)` 得到的主机内网 IPSDK `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` 传给 SDKSDK `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` 与一个数据库唯一索引即可闭环。