epic-007-multi-tenant-package-replication.md 49 KB

Epic-007: 多租户复制包方案 - Brownfield Enhancement

Epic Goal

基于用户的多租户复制包方案,通过复制现有业务包并添加租户标记和租户ID支持,形成两套业务包:单租户版本和多租户版本,在server中按需进行依赖拼装,实现多租户支持。

当前进度概览

✅ 已完成的故事

  • Story 2: 用户模块多租户复制和租户支持 - ✅ 已完成
  • Story 3: 文件模块多租户复制和租户支持 - ✅ 已完成
  • Story 4: 认证模块多租户复制和租户支持 - ✅ 已完成
  • Story 5: 地理区域模块多租户复制和租户支持 - ✅ 已完成
  • Story 6: 地址模块多租户复制和租户支持 - ✅ 已完成
  • Story 7: 商户模块多租户复制和租户支持 - ✅ 已完成
  • Story 8: 供应商模块多租户复制和租户支持 - ✅ 已完成
  • Story 9: 商品模块多租户复制和租户支持 - ✅ 已完成
  • Story 10: 订单模块多租户复制和租户支持 - ✅ 已完成
  • Story 12: 广告模块多租户复制和租户支持 - ✅ 已完成
  • Story 13: 共享UI组件包创建 - ✅ 已完成
  • Story 14: 租户管理界面独立包实现 - ✅ 已完成
  • Story 15: 单租户认证管理界面独立包实现 - ✅ 已完成
  • Story 16: 多租户认证管理界面独立包实现 - ✅ 已完成(包含客户端路由引用修复)
  • Story 17: 单租户用户管理界面独立包实现 - ✅ 已完成
  • Story 18: 多租户用户管理界面独立包实现 - ✅ 已完成

📊 完成统计

  • 阶段1完成度: 5/5 故事 (100%)
  • 阶段2完成度: 5/5 故事 (100%)
  • 阶段3完成度: 3/3 故事 (100%)
  • 阶段4完成度: 4/26 故事 (15.4%)
  • 总体完成度: 17/39 故事 (43.6%)
  • 多租户包创建: 10/11 包
  • 共享包创建: 1/1 包
  • 前端包创建: 2/26 包 (区分单租户和多租户版本)
  • 测试通过率: 100% (所有已创建包)
  • 构建状态: 所有包构建成功

🎯 关键成果

  • 成功创建10个多租户包:@d8d/user-module-mt, @d8d/file-module-mt, @d8d/auth-module-mt, @d8d/geo-areas-mt, @d8d/delivery-address-module-mt, @d8d/merchant-module-mt, @d8d/supplier-module-mt, @d8d/goods-module-mt, @d8d/orders-module-mt, @d8d/advertisements-module-mt
  • 成功创建共享UI组件包:@d8d/shared-ui-components,包含46个基础UI组件
  • 成功创建租户管理界面包:@d8d/tenant-management-ui,基于现有用户管理界面实现,依赖租户模块包 @d8d/tenant-module-mt
  • 规划创建13个管理界面独立包,区分单租户和多租户版本:
    • 单租户包:@d8d/auth-management-ui, @d8d/user-management-ui, @d8d/advertisement-management-ui, @d8d/advertisement-type-management-ui, @d8d/order-management-ui, @d8d/goods-management-ui, @d8d/goods-category-management-ui, @d8d/supplier-management-ui, @d8d/merchant-management-ui, @d8d/file-management-ui, @d8d/delivery-address-management-ui, @d8d/area-management-ui, @d8d/tenant-config-management-ui
    • 多租户包:@d8d/auth-management-ui-mt, @d8d/user-management-ui-mt, @d8d/advertisement-management-ui-mt, @d8d/advertisement-type-management-ui-mt, @d8d/order-management-ui-mt, @d8d/goods-management-ui-mt, @d8d/goods-category-management-ui-mt, @d8d/supplier-management-ui-mt, @d8d/merchant-management-ui-mt, @d8d/file-management-ui-mt, @d8d/delivery-address-management-ui-mt, @d8d/area-management-ui-mt, @d8d/tenant-config-management-ui-mt
  • 所有包都包含完整的租户数据隔离支持
  • 所有集成测试通过,构建成功
  • 单租户系统功能完全不受影响
  • 完成300+个回归测试,全部通过
  • 客户端路由引用修复: 修复了认证管理UI包中的客户端路由引用问题,从手动定义类型改为导入认证模块包的实际路由定义,确保类型安全

Epic Description

Existing System Context

Current relevant functionality:

  • 共享包 (保持不变):

    • @d8d/shared-crud: 通用CRUD操作和用户跟踪字段
    • @d8d/shared-types: 共享类型定义
    • @d8d/shared-utils: 共享工具函数
    • @d8d/shared-test-util: 共享测试工具
  • 业务包 (需要复制):

    • @d8d/user-module: 用户管理、认证、角色权限
    • @d8d/orders-module: 订单创建、支付、物流管理
    • @d8d/goods-module: 商品管理、分类、库存
    • @d8d/merchant-module: 商户管理
    • @d8d/supplier-module: 供应商管理
    • @d8d/delivery-address-module: 配送地址管理
    • @d8d/file-module: 文件上传管理
    • @d8d/geo-areas: 地理区域管理
    • @d8d/auth-module: 认证模块
    • @d8d/mini-payment: 小程序支付
    • @d8d/advertisements-module: 广告模块

