epic-008-server-web-multi-tenant-integration.md 16 KB

Epic-008: Server和Web多租户集成 - Brownfield Enhancement

Epic Goal

将当前的单租户系统直接改为多租户系统,基于史诗007已完成的多租户包,在server和web中集成完整的租户数据隔离和租户管理功能,为后续按需拼装单租户或多租户系统提供基础。

核心目标:直接改造现有单租户系统为多租户系统,按照现有包集成模式实现。

Epic Description

Existing System Context

Current relevant functionality:

  • 史诗007已完成11个多租户业务包的复制和26个前端管理界面包的创建
  • 现有server使用单租户包路由(如@d8d/user-module),支持基础的用户、认证、文件等模块
  • 多租户认证模块包(@d8d/auth-module-mt)中已实现租户上下文管理,在认证中间件中设置c.set('tenantId', user.tenantId)
  • web应用已集成用户管理UI包(@d8d/user-management-ui),展示包集成模式
  • web中routes.tsx使用import { UserManagement } from '@d8d/user-management-ui'模式
  • web中api_init.ts使用userClientManager.init('/api/v1/users')初始化客户端
  • 共享包(shared-crud、shared-types、shared-utils)在两套系统中复用
  • 数据库已支持多租户表结构(带_mt后缀的表)

Current limitations:

  • server只能运行单租户模式,不支持多租户
  • web应用只有用户管理使用包模式,其他管理界面仍为本地实现
  • 缺乏租户上下文管理和租户数据隔离
  • 认证系统不支持多租户用户隔离
  • 前端路由不支持租户感知

Technology stack:

  • Backend: Node.js, TypeScript, Hono, TypeORM, PostgreSQL
  • Frontend: React 19, TypeScript, TanStack Query, React Hook Form
  • Authentication: JWT, Redis session management
  • Database: PostgreSQL with tenant isolation
  • Package Management: pnpm workspace

Integration points:

  • 现有server路由注册机制
  • 认证中间件和用户上下文
  • 数据库连接和实体管理
  • 前端API客户端和路由配置
  • 共享UI组件包

Enhancement Details

