将当前的单租户系统直接改为多租户系统,基于史诗007已完成的多租户包,在server和web中集成完整的租户数据隔离和租户管理功能,为后续按需拼装单租户或多租户系统提供基础。
核心目标:直接改造现有单租户系统为多租户系统,按照现有包集成模式实现。
Current relevant functionality:
@d8d/user-module),支持基础的用户、认证、文件等模块@d8d/auth-module-mt)中已实现租户上下文管理,在认证中间件中设置c.set('tenantId', user.tenantId)@d8d/user-management-ui),展示包集成模式import { UserManagement } from '@d8d/user-management-ui'模式userClientManager.init('/api/v1/users')初始化客户端Current limitations:
Technology stack:
Integration points:
What's being added/changed:
@d8d/user-module → @d8d/user-module-mt)@d8d/user-management-ui-mt)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:
Success criteria:
[x] Story 1: Server多租户包替换和集成 - 在server的index.ts文件中,将单租户包替换为多租户包(如@d8d/user-module → @d8d/user-module-mt),包括包导入、实体初始化和路由注册,多租户模块包直接依赖多租户认证模块的认证中间件
[x] Story 2: Web多租户UI包全面集成 - 按照现有用户管理UI包的集成模式,将web中所有管理界面改为使用多租户UI包(如@d8d/user-management-ui-mt),移除本地实现
Story 3: 租户模块集成到server - 将租户模块包(@d8d/tenant-module-mt)集成到server中,包括租户管理路由、超级管理员认证和租户数据隔离功能,确保server能够支持租户管理操作
Story 4: 租户UI包集成到Web - 将多租户UI包集成到Web应用中,包括租户管理界面、租户切换功能和租户感知的UI组件,确保Web应用能够支持多租户操作和界面展示
Primary Risk: 租户上下文管理复杂,可能影响现有请求处理 Mitigation: 使用AsyncLocalStorage管理租户上下文,确保线程安全 Rollback Plan: 租户功能为可选配置,可随时禁用回退到单租户模式
Primary Risk: 多租户路由切换可能引入路由冲突 Mitigation: 清晰的命名空间分离,单租户和多租户路由独立管理 Rollback Plan: 保持单租户路由不变,多租户路由可独立移除
Primary Risk: 前端租户管理界面与现有功能冲突 Mitigation: 租户管理作为独立模块,与现有管理界面分离 Rollback Plan: 租户管理界面可独立禁用,不影响现有功能
Primary Risk: 性能影响,租户过滤增加查询复杂度 Mitigation: 数据库索引优化,查询条件合并,性能基准测试 Rollback Plan: 租户过滤可配置禁用,回退到无租户过滤模式
// 重构后的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/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包
}
// 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>
);
}
"请为这个brownfield epic开发详细的用户故事。关键考虑因素:
c.set('tenantId', user.tenantId)),无需额外添加租户中间件该epic应按照现有包集成模式,将server和web直接改为多租户系统,为后续按需拼装不同系统提供基础。"
🤖 Generated with Claude Code via Happy
Co-Authored-By: Claude noreply@anthropic.com Co-Authored-By: Happy yesreply@happy.engineering