This commit is contained in:
2026-08-05 16:26:10 +08:00
parent 06f7f9d2f2
commit 5ba9716e12
155 changed files with 2883 additions and 551 deletions
@@ -82,7 +82,7 @@ mvn -o "-Dmaven.repo.local=C:\Users\25137\.m2\repository" -pl iam,warehouse,orde
2. 在宿主机预建 File 上传目录和 11 个日志目录,确认 UID 10001 可以创建和删除文件。
3. 确认 Nacos 中只有当前版本实例,并验证 Gateway 到 `file-service`、`warehouse-service` 的负载均衡路由。
4. 使用受限商户成员重放新增和编辑成员请求:携带角色但无授角权限必须返回 403;角色不变的资料编辑必须成功。
5. 使用启用仓库、启用往来单位创建头程草稿;再分别验证不存在、跨租户、停用和重复提交。
5. 使用平台公共作用域 `0` 的启用仓库、启用往来单位创建头程草稿;再分别验证不存在、非平台公共作用域、停用和重复提交。
6. 上传一张脱敏营业执照并完成商户、平台预览;不得继续使用 `/phase1-seed/...` 路径。
7. 从 `http://localhost:5174` 直连 Gateway 验证登录预检、登录、401、403 和非白名单来源。
8. 检查宿主机固定日志目录,确认每个服务产生独立日志文件并按策略滚动。
@@ -42,7 +42,7 @@
- Firstleg 增加任务分页、详情、按订单查询、分配历史和分配/改派权限,所有仓储查询均带目标租户。
- `idx_task_tenant_created` 同时写入新库建表脚本和可重复执行的既有库增量迁移。
- Warehouse 增加租户级往来单位、快递公司分页、详情、新增、编辑和启停;停用保留历史主数据,不物理删除。
- Warehouse 增加往来单位、快递公司分页、详情、新增、编辑和启停;该报告产生时曾按客户作用域实现,现已于 2026-08-05 统一改为平台公共作用域 `0`,停用仍保留历史主数据。
- IAM 同步新增六个 Warehouse 按钮权限及四个 Firstleg 权限,并保持平台管理员、订单运营角色授权一致。
## 2. 自动化回归
@@ -14,7 +14,7 @@
- 平台客户档案分页、详情、编辑、启停,并阻止通过启停接口绕过企业审核。
- 平台内部用户的成员主键和角色回显,以及新增、编辑、启停、重置密码、角色分配闭环。
- 权限资源编辑、启停、删除;系统资源、存在子节点或已绑定角色的资源禁止删除。
- 平台快递公司和往来单位的分页、详情、新增、编辑、启停,所有读写强制携带目标租户。
- 历史实现曾要求平台快递公司和往来单位的所有读写携带目标租户;该口径已于 2026-08-05 废止,两类主数据现统一归属平台作用域 `0`。
- 平台代客创建、编辑、提交头程订单;商户端和平台端创建、提交均消费 `Idempotency-Key`。
- 订单不存在统一返回 HTTP 404;Warehouse 下游不可用统一映射为受控 503。
- Firstleg 履约任务列表、详情、按订单查询、分配历史和分页查询。
@@ -34,7 +34,7 @@
本地 File 存储目录 `D:\baiyue-firstleg-runtime\uploads` 已存在租户 `10001` 的 UUID 资质图片。使用商户 Token 请求 `/api/merchant/v1/files/qualifications/a1d6caa0-14df-4988-831c-d1a8f5d142ff.png` 返回 HTTP 200、`image/png`、47772 字节。旧 `/phase1-seed/demo-business-license.jpg` 记录状态已是 `SUPERSEDED`,不再作为当前有效资质。
关键索引只读核验结果:订单幂等唯一索引、Firstleg 租户任务索引、往来单位和快递公司的租户编码唯一索引均已存在。
关键索引只读核验结果:订单幂等唯一索引、Firstleg 客户隔离任务索引、往来单位和快递公司的兼容作用域编码唯一索引均已存在;后两者当前固定使用平台公共作用域 `0`。
迁移实测发现 MySQL 不支持 `ADD COLUMN IF NOT EXISTS`,IAM 与 Warehouse 增量脚本已改为 `information_schema + 动态 SQL` 的可重复写法,并在本地成功执行。远程测试环境部署时仍必须核对迁移版本;健康检查成功不能证明业务表结构已同步。
@@ -67,7 +67,7 @@ mvn -o "-Dmaven.repo.local=C:\Users\25137\.m2\repository" clean test
- 商户端、平台端统一登录均成功;平台权限快照返回 61 项权限,不依赖角色列表权限。
- 平台客户分页返回 16 条;内部用户返回 `membershipId` 和 `roleIds`,ID 的 JSON 类型为字符串。
- 商户仓库、往来单位候选、平台仓库、平台往来单位、平台快递公司均返回真实本地数据;19 位仓库 ID 保持 `190000000000000001`,未发生精度截断。
- 平台往来单位和快递公司均完成新增、编辑、停用、详情回读;测试记录随后按租户和精确 ID 清理。
- 平台往来单位和快递公司均完成新增、编辑、停用、详情回读;该历史回归当时按客户作用域清理,当前两类主数据已统一归平台公共作用域 `0`。
- 商户头程订单完成创建草稿、相同 `Idempotency-Key` 重放返回同一订单 ID、提交、相同提交键重放、详情读取和取消;测试订单及子表、Outbox、幂等记录随后精确清理。
- 不存在订单返回 HTTP 404;Order OpenAPI 已声明必填 `Idempotency-Key`,`OrderEntity.id` 类型为 `string`,平台提交路径正确。
- 有效资质文件鉴权预览返回 HTTP 200,文件内容与元数据路径一致。
@@ -0,0 +1,68 @@
# Java 单租户多客户设计审计汇总结论
## 结论
当前 Java 主体模型整体符合“业务单租户、平台管理多客户、客户严格隔离”:
- `iam_tenant` 的 `MERCHANT` 记录当前可以继续直接作为客户主档。
- Auth/JWT 和各业务表的 `tenant_id > 0` 当前承载客户 ID,是客户隔离键。
- 平台技术作用域和 RBAC 模板使用 `tenant_id = 0`。
- Order/Firstleg 中的 `customer_id` 多数承载往来单位或业务客户快照,不是 IAM 客户隔离 ID。
- 未发现可通过当前正常公开链路直接完成跨客户读写的 P0,不需要为上线重构全部主体模型。
## 已完成的上线前 P1
1. 公共仓库、往来单位、快递公司和部分种子数据使用 `tenant_id=10001`,而 IAM 又把 `10001` 建成普通 `MERCHANT` 演示客户。现已确认平台公共仓库、往来单位、快递公司、托盘、库位等公共资源统一归属平台技术作用域 `tenant_id=0`;必须同步新库种子、配置、消费者和可重复执行的存量迁移,客户订单、文件和费用等私有数据不得随之改为 `0`。
2. Warehouse 存量子表迁移新增 `tenant_id` 时直接默认填 `0`,却没有按父仓库或父记录回填,可能导致存量地址、能力、往来单位和快递公司丢失归属。
3. 统一迁移脚本在真正结束前已经输出 `PASS`,后面仍继续建表;成功标志和最终结构校验必须收敛到脚本末尾。
4. IAM/Auth 的部分内部 Controller 在内部令牌为空时可能把“空配置”和“缺失请求头”比较为相等,必须统一启动失败校验。
5. 平台身份只检查 `app_code=PLATFORM`,部分 Gateway/模块守卫没有再次断言登录主体 `tenant_id=0`;客户身份也应断言 `tenant_id>0`。
6. 平台代客业务审计当前可能记录平台主体 `tenant_id=0`,导致按目标客户查询审计日志漏记;应同时保留操作者作用域和目标客户归属。
7. Integration 内部令牌存在可预测默认值,必须改为环境显式注入并保持启动失败保护。
8. 模块级 `AGENTS.md`、README、分期文档和 OpenAPI 仍把兼容 `tenant_id` 描述成真实上级租户,三期存在重新按三级租户开发的返工风险。
## P2 最小加固
- 删除或拒绝 Gateway 未分端域的 `/api/iam/**` 通用路由。
- Auth 校验 IAM 授权快照回显的 `userId/tenantId/membershipId/appCode` 与请求完全一致。
- IAM 商户资料读取和修改再次限定 `tenant_type=MERCHANT`。
- Order/Firstleg/Warehouse/File 的平台守卫统一校验平台主体为 `tenant_id=0`。
- 对少量 `updateById`、按裸主键删除、Partner 类型关联操作增加仓储层客户条件。
- Order Outbox 投递前核对事件行和 Payload 的客户、订单及聚合类型。
- Event Bridge 重复事件比较同时核对客户和信封身份字段。
- File 平台测量图片上传增加目标入库单客户归属校验,避免孤儿或错误归属文件。
- 公共 OpenAPI 不再把可信身份头描述成普通客户端请求头。
- 增加客户 A/B、异常平台 Token、内部 API 错误客户、Outbox 归属漂移和迁移结果的负向测试。
- 修正 Order 取消事件长期停留 `PENDING` 的非隔离功能问题,或明确当前不发布该事件的终态。
## 本轮不实施
- 不新增真实租户表、租户切换、租户级 RBAC 或租户级配置。
- 不批量重命名 `tenant`、`merchant`、`Tenant*`、`tenant_id`。
- 不把所有现有 `tenant_id` 统一成默认租户 ID。
- 往来单位与快递公司均由平台联系和统一维护,`wh_partner`、`wh_partner_type` 与其他公共仓储主数据一起迁移为 `0`;客户订单、文件、费用等私有数据保持客户 ID。迁移必须按明确的公共资源表和父子关系执行。
- 不触碰生产环境;测试环境已按用户明确授权执行统一增量 SQL,并完成迁移后断言。
## 测试环境迁移结果
- 统一增量 SQL 已在测试 MySQL 执行,最终输出 `migration_status = PASS`。
- `wh_partner` 4 行、`wh_partner_type` 8 行、`wh_express_company` 4 行均已归平台公共作用域 `0`,非平台作用域违规数均为 0。
- 公共仓库、地址、能力、托盘、库位、预占和占用的非平台作用域违规数均为 0。
- 订单、文件和费用等客户私有表的 `tenant_id = 0` 违规数均为 0。
## 验证结果
- `mvn test`:19 个 Reactor 模块全部 `SUCCESS`,共执行 634 项测试,0 失败、0 错误、1 项跳过。
- 重点业务模块:IAM 128 项、Order 67 项、Firstleg 112 项、Warehouse 102 项、File 81 项均通过。
- 其余服务:Print 35 项(1 项跳过)、Audit 15 项、Integration 11 项、Event Bridge 10 项均通过。
- `mvn -DskipTests validate` 与 `git diff --check` 均通过。
## 第二轮文件所有权
- 身份安全:`iam/`、`auth/`、`gateway/`、`common-security/` 的生产代码与测试。
- 订单履约:`order/`、`firstleg/` 的生产代码与测试,不修改 SQL、OpenAPI、README、AGENTS。
- 公共审计事件:`common-audit/`、`audit-service/`、`integration-service/`、`event-bridge-service/`、`common-openapi/` 的生产代码与测试。
- Warehouse/File/迁移:`warehouse/`、`file/` 生产代码与测试,以及各模块数据库脚本、`deploy/sql/`、`config/nacos/`;平台公共资源(含往来单位、快递公司)作用域迁移为 `0`,客户私有资源保持客户 ID。
- 文档契约:模块 `AGENTS.md`、README、OpenAPI 和一期/二期/三期指导性文档;不修改 Java 或 SQL。
跨模块新接口、公共 DTO、事件结构和公共仓库作用域变更由主对话统一裁决,单个修改对话不得自行扩展范围。
@@ -0,0 +1,130 @@
# Java 单租户多客户设计审计计划
> 状态说明:本文保留第一轮只读审计的原始计划。用户随后明确授权修改代码、初始化 SQL 与测试环境数据;实际执行结果以同日《Java 单租户多客户设计审计汇总结论》和统一增量 SQL 为准。
## 一、目标
对 `baiyue-firstleg/` 全部 Java 服务、数据库脚本和跨服务契约做一次系统性只读审计,确认当前实现符合根 `AGENTS.md` 已确定的阶段模型:
- 当前业务单租户,平台管理多个客户,客户之间严格隔离。
- `iam_tenant` 中 `id > 0 && tenant_type = MERCHANT` 的记录当前直接作为客户主档。
- Auth、JWT 和业务表中的 `tenant_id > 0` 当前承载客户 ID,是客户隔离键。
- `tenant_id = 0` 是平台或 RBAC 模板技术作用域,不是未来的默认租户。
- 平台公共仓库、往来单位、快递公司、托盘、库位等公共资源归属平台技术作用域 `tenant_id = 0`;客户订单、文件和费用等私有数据仍使用客户 ID。
- 当前不新增真实租户层,不重命名现有兼容字段,不做大规模架构改造。
审计完成后,主对话统一汇总结论,再以最小改造原则安排修改。第一轮各审计对话一律只读,不修改任何文件。
## 二、非目标
- 不在本轮设计或实现真实多租户。
- 不把现有 `tenant_id` 统一替换为默认租户 ID。
- 不批量重命名 `tenant`、`merchant`、`Tenant*`、`tenant_id`。
- 不因为未来可能支持多租户而提前增加租户表、租户切换、租户级 RBAC 或租户级配置。
- 第一轮审计阶段不修改测试环境数据;后续已按用户在当前对话中的明确授权执行统一迁移。
## 三、统一审计判定标准
每个业务入口和数据链路都按以下问题检查:
1. 平台账号是否固定为 `app_code = PLATFORM && tenant_id = 0`。
2. 客户账号是否固定为 `app_code = MERCHANT && tenant_id > 0`,成员关系是否属于同一客户。
3. 商户端是否只从可信认证上下文取得当前客户 ID,而不是信任请求中的目标 `tenantId`。
4. 平台端跨客户查询或代客操作是否同时校验平台应用域、权限码和目标客户条件。
5. Repository、Mapper、SQL 是否在客户数据的查询、更新、删除和唯一约束中携带当前客户 ID。
6. 子表、历史、审计、Outbox、幂等、文件和缓存键是否继承主聚合的客户 ID。
7. 内部 API、Feign DTO 和事件是否完整传递客户 ID,并在被调用方再次按归属校验。
8. `tenant_id = 0` 是否只用于平台或全局模板,是否被错误当成客户或未来默认租户。
9. `customer_id`、`data_scope_id`、`operator_tenant_id`、`public-tenant-id` 等相邻字段是否被误用为 IAM 客户隔离键。
10. 是否存在通用路由、可选过滤、按主键裸查、跨库查询或平台/商户端域混用造成的跨客户访问风险。
11. 当前实现是否偷偷引入真实租户层,或制造一年后无法通过新增上级租户字段迁移的不可逆结构。
12. 自动化测试是否至少覆盖客户 A 不能读取或修改客户 B 数据,以及平台合法跨客户操作。
## 四、第一轮多对话分工(全部只读)
### 对话 A:IAM、Auth、Common Security、Gateway
负责目录:`iam/`、`auth/`、`common-security/`、`gateway/`。
重点检查账号归属、注册、成员、角色权限模板、授权快照、JWT Claim、刷新撤销、可信身份头、平台/商户访问域和 Gateway 路由。确认平台 `0`、客户 `>0` 的规则在所有入口一致,检查历史账号脏数据风险及通用路由绕过。
### 对话 B:Order 客户隔离
负责目录:`order/`。
重点检查商户下单、平台代客、列表详情、状态流转、货物/参与方/历史/备注、幂等、Outbox、IAM 资格查询和 Warehouse 主数据调用。必须区分当前隔离键 `tenant_id` 与往来单位语义的 `customer_id`。
### 对话 C:Firstleg 履约与二期入库
负责目录:`firstleg/`。
重点检查履约任务、入库单、测量、审核、FBA 计费、费用发布、托盘库位协作、第三方测量、内部接口、幂等和 Outbox。核对所有状态推进、详情和图片链路是否使用同一个客户 ID。
### 对话 D:Warehouse 与 File 资源归属
负责目录:`warehouse/`、`file/`。
重点检查仓库、往来单位、快递公司、托盘、库位、预占占用、资质文件、测量图片和导出任务。单独核对 `DEFAULT_TENANT_ID` / `public-tenant-id` 是否固定为平台作用域 `0`,确保公共仓库、往来单位和快递公司等资源不再绑定普通客户 `10001`;客户文件仍按客户隔离。
### 对话 E:其余服务与公共契约
负责目录:`common-core/`、`common-web/`、`common-audit/`、`common-event-contract/`、`common-openapi/`、`audit-service/`、`integration-service/`、`event-bridge-service/`、`print/`。
重点检查公共实体、审计上下文、事件信封、内部令牌、OpenAPI 自动配置、第三方适配和打印下载链路是否丢失、混淆或信任外部提交的客户 ID。
### 对话 F:数据库、迁移与部署配置
只负责各模块 `src/main/resources/db/`、`deploy/sql/`、`deploy/`、`config/nacos/`、相关初始化脚本,不审计 Java 业务实现。
重点检查表、索引、唯一键、增量迁移、种子数据和环境配置中的客户隔离维度;盘点 `tenant_id = 0`、`10001` 等保留值的真实用途;提出上线前只读数据核验 SQL,不执行写操作。
### 对话 G:测试、OpenAPI、文档与三期防返工
只负责 `src/test/`、各模块 `src/main/resources/openapi/`、`docs/` 和模块 `README` / `AGENTS.md`,不修改生产代码。
检查测试是否覆盖跨客户拒绝、平台跨客户、内部调用归属和端域隔离;检查契约及文档是否仍把当前 `tenant_id` 描述成真实上级租户;定向核对三期文档,列出可能诱导后续按三级租户开发的内容。
## 五、每个审计对话的统一输出格式
每项发现必须包含:
- 编号和风险级别。
- 结论:符合、缺口或需要业务确认。
- 可复核证据:绝对文件路径和准确行号。
- 触发场景及可能造成的跨客户或上线影响。
- 最小修复建议,明确涉及的文件和消费者。
- 建议增加或调整的测试。
- 是否应延后到约一年后的真实租户迭代。
风险级别统一为:
- `P0`:可直接造成未授权跨客户读写、身份伪造或生产数据破坏,阻断上线。
- `P1`:明确的隔离缺口、端域绕过或高概率配置错误,应在上线前修复。
- `P2`:防御性加固、测试缺口、易误用设计或运维核验项,可在上线前排期完成。
- `Deferred`:只与未来真实多租户有关,当前禁止提前改造,仅记录迁移边界。
没有发现问题的负责范围也必须给出检查过的入口、隔离链路和测试证据,不能只回复“未发现问题”。
## 六、汇总与裁决流程
1. 主对话等待全部只读审计完成,去重并处理跨模块结论冲突。
2. 以当前根 `AGENTS.md` 为事实来源,把发现分成“必须上线前修改、上线前建议加固、仅需数据核验、延后到真实租户阶段”。
3. 对跨模块字段和契约,由主对话确定唯一兼容策略;任何审计对话不得自行决定重定义 `tenant_id`。
4. 输出修改清单、文件所有权、依赖顺序、验证命令和回滚边界,用户确认后进入修改阶段。
## 七、第二轮修改对话安排原则
- 修改对话数量由最终发现决定,不预先假设每个审计对话都有代码要改。
- 每个修改对话拥有互斥的生产文件范围;公共 DTO、事件、OpenAPI 和统一 SQL 入口由主对话协调。
- 优先处理 P0/P1,再处理测试和文档;`Deferred` 项不得实现。
- 每个修改对话只运行目标模块实际声明的测试;主对话最后执行受影响模块及跨模块消费者验证。
- 所有修改完成后再次定向检索 `tenant_id`、`customer_id`、`operator_tenant_id`、`DEFAULT_TENANT_ID` 和通用 `/api/iam/**` 路由,确认没有新增语义漂移。
## 八、最终交付物
- 全仓单租户多客户设计审计结论。
- 按优先级排序的最小修改清单及完成状态。
- 上线前只读数据核验 SQL 和配置核对清单。
- 受影响模块测试与跨服务契约验证结果。
- 更新后的根/模块 `AGENTS.md`、必要 OpenAPI 和回归记录。
- 约一年后引入真实租户时必须保留的迁移边界清单。