What's being added/changed:

  • 将server从单租户包改为多租户包(如@d8d/user-module@d8d/user-module-mt
  • 租户上下文管理,支持租户ID在请求链中传递
  • 多租户认证中间件,支持租户感知的用户认证
  • web应用全部改为使用多租户UI包(如@d8d/user-management-ui-mt
  • 前端API客户端支持租户上下文传递
  • 租户数据隔离验证和权限控制

Enhanced architecture:

packages/
├── server/                    # 改为多租户系统
│   ├── src/
│   │   └── index.ts (改为多租户包路由)
├── web/                       # 全部改为多租户UI包
│   ├── src/
│   │   ├── client/
│   │   │   ├── admin/
│   │   │   │   └── routes.tsx (改为导入多租户UI包)
│   │   │   ├── api_init.ts (改为初始化多租户客户端)
│   │   │   └── pages/ (移除本地实现,全部使用包)
└── 多租户包 (史诗007已完成)
    ├── @d8d/user-module-mt
    ├── @d8d/auth-module-mt (已包含租户上下文管理)
    ├── @d8d/file-module-mt
    ├── @d8d/user-management-ui-mt
    ├── @d8d/order-management-ui-mt
    └── ... (其他多租户UI包)

How it integrates:

  • 按照现有server包导入模式,将单租户包替换为多租户包
  • 多租户模块包直接依赖多租户认证模块的认证中间件,其中已包含租户上下文管理
  • 按照web中用户管理UI包的集成模式,将其他管理界面改为使用多租户UI包
  • 保持现有API接口不变,新增租户相关API
  • 前端复用现有UI组件,新增租户管理功能
  • 共享包在两套系统中继续复用

Success criteria:

  • server直接使用多租户包,无需环境变量切换
  • 多租户模式下租户数据完全隔离
  • web应用全部使用多租户UI包,移除本地实现
  • 租户管理界面功能完整,支持租户CRUD操作
  • 现有单租户功能不受影响,保持向后兼容
  • 性能影响小于5%,租户过滤使用数据库索引
  • 完整的测试覆盖和文档

Stories

阶段 1: Server多租户集成

  1. [x] Story 1: Server多租户包替换和集成 - 在server的index.ts文件中,将单租户包替换为多租户包(如@d8d/user-module@d8d/user-module-mt),包括包导入、实体初始化和路由注册,多租户模块包直接依赖多租户认证模块的认证中间件

  2. [x] Story 2: Web多租户UI包全面集成 - 按照现有用户管理UI包的集成模式,将web中所有管理界面改为使用多租户UI包(如@d8d/user-management-ui-mt),移除本地实现

阶段 2: 租户模块和UI包集成

  1. [x] Story 3: 租户模块集成到server - 将租户模块包(@d8d/tenant-module-mt)集成到server中,包括租户管理路由、超级管理员认证和租户数据隔离功能,确保server能够支持租户管理操作

  2. [x] Story 4: 租户UI包集成到Web - 复制web/src/client/admin目录为web/src/client/tenant,在tenant目录中集成@d8d/tenant-management-ui-mt租户管理UI包,使用超级管理员认证系统(superadmin/admin123),添加租户管理路由和超级管理员认证逻辑,确保Web应用能够支持租户管理操作

阶段 3: 系统集成和验证

  1. Story 5: 多租户系统集成测试和验证 - 进行完整的系统集成测试,验证租户数据隔离、权限控制和性能表现,确保系统稳定性和可靠性

Compatibility Requirements

  • 现有单租户APIs保持不变,多租户为可选功能
  • 数据库schema变更向后兼容,多租户使用独立表
  • 现有UI组件和设计模式保持不变
  • 性能影响最小化,租户过滤使用数据库索引
  • 配置向后兼容,现有配置继续有效

Risk Mitigation

Primary Risk: 租户上下文管理复杂,可能影响现有请求处理 Mitigation: 使用AsyncLocalStorage管理租户上下文,确保线程安全 Rollback Plan: 租户功能为可选配置,可随时禁用回退到单租户模式

Primary Risk: 多租户路由切换可能引入路由冲突 Mitigation: 清晰的命名空间分离,单租户和多租户路由独立管理 Rollback Plan: 保持单租户路由不变,多租户路由可独立移除

Primary Risk: 前端租户管理界面与现有功能冲突 Mitigation: 租户管理作为独立模块,与现有管理界面分离 Rollback Plan: 租户管理界面可独立禁用,不影响现有功能

Primary Risk: 性能影响,租户过滤增加查询复杂度 Mitigation: 数据库索引优化,查询条件合并,性能基准测试 Rollback Plan: 租户过滤可配置禁用,回退到无租户过滤模式

Definition of Done

  • 所有故事完成且验收标准满足 (4/5 故事已完成)
  • server支持单租户/多租户模式动态切换
  • 租户数据隔离验证通过
  • 租户管理界面功能完整
  • 现有单租户功能通过回归测试验证
  • 性能基准测试通过,无明显性能下降
  • 完整的单元测试和集成测试覆盖
  • 使用文档和配置说明完整
  • 向后兼容性验证通过

架构设计详情

Server多租户包集成

// 重构后的server入口 - 直接使用多租户包
// 当前实际写法(packages/server/src/index.ts:4-6)
import { userRoutes as userModuleRoutes } from '@d8d/user-module'        // 当前:单租户包
import { authRoutes as authModuleRoutes } from '@d8d/auth-module'        // 当前:单租户包
import { fileRoutes as fileModuleRoutes } from '@d8d/file-module'        // 当前:单租户包

// 改为多租户包
import { userRoutes as userModuleRoutes } from '@d8d/user-module-mt'     // 改为:多租户包
import { authRoutes as authModuleRoutes } from '@d8d/auth-module-mt'     // 改为:多租户包
import { fileRoutes as fileModuleRoutes } from '@d8d/file-module-mt'     // 改为:多租户包

// 多租户认证模块包中已实现租户上下文(packages/auth-module-mt/src/middleware/auth.middleware.mt.ts:44-46)
// 在认证中间件中设置租户上下文:c.set('tenantId', user.tenantId)

Web多租户UI包集成

// web/src/client/admin/routes.tsx - 改为使用多租户UI包
import { UserManagement } from '@d8d/user-management-ui-mt'  // 改为多租户UI包
import { OrderManagement } from '@d8d/order-management-ui-mt'  // 改为多租户UI包
import { GoodsManagement } from '@d8d/goods-management-ui-mt'  // 改为多租户UI包
// ... 其他管理界面包

// 路由配置
{
  path: 'users',
  element: <UserManagement />,  // 使用多租户UI包
},
{
  path: 'orders',
  element: <OrderManagement />,  // 使用多租户UI包
},
{
  path: 'goods',
  element: <GoodsManagement />,  // 使用多租户UI包
}

租户UI包集成实施步骤

# 1. 复制admin目录为tenant目录
cp -r web/src/client/admin web/src/client/tenant

# 2. 修改tenant目录中的关键文件
# - web/src/client/tenant/index.tsx: 替换AuthProvider为租户专用的AuthProvider
# - web/src/client/tenant/hooks/AuthProvider.tsx: 实现租户感知的认证逻辑
# - web/src/client/tenant/pages/Login.tsx: 添加租户选择功能
# - web/src/client/tenant/routes.tsx: 添加租户管理路由

# 3. 集成租户管理UI包
# - 在routes.tsx中添加租户管理路由
# - 在api_init.ts中初始化租户管理客户端
// web/src/client/tenant/routes.tsx - 添加租户管理路由
import { TenantsPage } from '@d8d/tenant-management-ui-mt';

// 在路由配置中添加
{
  path: 'tenants',
  element: <TenantsPage />,  // 租户管理界面
},

// web/src/client/tenant/api_init.ts - 初始化租户管理客户端
import { tenantClientManager } from '@d8d/tenant-management-ui-mt/api';

// 初始化租户管理客户端
tenantClientManager.init('/api/v1/tenants');

// web/src/client/tenant/hooks/AuthProvider.tsx - 使用超级管理员认证
const handleLogin = async (username: string, password: string): Promise<void> => {
  // 使用租户模块的超级管理员登录API
  const response = await tenantClientManager.get().login.$post({
    json: {
      username,
      password
    }
  });
  // ... 其他登录逻辑
};

// web/src/client/tenant/pages/Login.tsx - 超级管理员登录页
// 使用固定的超级管理员账号进行登录,无需租户选择

API客户端初始化

// web/src/client/api_init.ts - 改为初始化多租户客户端
import { userClientManager } from '@d8d/user-management-ui-mt/api'  // 改为多租户包
import { orderClientManager } from '@d8d/order-management-ui-mt/api'  // 改为多租户包
import { goodsClientManager } from '@d8d/goods-management-ui-mt/api'  // 改为多租户包

// 初始化多租户客户端
userClientManager.init('/api/v1/users')
orderClientManager.init('/api/v1/orders')
goodsClientManager.init('/api/v1/goods')

前端租户上下文管理

// 租户上下文Hook
export function useTenant() {
  const [currentTenant, setCurrentTenant] = useState<Tenant | null>(null);

  const switchTenant = useCallback(async (tenantId: number) => {
    // 切换租户逻辑
    const response = await tenantClient.switchTenant({ tenantId });
    setCurrentTenant(response.data);

    // 更新API客户端租户上下文
    updateApiClientTenant(tenantId);
  }, []);

  return {
    currentTenant,
    switchTenant,
    isMultiTenant: !!currentTenant
  };
}

// API客户端租户上下文
export function createApiClient(baseURL: string, tenantId?: number) {
  const client = rpcClient(baseURL);

  // 添加租户上下文到请求头
  if (tenantId) {
    client.$default.headers.set('X-Tenant-ID', tenantId.toString());
  }

  return client;
}

租户管理界面

// 租户管理页面组件
export function TenantsPage() {
  const { data: tenants, isLoading } = useQuery({
    queryKey: ['tenants'],
    queryFn: () => tenantClient.index.$get()
  });

  const createMutation = useMutation({
    mutationFn: (data: CreateTenantDto) => tenantClient.index.$post({ json: data }),
    onSuccess: () => {
      // 刷新租户列表
      queryClient.invalidateQueries({ queryKey: ['tenants'] });
    }
  });

  return (
    <div className="container mx-auto p-6">
      <div className="flex justify-between items-center mb-6">
        <h1 className="text-2xl font-bold">租户管理</h1>
        <Button onClick={() => setShowCreateDialog(true)}>
          创建租户
        </Button>
      </div>

      <DataTable
        data={tenants?.data || []}
        columns={tenantColumns}
        isLoading={isLoading}
      />

      <TenantCreateDialog
        open={showCreateDialog}
        onOpenChange={setShowCreateDialog}
        onSubmit={createMutation.mutate}
      />
    </div>
  );
}

实施策略

阶段化实施

  1. 第一阶段: Server多租户集成(多租户包替换、租户上下文、认证增强)
  2. 第二阶段: Web多租户UI包集成(全面使用多租户UI包、移除本地实现)
  3. 第三阶段: 系统集成和验证(集成测试、性能测试、文档)

数据迁移策略

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

技术实现要点

向后兼容性保证

  1. 包替换: 直接替换单租户包为多租户包,保持相同API接口
  2. API 不变: 现有单租户API接口和行为保持不变
  3. 数据独立: 多租户使用独立的数据表
  4. 性能隔离: 两套系统性能互不影响

安全性考虑

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

性能优化

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

测试策略

单元测试

  • 租户上下文管理测试
  • 多租户包集成测试
  • 认证中间件增强测试
  • 前端租户Hook测试

集成测试

  • 租户数据隔离验证测试
  • 租户管理界面功能测试
  • 性能基准测试

回归测试

  • 现有单租户功能回归测试
  • 向后兼容性验证测试
  • API接口兼容性测试

Story Manager Handoff

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

  • 这是基于史诗007已完成的多租户包,在现有Node.js + TypeScript + Hono + TypeORM + PostgreSQL + React技术栈上的集成
  • 集成点:现有server包导入模式(packages/server/src/index.ts)、web中用户管理UI包的集成模式
  • 需要遵循的现有模式:server直接包导入、web中UI包集成、Hono中间件、TypeORM实体、React组件、TanStack Query数据获取
  • 关键发现:多租户模块包直接依赖多租户认证模块的认证中间件,其中已实现租户上下文管理(c.set('tenantId', user.tenantId)),无需额外添加租户中间件
  • 故事结构简化:Server多租户集成(包替换、实体初始化、路由注册)在同一个文件中完成,前端无需租户上下文管理
  • 关键兼容性要求:现有单租户系统完全不变、直接替换为多租户包、性能影响最小化
  • 每个故事必须包含验证现有功能保持完整的回归测试

该epic应按照现有包集成模式,将server和web直接改为多租户系统,为后续按需拼装不同系统提供基础。"


🤖 Generated with Claude Code via Happy

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