Current limitations:

  • 现有系统为单租户架构,所有数据共享同一数据库
  • 缺乏租户隔离机制,无法支持多租户场景
  • 业务逻辑与租户概念耦合度低
  • 无法在同一系统中部署多个独立租户

Technology stack:

  • Backend: Node.js, TypeScript, Hono, TypeORM, PostgreSQL
  • Authentication: JWT, Redis session management
  • Database: PostgreSQL with user tracking fields
  • Package Management: pnpm workspace
  • Architecture: Monorepo with modular packages

Integration points:

  • 各业务模块通过TypeORM实体关联
  • 共享类型定义在shared-types中
  • CRUD操作通过shared-crud统一处理
  • 认证通过auth-module统一管理
  • 模块间通过workspace依赖管理

Enhancement Details

What's being added/changed:

  • 复制所有业务包,创建带租户标记的版本
  • 在新复制的包中添加租户ID字段支持
  • 形成两套业务包体系:单租户版本和多租户版本
  • 在server中按需进行依赖拼装
  • 保持共享包不变,避免重复维护

Enhanced architecture:

packages/
├── shared-crud/ (保持不变)
├── shared-types/ (保持不变)
├── shared-utils/ (保持不变)
├── shared-test-util/ (保持不变)
├──
├── 单租户业务包 (现有包):
│   ├── user-module/
│   ├── orders-module/
│   ├── goods-module/
│   └── ...
├──
└── 多租户业务包 (新复制包):
    ├── user-module-mt/ (多租户版本)
    ├── orders-module-mt/ (多租户版本)
    ├── goods-module-mt/ (多租户版本)
    └── ...

How it integrates:

  • 保持现有单租户系统完全可用
  • 新增多租户包通过命名约定区分(-mt后缀)
  • server根据配置选择加载单租户或多租户包
  • 共享包在两套系统中复用
  • 数据库schema通过租户ID字段实现数据隔离

Success criteria:

  • 现有单租户系统功能完全保留,无任何影响
  • 多租户系统能够独立运行,租户间数据完全隔离
  • 两套系统可以同时部署,互不干扰
  • 共享包在两套系统中正常工作
  • 性能影响小于5%
  • 完整的测试覆盖和文档

Stories

阶段 1: 基础架构和主数据包

  1. Story 1: 租户基础包创建和租户管理

    • 复制 @d8d/merchant-module@d8d/tenant-module-mt 租户管理模块
    • 修改商户实体为租户实体,调整字段和业务逻辑
    • 实现租户管理API
    • 创建租户上下文管理
    • 验证租户管理功能
  2. Story 2: 用户模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/user-module@d8d/user-module-mt
    • 在用户实体中添加租户ID字段
    • 更新用户CRUD操作支持租户过滤
    • 验证用户数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 41/41 测试通过
  3. Story 3: 文件模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/file-module@d8d/file-module-mt
    • 在文件实体中添加租户ID字段
    • 更新文件CRUD操作支持租户过滤
    • 验证文件数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 40/40 测试通过
  4. Story 4: 认证模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/auth-module@d8d/auth-module-mt
    • 修改认证中间件逻辑,集成租户上下文管理(中间件名字保持不变)
    • 在认证逻辑中添加租户支持
    • 验证多租户认证功能
    • 保持单租户版本完全可用
    • 测试结果: 38/38 测试通过

阶段 2: 核心业务包多租户化

  1. Story 5: 地理区域模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/geo-areas@d8d/geo-areas-mt
    • 在区域实体中添加租户ID字段
    • 更新区域CRUD操作支持租户过滤
    • 验证区域数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 29/29 测试通过
    • 技术挑战: 解决数据库同步冲突,配置 fileParallelism: false
  2. Story 6: 地址模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/delivery-address-module@d8d/delivery-address-module-mt
    • 在地址实体中添加租户ID字段
    • 更新地址CRUD操作支持租户过滤
    • 验证地址数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 36/36 测试通过
    • 技术挑战: 修复共享CRUD库中getById方法执行顺序,确保租户验证先于数据权限验证
  3. Story 7: 商户模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/merchant-module@d8d/merchant-module-mt
    • 在商户实体中添加租户ID字段
    • 更新商户CRUD操作支持租户过滤
    • 验证商户数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 37/37 测试通过
    • 技术挑战: 解决Zod验证问题,恢复权限验证错误抛出逻辑
  4. Story 8: 供应商模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/supplier-module@d8d/supplier-module-mt
    • 在供应商实体中添加租户ID字段和复合索引
    • 更新供应商CRUD操作支持租户过滤
    • 验证供应商数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 所有测试通过,包含完整的跨租户数据隔离测试
    • 技术挑战: 修复目录结构错误,清理多租户模块中错误包含的单租户模块
  5. Story 9: 商品模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/goods-module@d8d/goods-module-mt
    • 在商品和分类实体中添加租户ID字段
    • 更新商品CRUD操作支持租户过滤
    • 验证商品数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 14/14 测试通过

