# 故事007.007: 商户模块多租户复制

## 状态

已完成

## 故事

**作为** 系统管理员，
**我想要** 复制商户模块并添加多租户支持，
**以便** 商户可以在租户隔离的环境中管理，同时保持与现有单租户系统的完全兼容性。

## 验收标准

1. **AC 1**: ✅ 成功复制 `@d8d/merchant-module` 为 `@d8d/merchant-module-mt`，包含正确的包配置
2. **AC 2**: ✅ 创建多租户商户实体 `MerchantMt`，包含租户ID字段和表名 `merchants_mt`
3. **AC 3**: ✅ 更新所有商户CRUD操作，自动包含租户过滤并在创建时设置租户ID
4. **AC 4**: ✅ 验证商户数据隔离在不同租户间正常工作
5. **AC 5**: ✅ 保持与现有单租户商户模块功能的完全兼容性
6. **AC 6**: ✅ 所有现有单租户API接口保持不变且功能正常
7. **AC 7**: ✅ 完整的集成测试证明租户隔离和功能正常
8. **AC 8**: ✅ 性能影响相比单租户版本小于5%
9. **AC 9**: ✅ 完成所有多租户模块的回归测试验证，确保系统稳定性

## 任务 / 子任务

- [x] 复制商户模块为多租户版本 (AC: 1)
  - [x] 复制 `packages/merchant-module` 为 `packages/merchant-module-mt`
  - [x] 更新包配置为 `@d8d/merchant-module-mt`
  - [x] **清理单租户文件**: 删除多租户包中所有单租户相关文件，避免命名冲突
  - [x] 更新依赖:
    - [x] 将 `@d8d/user-module` 替换为 `@d8d/user-module-mt`
    - [x] 将 `@d8d/auth-module` 替换为 `@d8d/auth-module-mt`
    - [x] 将 `@d8d/file-module` 替换为 `@d8d/file-module-mt`

- [x] 更新多租户商户实体 (AC: 2)
  - [x] 创建 `MerchantMt` 实体，表名为 `merchants_mt`
  - [x] 添加 `tenantId` 字段和正确的TypeORM配置
  - [x] 保持其他字段与单租户版本一致

- [x] 更新多租户商户服务 (AC: 3, 4)
  - [x] 创建 `MerchantServiceMt` 服务，继承GenericCrudService
  - [x] 所有查询操作自动添加租户过滤
  - [x] 创建操作自动设置租户ID
  - [x] 更新登录统计方法支持租户隔离
  - [x] 更新用户名查找方法支持租户过滤

- [x] 更新多租户路由配置 (AC: 3)
  - [x] 更新用户路由使用多租户实体和服务
  - [x] 更新管理员路由使用多租户实体和服务
  - [x] 保持API接口与单租户版本一致
  - [x] 更新认证中间件支持租户ID提取

- [x] 更新Schema定义 (AC: 3)
  - [x] 创建多租户商户Schema `MerchantSchemaMt`
  - [x] 创建多租户用户商户Schema `UserMerchantSchemaMt`
  - [x] 创建多租户管理员商户Schema `AdminMerchantSchemaMt`
  - [x] 添加租户ID字段定义

- [x] 实现租户数据隔离API测试 (AC: 7)
  - [x] 在 `packages/merchant-module-mt/tests/integration/user-routes.integration.test.ts` 中添加租户隔离测试用例
  - [x] 在 `packages/merchant-module-mt/tests/integration/admin-routes.integration.test.ts` 中添加跨租户商户访问安全验证
  - [x] 在现有功能测试中验证租户过滤功能正确性

- [x] 验证单租户系统完整性 (AC: 5, 6)
  - [x] 运行单租户商户模块回归测试
  - [x] 验证单租户API接口不受影响
  - [x] 确认单租户数据库表结构不变

- [x] 在创建复制的代码修改完后先运行安装
  - [x] 在复制模块后运行 `pnpm install` 安装依赖
  - [x] 验证新包已正确添加到工作区
  - [x] 确认所有依赖解析正确

- [x] 执行性能基准测试 (AC: 8)
  - [x] 运行多租户商户模块性能测试
  - [x] 比较单租户与多租户性能差异
  - [x] 确保性能影响小于5%