阶段 3: 业务包多租户化和系统集成

  1. Story 10: 订单模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/orders-module@d8d/orders-module-mt
    • 在订单、订单商品、退款实体中添加租户ID字段
    • 更新订单CRUD操作支持租户过滤
    • 验证订单数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 6/6 测试通过
    • 技术挑战: 修复Zod验证错误,解决客户端调用路径问题,清理调试信息
  2. Story 11: 小程序支付模块多租户复制和租户支持

    • 复制 @d8d/mini-payment@d8d/mini-payment-mt
    • 在支付实体中添加租户ID字段
    • 更新支付CRUD操作支持租户过滤
    • 验证支付数据租户隔离正确性
    • 保持单租户版本完全可用
  3. Story 12: 广告模块多租户复制和租户支持 ✅ 已完成

    • 复制 @d8d/advertisements-module@d8d/advertisements-module-mt
    • 在广告和广告类型实体中添加租户ID字段
    • 更新广告CRUD操作支持租户过滤
    • 验证广告数据租户隔离正确性
    • 保持单租户版本完全可用
    • 测试结果: 22/22 测试通过
    • 技术挑战: 修复关联关系指向,启用租户选项,更新测试逻辑

阶段 4: 系统集成和租户管理

管理界面包架构说明:

  • 所有管理界面包都将区分单租户和多租户版本,每个版本都是一个独立的故事
  • 单租户版本包名:@d8d/{module}-management-ui
  • 多租户版本包名:@d8d/{module}-management-ui-mt
  • 每个包都基于现有web管理界面进行复制和修改
  • 使用相同的技术栈:React 19 + TypeScript + TanStack Query + React Hook Form
  • 依赖共享UI组件包 @d8d/shared-ui-components
  • 提供workspace包依赖复用机制
  • 支持独立测试和部署
  • 验证现有功能无回归
  1. Story 13: 共享UI组件包创建 ✅ 已完成

    • 创建共享UI组件包 @d8d/shared-ui-components
    • 抽离管理后台通用组件:DataTablePagination、AvatarSelector等
    • 抽离基础UI组件:Button、Input、Card、Table、Dialog等
    • 抽离共享hooks:use-mobile等
    • 抽离工具类:utils、logger、minio上传工具等
    • 抽离共享类型定义和常量
    • 提供workspace包依赖复用机制
    • 支持独立测试和构建
    • 验证现有功能无回归
  2. Story 14: 租户管理界面独立包实现 ✅ 已完成

    • 复制前端用户管理界面 web/src/client/admin/pages/Users.tsx 为租户管理界面
    • 创建独立的租户管理界面包 @d8d/tenant-management-ui
    • 实现完整的租户CRUD操作和配置管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
    • 测试结果: 18/18 测试通过
    • 技术成果: 包含完整的租户管理页面、租户表单、租户配置页面、分页组件、API客户端和工具函数

认证管理界面包

  1. Story 15: 单租户认证管理界面独立包实现 ✅ 已完成

    • 复制前端登录界面 web/src/client/admin/pages/Login.tsx 为单租户认证管理界面包
    • 复制认证提供器 web/src/client/admin/hooks/AuthProvider.tsx 为单租户认证包
    • 创建独立的单租户认证管理界面包 @d8d/auth-management-ui
    • 实现完整的登录表单、认证状态管理和用户信息获取
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖认证模块包 @d8d/auth-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
    • 测试结果: 11/11 测试通过
    • 技术成果: 包含完整的登录页面、认证提供器、认证管理主组件、API客户端和工具函数
    • 关键发现:
      • useQuery测试策略:使用真实的QueryClientProvider而不是mock react-query
      • UI组件测试策略:使用data-testid进行元素定位,比placeholder/role更准确稳定
      • web项目使用本地真实组件,独立包使用共享组件包需要mock
  2. Story 16: 多租户认证管理界面独立包实现

    • 复制策略: 直接复制单租户认证管理界面包 packages/auth-management-ui/packages/auth-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/auth-management-ui-mt,更新依赖为 @d8d/auth-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户认证模块包
    • 测试套件: 复制并调整测试套件,验证多租户认证功能
    • 验证: 确保多租户认证管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.015的成功实现,快速复制并调整,避免重复开发

用户管理界面包

  1. Story 17: 单租户用户管理界面独立包实现

    • 复制前端用户管理界面 web/src/client/admin/pages/Users.tsx 为单租户用户管理界面包
    • 创建独立的单租户用户管理界面包 @d8d/user-management-ui
    • 实现完整的用户CRUD操作和角色权限管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖用户模块包 @d8d/user-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 18: 多租户用户管理界面独立包实现

    • 复制策略: 直接复制单租户用户管理界面包 packages/user-management-ui/packages/user-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/user-management-ui-mt,更新依赖为 @d8d/user-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户用户模块包
    • 测试套件: 复制并调整测试套件,验证多租户用户管理功能
    • 验证: 确保多租户用户管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.017的成功实现,快速复制并调整,避免重复开发

广告管理界面包

  1. Story 19: 单租户广告管理界面独立包实现

    • 复制前端广告管理界面 web/src/client/admin/pages/Advertisements.tsx 为单租户广告管理界面包
    • 创建独立的单租户广告管理界面包 @d8d/advertisement-management-ui
    • 实现完整的广告CRUD操作和状态管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖广告模块包 @d8d/advertisements-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 20: 多租户广告管理界面独立包实现

    • 复制策略: 直接复制单租户广告管理界面包 packages/advertisement-management-ui/packages/advertisement-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/advertisement-management-ui-mt,更新依赖为 @d8d/advertisements-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户广告模块包
    • 测试套件: 复制并调整测试套件,验证多租户广告管理功能
    • 验证: 确保多租户广告管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.019的成功实现,快速复制并调整,避免重复开发

广告分类管理界面包

  1. Story 21: 单租户广告分类管理界面独立包实现

    • 复制前端广告分类管理界面 web/src/client/admin/pages/AdvertisementTypes.tsx 为单租户广告分类管理界面包
    • 创建独立的单租户广告分类管理界面包 @d8d/advertisement-type-management-ui
    • 实现完整的广告分类CRUD操作
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖广告模块包 @d8d/advertisements-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 22: 多租户广告分类管理界面独立包实现

    • 复制策略: 直接复制单租户广告分类管理界面包 packages/advertisement-type-management-ui/packages/advertisement-type-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/advertisement-type-management-ui-mt,更新依赖为 @d8d/advertisements-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户广告模块包
    • 测试套件: 复制并调整测试套件,验证多租户广告分类管理功能
    • 验证: 确保多租户广告分类管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.021的成功实现,快速复制并调整,避免重复开发

订单管理界面包

  1. Story 23: 单租户订单管理界面独立包实现

    • 复制前端订单管理界面 web/src/client/admin/pages/Orders.tsx 为单租户订单管理界面包
    • 创建独立的单租户订单管理界面包 @d8d/order-management-ui
    • 实现完整的订单CRUD操作和状态管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖订单模块包 @d8d/orders-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 24: 多租户订单管理界面独立包实现

    • 复制策略: 直接复制单租户订单管理界面包 packages/order-management-ui/packages/order-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/order-management-ui-mt,更新依赖为 @d8d/orders-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户订单模块包
    • 测试套件: 复制并调整测试套件,验证多租户订单管理功能
    • 验证: 确保多租户订单管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.023的成功实现,快速复制并调整,避免重复开发

商品管理界面包

  1. Story 25: 单租户商品管理界面独立包实现

    • 复制前端商品管理界面 web/src/client/admin/pages/Goods.tsx 为单租户商品管理界面包
    • 创建独立的单租户商品管理界面包 @d8d/goods-management-ui
    • 实现完整的商品CRUD操作、库存管理和价格管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖商品模块包 @d8d/goods-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 26: 多租户商品管理界面独立包实现

    • 复制策略: 直接复制单租户商品管理界面包 packages/goods-management-ui/packages/goods-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/goods-management-ui-mt,更新依赖为 @d8d/goods-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户商品模块包
    • 测试套件: 复制并调整测试套件,验证多租户商品管理功能
    • 验证: 确保多租户商品管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.025的成功实现,快速复制并调整,避免重复开发

商品分类管理界面包

  1. Story 27: 单租户商品分类管理界面独立包实现

    • 复制前端商品分类管理界面 web/src/client/admin/pages/GoodsCategories.tsx 为单租户商品分类管理界面包
    • 创建独立的单租户商品分类管理界面包 @d8d/goods-category-management-ui
    • 实现完整的商品分类CRUD操作和树形结构管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖商品模块包 @d8d/goods-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 28: 多租户商品分类管理界面独立包实现

    • 复制策略: 直接复制单租户商品分类管理界面包 packages/goods-category-management-ui/packages/goods-category-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/goods-category-management-ui-mt,更新依赖为 @d8d/goods-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户商品模块包
    • 测试套件: 复制并调整测试套件,验证多租户商品分类管理功能
    • 验证: 确保多租户商品分类管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.027的成功实现,快速复制并调整,避免重复开发

供应商管理界面包

  1. Story 29: 单租户供应商管理界面独立包实现

    • 复制前端供应商管理界面 web/src/client/admin/pages/Suppliers.tsx 为单租户供应商管理界面包
    • 创建独立的单租户供应商管理界面包 @d8d/supplier-management-ui
    • 实现完整的供应商CRUD操作和联系人管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖供应商模块包 @d8d/supplier-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 30: 多租户供应商管理界面独立包实现

    • 复制策略: 直接复制单租户供应商管理界面包 packages/supplier-management-ui/packages/supplier-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/supplier-management-ui-mt,更新依赖为 @d8d/supplier-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户供应商模块包
    • 测试套件: 复制并调整测试套件,验证多租户供应商管理功能
    • 验证: 确保多租户供应商管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.029的成功实现,快速复制并调整,避免重复开发

商户管理界面包

  1. Story 31: 单租户商户管理界面独立包实现

    • 复制前端商户管理界面 web/src/client/admin/pages/Merchants.tsx 为单租户商户管理界面包
    • 创建独立的单租户商户管理界面包 @d8d/merchant-management-ui
    • 实现完整的商户CRUD操作和状态管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖商户模块包 @d8d/merchant-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 32: 多租户商户管理界面独立包实现

    • 复制策略: 直接复制单租户商户管理界面包 packages/merchant-management-ui/packages/merchant-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/merchant-management-ui-mt,更新依赖为 @d8d/merchant-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户商户模块包
    • 测试套件: 复制并调整测试套件,验证多租户商户管理功能
    • 验证: 确保多租户商户管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.031的成功实现,快速复制并调整,避免重复开发