- [x] 执行回归测试验证 (AC: 9)
  - [x] 运行所有多租户模块的回归测试
  - [x] 验证权限模块多租户测试 (38个测试)
  - [x] 验证文件模块多租户测试 (40个测试)
  - [x] 验证区域模块多租户测试 (29个测试)
  - [x] 验证用户模块多租户测试 (41个测试)
  - [x] 验证配送地址模块多租户测试 (36个测试)
  - [x] 验证租户模块多租户测试 (16个测试)
  - [x] 确认所有200个测试全部通过

## 开发说明

### 前面故事的经验教训

**从故事007.006（地址模块）学到的关键经验：**
- **共享CRUD库执行顺序**: 必须确保在GenericCrudService.getById方法中租户验证先于数据权限验证 [Source: epic-007-multi-tenant-package-replication.md#技术挑战和解决方案]
- **测试数据租户ID管理**: 测试数据工厂必须显式设置tenantId字段以避免约束错误 [Source: epic-007-multi-tenant-package-replication.md#技术挑战和解决方案]
- **跨租户访问状态码**: 跨租户访问应该返回404（未找到）而不是403（禁止访问） [Source: epic-007-multi-tenant-package-replication.md#技术挑战和解决方案]
- **文件命名约定**: 严格使用 `.mt.ts` 后缀区分多租户文件 [Source: epic-007-multi-tenant-package-replication.md#最佳实践]
- **数据库同步**: 在vitest配置中使用 `fileParallelism: false` 避免数据库冲突 [Source: epic-007-multi-tenant-package-replication.md#技术挑战和解决方案]

### 数据模型

**商户实体结构:**
- 表名: `merchants_mt` (多租户版本)
- 租户ID字段: `tenantId` (必需，已索引)
- 核心字段: `name`, `username`, `password`, `phone`, `realname`, `loginNum`, `loginTime`, `loginIp`, `lastLoginTime`, `lastLoginIp`, `state`, `rsaPublicKey`, `aesKey` [Source: source-tree.md#商户管理模块]
- 用户跟踪字段: `createdBy`, `updatedBy` [Source: source-tree.md#商户管理模块]
- 时间戳字段: `createdAt`, `updatedAt` [Source: source-tree.md#商户管理模块]

### API规范

**用户路由:**
- 路径: `/api/merchants` (用户专用)
- 认证: `authMiddleware` 来自 `@d8d/auth-module-mt` [Source: source-tree.md#商户管理模块]
- 数据权限: 启用，使用 `userIdField: 'createdBy'` [Source: source-tree.md#商户管理模块]
- 搜索字段: `['name', 'username', 'realname', 'phone']` [Source: source-tree.md#商户管理模块]

**管理员路由:**
- 路径: `/api/admin/merchants` (完整权限)
- 认证: `authMiddleware` 来自 `@d8d/auth-module-mt` [Source: source-tree.md#商户管理模块]
- 数据权限: 管理员路由禁用数据权限控制 [Source: source-tree.md#商户管理模块]

### 组件规范

**商户服务方法:**
- `updateLoginStats(merchantId, loginTime, loginIp)`: 更新商户登录统计信息 [Source: source-tree.md#商户管理模块]
- `findByUsername(username)`: 根据用户名查找商户，包含租户过滤 [Source: source-tree.md#商户管理模块]
- `getByState(state)`: 根据状态获取商户列表，包含租户过滤 [Source: source-tree.md#商户管理模块]

### 文件位置

**要创建的源文件:**
- `packages/merchant-module-mt/src/entities/merchant.mt.entity.ts` (多租户实体)
- `packages/merchant-module-mt/src/services/merchant.mt.service.ts` (多租户服务)
- `packages/merchant-module-mt/src/routes/user-routes.mt.ts` (多租户用户路由)
- `packages/merchant-module-mt/src/routes/admin-routes.mt.ts` (多租户管理员路由)
- `packages/merchant-module-mt/src/schemas/merchant.mt.schema.ts` (多租户schemas)
- `packages/merchant-module-mt/tests/integration/tenant-isolation.integration.test.ts` (租户隔离测试)

**要删除的文件（单租户清理）:**
- 多租户包中所有没有 `.mt.ts` 后缀的文件
- 任何单租户特定的配置

### 技术约束

**数据库索引:**
- 必须在 `tenantId` 字段上创建索引以提高性能 [Source: epic-007-multi-tenant-package-replication.md#数据库迁移策略]
- 为常见查询模式创建复合索引 (tenantId + state, tenantId + username)

**认证集成:**
- 使用来自 `@d8d/auth-module-mt` 的更新版 `authMiddleware`，从用户上下文中提取租户ID [Source: epic-007-multi-tenant-package-replication.md#租户上下文管理]
- 租户ID应该从认证用户上下文中自动设置

### 测试

**测试标准:**
- **测试文件位置**: `packages/merchant-module-mt/tests/integration/` [Source: testing-strategy.md#集成测试]
- **测试框架**: Vitest + hono/testing + shared-test-util [Source: testing-strategy.md#集成测试]
- **测试模式**: 使用测试数据工厂并显式设置tenantId [Source: testing-strategy.md#测试数据管理]
- **覆盖率目标**: ≥ 60% 集成测试覆盖率 [Source: testing-strategy.md#各层覆盖率要求]
- **数据库策略**: 使用测试数据库和事务回滚 [Source: testing-strategy.md#数据库测试策略]

**特定测试要求:**
- 租户隔离验证: 验证不同租户的商户无法访问彼此的数据
- 跨租户访问: 跨租户访问尝试应该返回404状态码
- 登录统计: 验证登录统计在租户上下文中正确更新
- 用户名唯一性: 确保用户名唯一性在租户级别强制执行，而不是全局

## 变更日志

| 日期 | 版本 | 描述 | 作者 |
|------|------|------|------|
| 2025-11-14 | 1.0 | 初始故事创建，包含从前面的故事中学到的全面经验教训 | Bob (Scrum Master) |

## 开发代理记录

**实施进展 (2025-11-14):**

✅ **已完成任务:**
- 成功复制商户模块为多租户版本 `@d8d/merchant-module-mt`
- 创建多租户商户实体 `MerchantMt` 和表结构
- 更新多租户商户服务 `MerchantServiceMt` 支持租户过滤
- 更新多租户路由配置（用户路由和管理员路由）
- 更新Schema定义支持租户ID字段
- 在现有测试中添加租户隔离测试用例
- 解决实体循环依赖问题（UserEntityMt和FileMt使用字符串形式的关系定义）
- 修复跨租户访问状态码（返回404而不是403）
- 解决测试数据字段长度问题
- 修复租户选项配置问题
- **最终修复**: 恢复共享CRUD服务中的正确错误处理逻辑，确保路由层能正确捕获权限错误
- **回归测试**: 完成所有多租户模块的回归测试验证

✅ **所有测试通过:**
- 37个集成测试全部通过
- GET访问其他用户的商户返回404状态码
- PUT/DELETE操作其他用户的商户返回403状态码
- Zod验证问题完全解决，数据库数据都能通过Schema验证
- 租户数据隔离验证成功

✅ **回归测试结果 (200个测试全部通过):**
- 权限模块 (auth-module-mt): 38个测试 ✅
- 文件模块 (file-module-mt): 40个测试 ✅
- 区域模块 (geo-areas-mt): 29个测试 ✅
- 用户模块 (user-module-mt): 41个测试 ✅
- 配送地址模块 (delivery-address-module-mt): 36个测试 ✅
- 租户模块 (tenant-module-mt): 16个测试 ✅

**技术挑战解决:**
- 实体循环依赖：UserEntityMt和FileMt必须使用字符串形式的关系定义
- 实体注册：确保所有相关实体（RoleMt、FileMt）正确注册
- 租户验证执行顺序：确保在GenericCrudService中租户验证先于数据权限验证
- 跨租户访问状态码：返回404（未找到）而不是403（禁止访问）
- **共享CRUD错误处理**: 恢复`update`和`delete`方法中的权限验证抛出错误逻辑，确保路由层能正确捕获并返回相应状态码
- **Zod验证问题**: 完全解决所有数据库返回数据的Schema验证问题

**代码质量:**
- 创建了商户模块专用的测试工具类 `MerchantTestUtils`
- 使用测试数据工厂模式简化测试代码
- 所有多租户文件使用 `.mt.ts` 后缀
- 保持API接口与单租户版本完全兼容
- 共享CRUD服务中的错误处理逻辑完全符合设计原则
- 所有多租户模块回归测试通过，确保系统稳定性

## QA结果

*此部分将在质量保证审查过程中由QA代理填充*