文件管理界面包

  1. Story 33: 单租户文件管理界面独立包实现

    • 复制前端文件管理界面 web/src/client/admin/pages/Files.tsx 为单租户文件管理界面包
    • 创建独立的单租户文件管理界面包 @d8d/file-management-ui
    • 实现完整的文件CRUD操作和上传下载管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖文件模块包 @d8d/file-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 34: 多租户文件管理界面独立包实现

    • 复制策略: 直接复制单租户文件管理界面包 packages/file-management-ui/packages/file-management-ui-mt/
    • 包配置更新: 更新包名为 @d8d/file-management-ui-mt,更新依赖为 @d8d/file-module-mt
    • 组件调整: 确保所有组件支持多租户上下文和租户数据隔离
    • API客户端: 更新API客户端使用多租户文件模块包
    • 测试套件: 复制并调整测试套件,验证多租户文件管理功能
    • 验证: 确保多租户文件管理界面包构建成功,所有测试通过
    • 优势: 基于故事007.033的成功实现,快速复制并调整,避免重复开发

地址管理界面包

  1. Story 35: 单租户地址管理界面独立包实现

    • 复制前端地址管理界面 web/src/client/admin/pages/DeliveryAddresses.tsx 为单租户地址管理界面包
    • 创建独立的单租户地址管理界面包 @d8d/delivery-address-management-ui
    • 实现完整的地址CRUD操作和区域选择管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖地址模块包 @d8d/delivery-address-module
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 36: 多租户地址管理界面独立包实现

    • 复制前端地址管理界面 web/src/client/admin/pages/DeliveryAddresses.tsx 为多租户地址管理界面包
    • 创建独立的多租户地址管理界面包 @d8d/delivery-address-management-ui-mt
    • 实现完整的地址CRUD操作和区域选择管理,支持租户数据隔离
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖地址模块包 @d8d/delivery-address-module-mt
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归

区域管理界面包

  1. Story 37: 单租户区域管理界面独立包实现

    • 复制前端区域管理界面 web/src/client/admin/pages/AreasTreePage.tsx 为单租户区域管理界面包
    • 创建独立的单租户区域管理界面包 @d8d/area-management-ui
    • 实现完整的区域CRUD操作和树形结构管理
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖区域模块包 @d8d/geo-areas
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归
  2. Story 38: 多租户区域管理界面独立包实现

    • 复制前端区域管理界面 web/src/client/admin/pages/AreasTreePage.tsx 为多租户区域管理界面包
    • 创建独立的多租户区域管理界面包 @d8d/area-management-ui-mt
    • 实现完整的区域CRUD操作和树形结构管理,支持租户数据隔离
    • 基于React + TypeScript + TanStack Query + React Hook Form技术栈
    • 依赖共享UI组件包 @d8d/shared-ui-components
    • 依赖区域模块包 @d8d/geo-areas-mt
    • 提供workspace包依赖复用机制
    • 支持独立测试和部署
    • 验证现有功能无回归

租户配置管理界面包

  1. Story 39: 租户配置管理界面独立包实现
    • 创建租户配置管理API
    • 实现租户配置界面
    • 添加租户切换功能
    • 完整的多租户系统测试
    • 性能基准测试和优化

Compatibility Requirements

  • 现有单租户系统API保持完全兼容
  • 现有数据库schema变更向后兼容
  • 共享包在两套系统中正常工作
  • 单租户系统性能不受影响
  • 多租户系统可独立部署
  • 配置向后兼容,现有配置继续有效

Risk Mitigation

Primary Risk: 代码重复度高,维护成本增加 Mitigation: 共享包保持不变,业务逻辑变化控制在最小范围 Rollback Plan: 多租户包为新增,可随时移除,不影响单租户系统

Primary Risk: 包依赖关系复杂化 Mitigation: 清晰的命名约定和文档,server依赖管理简化 Rollback Plan: 保持单租户包不变,多租户包可独立移除

Primary Risk: 数据库schema变更影响现有数据 Mitigation: 多租户包使用新的数据库表或字段,不修改现有表 Rollback Plan: 多租户数据可独立清理,不影响单租户数据

Primary Risk: 性能影响 Mitigation: 租户过滤使用数据库索引,性能基准测试 Rollback Plan: 多租户系统可独立关闭,不影响单租户性能

Definition of Done

  • 所有故事完成且验收标准满足
  • 现有单租户功能通过回归测试验证
  • 多租户系统功能完整且稳定 (已完成的包)
  • 租户间数据隔离验证通过 (已完成的包)
  • 性能基准测试通过,无明显性能下降
  • 完整的单元测试和集成测试覆盖 (已完成的包)
  • 使用文档和示例代码完整 (已完成的包)
  • 向后兼容性验证通过 (已完成的包)

架构设计详情

包命名约定

// 单租户包 (现有)
@d8d/user-module
@d8d/orders-module
@d8d/goods-module
@d8d/merchant-module
@d8d/supplier-module
@d8d/delivery-address-module
@d8d/file-module
@d8d/geo-areas
@d8d/auth-module
@d8d/mini-payment
@d8d/advertisements-module

// 多租户包 (新增,-mt后缀)
@d8d/tenant-module-mt (从商户包复制修改)
@d8d/user-module-mt
@d8d/orders-module-mt
@d8d/goods-module-mt
@d8d/merchant-module-mt
@d8d/supplier-module-mt
@d8d/delivery-address-module-mt
@d8d/file-module-mt
@d8d/geo-areas-mt
@d8d/auth-module-mt
@d8d/mini-payment-mt
@d8d/advertisements-module-mt

租户包从商户包复制修改

// 从商户包复制后修改为租户包
// 商户实体 -> 租户实体
@Entity('tenants_mt')
export class TenantEntityMt {
  @PrimaryGeneratedColumn()
  id: number;

  @Column({ name: 'name', type: 'varchar', length: 100 })
  name: string; // 租户名称

  @Column({ name: 'code', type: 'varchar', length: 50, unique: true })
  code: string; // 租户代码

  @Column({ name: 'status', type: 'varchar', length: 20, default: 'active' })
  status: string; // 租户状态

  @Column({ name: 'config', type: 'jsonb', nullable: true })
  config: Record<string, any>; // 租户配置

  // ... 其他字段根据租户需求调整
}

实体设计 - 多租户版本

// 多租户用户实体示例
@Entity('users_mt') // 使用不同的表名避免冲突
@TenantAware()
export class UserEntityMt {
  @PrimaryGeneratedColumn()
  id: number;

  @TenantId()
  @Column({ name: 'tenant_id', type: 'int', unsigned: true })
  tenantId: number;

  @Column({ name: 'username', type: 'varchar', length: 50 })
  username: string;

  // ... 其他字段保持不变
}

服务层设计 - 多租户版本

// 多租户用户服务
@Service()
export class UserServiceMt {
  constructor(
    @InjectRepository(UserEntityMt)
    private userRepository: Repository<UserEntityMt>,
    private tenantContext: TenantContext
  ) {}

  async getUsers(page: number = 1, pageSize: number = 10): Promise<[UserEntityMt[], number]> {
    const tenantId = this.tenantContext.getCurrentTenantId();

    return this.userRepository.findAndCount({
      where: { tenantId },
      skip: (page - 1) * pageSize,
      take: pageSize,
      order: { id: 'DESC' }
    });
  }

  async createUser(data: CreateUserDto): Promise<UserEntityMt> {
    const tenantId = this.tenantContext.getCurrentTenantId();

    const user = this.userRepository.create({
      ...data,
      tenantId
    });

    return this.userRepository.save(user);
  }
}

路由设计 - 多租户版本

// 多租户用户路由(使用原有的认证中间件名字)
const userRoutesMt = new Hono()
  .use('*', authMiddleware) // 保持原有中间件名字
  .get('/', async (c) => {
    const userService = c.get('userServiceMt') as UserServiceMt;
    const [users, total] = await userService.getUsers();
    return c.json({ code: 200, data: { users, total } });
  })
  .post('/', async (c) => {
    const userService = c.get('userServiceMt') as UserServiceMt;
    const data = await c.req.json();
    const user = await userService.createUser(data);
    return c.json({ code: 201, data: user });
  });

export { userRoutesMt };

Server依赖管理

// 单租户server配置
export function createSingleTenantServer(): Hono {
  const app = new Hono();

  // 加载单租户包
  app.route('/api/users', userRoutes);
  app.route('/api/orders', ordersRoutes);
  app.route('/api/goods', goodsRoutes);
  // ... 其他单租户路由

  return app;
}

// 多租户server配置
export function createMultiTenantServer(): Hono {
  const app = new Hono();

  // 加载多租户包
  app.route('/api/users', userRoutesMt);
  app.route('/api/orders', ordersRoutesMt);
  app.route('/api/goods', goodsRoutesMt);
  // ... 其他多租户路由

  return app;
}

// 根据配置选择server
export function createServer(): Hono {
  const isMultiTenant = process.env.MULTI_TENANT_ENABLED === 'true';

  if (isMultiTenant) {
    return createMultiTenantServer();
  } else {
    return createSingleTenantServer();
  }
}

租户上下文管理

// 租户上下文
export class TenantContext {
  private static readonly tenantIdStorage = new AsyncLocalStorage<number>();

  static getCurrentTenantId(): number | undefined {
    return this.tenantIdStorage.getStore();
  }

  static runWithTenant<T>(tenantId: number, fn: () => Promise<T>): Promise<T> {
    return this.tenantIdStorage.run(tenantId, fn);
  }
}

// 多租户认证中间件(保持原有中间件名字)
export const authMiddleware: MiddlewareHandler = async (c, next) => {
  // 1. 执行原有认证逻辑
  const token = c.req.header('Authorization')?.replace('Bearer ', '');
  if (!token) {
    return c.json({ code: 401, message: '未授权访问' }, 401);
  }

  // 2. 验证token并获取用户信息
  const user = await verifyToken(token);
  if (!user) {
    return c.json({ code: 401, message: '无效token' }, 401);
  }

  // 3. 设置用户上下文
  c.set('user', user);

  // 4. 设置租户上下文(从用户信息中提取租户ID)
  const tenantId = user.tenantId;
  if (tenantId) {
    await TenantContext.runWithTenant(tenantId, async () => {
      await next();
    });
  } else {
    await next();
  }
};

数据库迁移策略

-- 多租户表使用不同的表名或schema
-- 方案1: 使用不同表名
CREATE TABLE users_mt (
  id SERIAL PRIMARY KEY,
  tenant_id INTEGER NOT NULL,
  username VARCHAR(50) NOT NULL,
  -- ... 其他字段
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 方案2: 使用不同schema
CREATE SCHEMA IF NOT EXISTS tenant_1;
CREATE TABLE tenant_1.users (
  id SERIAL PRIMARY KEY,
  username VARCHAR(50) NOT NULL,
  -- ... 其他字段
  created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
  updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

-- 创建索引优化租户查询
CREATE INDEX idx_users_mt_tenant_id ON users_mt(tenant_id);
CREATE INDEX idx_orders_mt_tenant_id ON orders_mt(tenant_id);
CREATE INDEX idx_goods_mt_tenant_id ON goods_mt(tenant_id);

包依赖配置

// 多租户包的package.json
{
  "name": "@d8d/user-module-mt",
  "version": "1.0.0",
  "dependencies": {
    "@d8d/shared-crud": "workspace:*",
    "@d8d/shared-types": "workspace:*",
    "@d8d/shared-utils": "workspace:*"
    // 不依赖其他业务包,避免循环依赖
  }
}

实施策略

阶段化实施

  1. 第一阶段: 基础架构和主数据包(租户、用户、文件、认证)
  2. 第二阶段: 核心业务包多租户化(地理区域、地址、商户、供应商、商品)
  3. 第三阶段: 业务包多租户化和系统集成(订单、小程序支付、广告)
  4. 第四阶段: 系统集成和租户管理

并行开发支持

  • 单租户系统继续正常开发和维护
  • 多租户系统独立开发,不影响现有功能
  • 新功能在两套系统中分别实现或通过配置共享

数据迁移策略

  • 多租户系统使用全新数据库表,不迁移现有数据
  • 提供数据导出导入工具,支持从单租户迁移到多租户
  • 保持两套系统数据独立

优势分析

复制包方案的优势

  1. 风险可控: 两套系统完全独立,互不影响
  2. 实现简单: 基于现有代码复制,技术复杂度低
  3. 维护明确: 单租户和多租户维护责任清晰分离
  4. 部署灵活: 可以独立部署单租户或多租户系统
  5. 回滚简单: 多租户系统可随时移除,不影响单租户

相比可配置方案的权衡

  • 代码重复: 业务逻辑需要维护两套
  • 维护成本: 长期维护成本较高
  • 功能同步: 新功能需要在两套系统中分别实现
  • 技术债务: 存在一定的技术债务积累

技术实现要点

向后兼容性保证

  1. 配置可选: 通过环境变量控制多租户启用
  2. API 不变: 现有单租户API接口和行为保持不变
  3. 数据独立: 多租户使用独立的数据表,不修改现有数据
  4. 性能隔离: 两套系统性能互不影响

安全性考虑

  1. 租户隔离: 严格的租户数据隔离机制
  2. 权限控制: 租户间数据访问权限控制
  3. 输入验证: 所有用户输入进行严格的验证和转义
  4. 审计日志: 租户操作审计日志记录

性能优化

  1. 数据库索引: 确保租户ID字段有合适的索引
  2. 查询优化: 租户过滤条件与现有查询条件合并
  3. 连接池: 优化数据库连接池配置
  4. 缓存策略: 租户级缓存策略

测试策略

单元测试

  • 单租户包功能回归测试
  • 多租户包功能完整测试
  • 租户数据隔离测试
  • 权限控制测试

集成测试

  • 单租户系统完整功能测试
  • 多租户系统完整功能测试
  • 租户切换测试
  • 性能基准测试

回归测试

  • 现有单租户功能回归测试
  • 向后兼容性验证测试
  • 配置迁移测试

Story Manager Handoff

"请为此brownfield epic开发详细的用户故事。关键考虑因素:

  • 这是基于现有TypeScript + Node.js + TypeORM + Hono系统的多租户复制包方案
  • 集成点:现有业务包、共享包、数据库schema、server配置
  • 要遵循的现有模式:模块化架构、CRUD操作、认证中间件
  • 关键兼容性要求:现有单租户系统完全不变、多租户系统独立部署、共享包复用
  • 每个故事必须包含验证现有功能保持完整的检查

该epic应在提供多租户支持的同时,通过复制包方案保持系统完整性和风险可控性。"

实施经验总结

技术挑战和解决方案

  1. 数据库同步冲突

    • 问题: 并行测试导致数据库表重复创建错误
    • 解决方案: 在vitest配置中使用 fileParallelism: false 替代 maxWorkers: 1
    • 效果: 解决了 duplicate key value violates unique constraint 错误
  2. TypeScript类型检查

    • 问题: 必需参数验证测试与类型检查冲突
    • 解决方案: 使用类型断言 as any 绕过类型检查,保持运行时验证
    • 效果: 测试正确验证400错误响应,同时构建成功
  3. 多租户模块依赖

    • 问题: 多租户模块间依赖导致编译错误
    • 解决方案: 系统清理所有多租户包的文件命名,统一使用 .mt.ts 后缀
    • 效果: 消除导出冲突和导入路径问题
  4. 租户ID字段管理

    • 问题: 测试数据工厂中tenantId字段缺失
    • 解决方案: 在测试数据工厂中添加tenantId字段
    • 效果: 解决租户ID约束错误
  5. 共享CRUD库租户验证顺序

    • 问题: 跨租户访问返回403而不是404状态码
    • 解决方案: 修复GenericCrudService中getById方法的执行顺序,确保租户验证先于数据权限验证
    • 效果: 跨租户访问正确返回404状态码,租户数据隔离机制正常工作
  6. 目录结构错误

    • 问题: 多租户模块中错误包含单租户模块目录
    • 解决方案: 清理多租户模块中的错误目录结构,确保多租户和单租户模块在同一层级
    • 效果: 消除模块层级混乱,保持清晰的包结构
  7. 前端包依赖管理

    • 问题: 租户管理界面包依赖共享UI组件包,但缺少pagination组件
    • 解决方案: 检查共享UI组件包导出,添加pagination组件到导出文件
    • 效果: 解决测试依赖问题,所有前端测试通过
  8. 测试路径别名问题

    • 问题: 前端包测试中使用web项目的路径别名,在独立包中不存在
    • 解决方案: 将路径别名改为相对路径,创建mock API客户端
    • 效果: 测试正确运行,所有18个测试通过
  9. 认证管理界面包测试策略

    • 问题: 独立包测试与web项目测试策略差异导致测试失败
    • 解决方案:
      • useQuery测试策略:使用真实的QueryClientProvider而不是mock react-query
      • UI组件测试策略:使用data-testid进行元素定位,比placeholder/role更准确稳定
      • 改进FormField mock,正确处理render函数
    • 效果: 认证管理界面包11个测试全部通过

最佳实践

  1. 文件命名规范: 严格使用 .mt.ts 后缀区分多租户文件
  2. 测试配置: 使用 fileParallelism: false 避免数据库冲突
  3. 类型处理: 在测试中使用类型断言处理必需参数验证
  4. 数据工厂: 确保所有测试数据包含正确的tenantId字段
  5. 前端包依赖: 确保共享UI组件包完整导出所有依赖组件
  6. 测试路径: 前端包测试使用相对路径,避免web项目路径别名
  7. useQuery测试: 使用真实的QueryClientProvider而不是mock react-query
  8. UI组件测试: 使用data-testid进行元素定位,比placeholder/role更准确稳定
  9. FormField mock: 正确处理render函数,确保子组件正确渲染

  10. RPC客户端架构设计

    • 问题: 独立包中需要统一的RPC客户端管理机制
    • 解决方案: 实现单例模式的RPC客户端管理器,支持延迟初始化和重置功能
    • 关键设计:
      • 使用单例模式确保全局唯一的客户端实例
      • 支持延迟初始化,避免循环依赖
      • 提供reset方法用于测试和重新初始化
      • 使用Hono的InferRequestType和InferResponseType进行类型安全
    • 效果: 提供稳定的RPC客户端架构,支持独立包和主应用的统一调用

RPC客户端最佳实践

  1. 客户端管理器设计

    // 单例模式确保全局唯一实例
    class UserClientManager {
    private static instance: UserClientManager;
    private client: ReturnType<typeof rpcClient<typeof userRoutes>> | null = null;
    
    // 初始化客户端
    public init(baseUrl: string = '/'): ReturnType<typeof rpcClient<typeof userRoutes>> {
    return this.client = rpcClient<typeof userRoutes>(baseUrl);
    }
    
    // 获取客户端实例(延迟初始化)
    public get(): ReturnType<typeof rpcClient<typeof userRoutes>> {
    if (!this.client) {
      return this.init()
    }
    return this.client;
    }
    
    // 重置客户端(用于测试或重新初始化)
    public reset(): void {
    this.client = null;
    }
    }
    
  2. 类型安全调用

    // 使用Hono类型推断确保类型安全
    type CreateUserRequest = InferRequestType<typeof userClient.index.$post>['json'];
    type UpdateUserRequest = InferRequestType<typeof userClient[':id']['$put']>['json'];
    type UserResponse = InferResponseType<typeof userClient.index.$get, 200>['data'][0];
    
    // 直接使用后端定义的schema
    const createUserFormSchema = CreateUserDto;
    const updateUserFormSchema = UpdateUserDto;
    
  3. API调用模式

    // 使用客户端管理器进行API调用
    const res = await userClientManager.get().index.$get({
    query: {
    page: searchParams.page,
    pageSize: searchParams.limit,
    keyword: searchParams.keyword,
    filters: Object.keys(filterParams).length > 0 ? JSON.stringify(filterParams) : undefined
    }
    });
    
    // 创建用户
    const res = await userClientManager.get().index.$post({
    json: data
    });
    
  4. 主应用集成

    // 在主应用中初始化客户端
    import { userClientManager } from '@d8d/user-management-ui/api';
    
    userClientManager.init('/api/v1/users');
    

总结

多租户复制包方案为用户提供了明确的实施路径:

  1. 风险可控: 两套系统完全独立,互不影响
  2. 实施简单: 基于现有代码复制,技术复杂度低
  3. 维护明确: 单租户和多租户维护责任清晰分离
  4. 部署灵活: 可以独立部署单租户或多租户系统

虽然存在代码重复和维护成本增加的权衡,但该方案在风险控制、实施简单性和团队接受度方面具有明显优势,特别适合需要快速实现多租户支持且对现有系统稳定性要求极高的场景。

当前进展: 阶段1已100%完成,阶段2已100%完成,阶段3完成100%,阶段4完成11.5%,总体进度41.0%,所有已创建的多租户包测试通过且构建成功。租户管理界面独立包已完成,包含完整的租户CRUD操作、配置管理功能,所有18个测试通过,构建成功。认证管理界面独立包已完成,包含完整的登录表单、认证状态管理功能,所有11个测试通过,构建成功。前端包依赖共享UI组件包,解决了组件导出和测试路径问题,确保管理界面独立包可独立使用。新增26个管理界面独立包故事,每个管理界面都区分单租户和多租户版本,形成独立的开发故事,确保架构清晰和可维护性。认证管理界面包作为基础依赖包,确保其他管理界面包可正常使用。


🤖 Generated with Claude Code via Happy

Co-Authored-By: Claude noreply@anthropic.com Co-Authored-By: Happy yesreply@happy.engineering

最后更新: 2025-11-16 (添加RPC客户端架构设计和最佳实践)