t
This commit is contained in:
547
.kiro/specs/urbanlifeline-to-pigx-migration/design.md
Normal file
547
.kiro/specs/urbanlifeline-to-pigx-migration/design.md
Normal file
@@ -0,0 +1,547 @@
|
||||
# Design Document
|
||||
|
||||
## Overview
|
||||
|
||||
本设计文档描述了将 urbanLifelineServ 和 urbanLifelineWeb 的**业务功能**迁移到 pigx-ai 平台的技术方案。
|
||||
|
||||
### 核心原则
|
||||
- **只迁移业务代码**:招标、工单、平台管理、AI、消息等业务功能
|
||||
- **复用 pigx 基础设施**:人员、部门、权限、认证完全使用 pigx 原生实现
|
||||
- **适配 pigx 规范**:使用 PigxUser、R<T> 响应格式、Feign 远程调用等
|
||||
|
||||
### 迁移范围
|
||||
|
||||
| 源模块 | 目标位置 | 说明 |
|
||||
|-------|---------|------|
|
||||
| bidding | pigx-app-server-biz | 招标业务 |
|
||||
| workcase | pigx-app-server-biz | 工单业务 |
|
||||
| platform | pigx-app-server-biz | 平台管理 |
|
||||
| ai | pigx-dify(新建模块) | AI对话/知识库/Dify集成 |
|
||||
| message | pigx-app-server-biz | 消息通知 |
|
||||
| file | 使用 pigx-common-oss | 文件服务 |
|
||||
| crontab | pigx-visual/xxl-job | 定时任务 |
|
||||
|
||||
## Architecture
|
||||
|
||||
### 后端模块结构
|
||||
|
||||
```
|
||||
pigx-app-server/
|
||||
├── pigx-app-server-api/ # API接口定义
|
||||
│ └── src/main/java/com/pig4cloud/pigx/app/api/
|
||||
│ ├── entity/ # 业务实体
|
||||
│ │ ├── bidding/ # 招标实体
|
||||
│ │ ├── workcase/ # 工单实体
|
||||
│ │ └── platform/ # 平台实体
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ ├── vo/ # 视图对象
|
||||
│ └── feign/ # Feign接口
|
||||
└── pigx-app-server-biz/ # 业务实现
|
||||
└── src/main/java/com/pig4cloud/pigx/app/
|
||||
├── controller/
|
||||
│ ├── bidding/ # 招标控制器
|
||||
│ ├── workcase/ # 工单控制器
|
||||
│ ├── platform/ # 平台控制器
|
||||
│ └── message/ # 消息控制器
|
||||
├── service/
|
||||
│ ├── bidding/
|
||||
│ ├── workcase/
|
||||
│ ├── platform/
|
||||
│ └── message/
|
||||
└── mapper/
|
||||
├── bidding/
|
||||
├── workcase/
|
||||
├── platform/
|
||||
└── message/
|
||||
|
||||
pigx-dify/ # 新建的AI模块
|
||||
├── pigx-dify-api/ # API接口定义
|
||||
│ └── src/main/java/com/pig4cloud/pigx/dify/api/
|
||||
│ ├── entity/ # AI实体
|
||||
│ │ ├── TbAgent.java # 智能体配置
|
||||
│ │ ├── TbChat.java # 聊天会话
|
||||
│ │ ├── TbChatMessage.java # 聊天消息
|
||||
│ │ └── TbKnowledge.java # 知识库
|
||||
│ ├── dto/ # 数据传输对象
|
||||
│ └── feign/ # Feign接口
|
||||
└── pigx-dify-biz/ # 业务实现
|
||||
└── src/main/java/com/pig4cloud/pigx/dify/
|
||||
├── controller/
|
||||
│ ├── AgentController.java # 智能体管理
|
||||
│ ├── ChatController.java # 对话管理
|
||||
│ └── KnowledgeController.java # 知识库管理
|
||||
├── service/
|
||||
│ ├── AgentService.java
|
||||
│ ├── ChatService.java
|
||||
│ └── KnowledgeService.java
|
||||
├── mapper/
|
||||
│ ├── AgentMapper.java
|
||||
│ ├── ChatMapper.java
|
||||
│ └── KnowledgeMapper.java
|
||||
└── client/
|
||||
└── DifyApiClient.java # Dify API客户端
|
||||
```
|
||||
|
||||
### 前端模块结构
|
||||
|
||||
```
|
||||
pigx-ai-ui/src/
|
||||
├── views/
|
||||
│ ├── urban/ # 迁移的业务视图
|
||||
│ │ ├── bidding/ # 招标页面
|
||||
│ │ ├── workcase/ # 工单页面
|
||||
│ │ └── platform/ # 平台管理页面
|
||||
│ └── dify/ # AI功能页面(新建)
|
||||
│ ├── agent/ # 智能体管理
|
||||
│ ├── chat/ # 对话界面
|
||||
│ └── knowledge/ # 知识库管理
|
||||
├── components/
|
||||
│ └── urban/ # 迁移的共享组件
|
||||
└── api/
|
||||
├── urban/ # 业务API定义
|
||||
│ ├── bidding.ts
|
||||
│ ├── workcase.ts
|
||||
│ └── platform.ts
|
||||
└── dify/ # AI API定义
|
||||
├── agent.ts
|
||||
├── chat.ts
|
||||
└── knowledge.ts
|
||||
```
|
||||
|
||||
## Components and Interfaces
|
||||
|
||||
### 1. 权限模型完全替换
|
||||
|
||||
**核心原则**: 不迁移任何用户、部门、角色、权限数据,完全使用 pigx 原生权限体系。
|
||||
|
||||
#### 权限注解适配
|
||||
|
||||
```java
|
||||
// 源代码 (urbanLifelineServ 使用 @PreAuthorize)
|
||||
@PreAuthorize("hasAuthority('workcase:ticket:create')")
|
||||
@PostMapping
|
||||
public ResultDomain<TbWorkcaseDTO> createWorkcase(@RequestBody TbWorkcaseDTO workcase) {
|
||||
return ResultDomain.success(workcaseService.save(workcase));
|
||||
}
|
||||
|
||||
// 目标代码 (pigx-app-server 使用 @HasPermission)
|
||||
@HasPermission("workcase_ticket_add")
|
||||
@PostMapping
|
||||
public R<TbWorkcaseDTO> createWorkcase(@RequestBody TbWorkcaseDTO workcase) {
|
||||
return R.ok(workcaseService.save(workcase));
|
||||
}
|
||||
```
|
||||
|
||||
#### 权限标识映射规则
|
||||
|
||||
| 源权限标识 | 目标权限标识 | 说明 |
|
||||
|-----------|-------------|------|
|
||||
| workcase:ticket:create | workcase_ticket_add | 工单创建 |
|
||||
| workcase:ticket:update | workcase_ticket_edit | 工单编辑 |
|
||||
| workcase:ticket:view | workcase_ticket_view | 工单查看 |
|
||||
| workcase:ticket:delete | workcase_ticket_del | 工单删除 |
|
||||
| bidding:project:create | bidding_project_add | 招标创建 |
|
||||
| bidding:project:view | bidding_project_view | 招标查看 |
|
||||
|
||||
#### 用户信息获取适配
|
||||
|
||||
```java
|
||||
// 源代码 (JWT 获取用户)
|
||||
Long userId = JwtUtils.getUserId();
|
||||
String username = JwtUtils.getUsername();
|
||||
|
||||
// 目标代码 (pigx SecurityUtils)
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
Long userId = user.getId();
|
||||
String username = user.getUsername();
|
||||
Long tenantId = user.getTenantId(); // 租户ID
|
||||
Long deptId = user.getDeptId(); // 部门ID
|
||||
```
|
||||
|
||||
#### 用户服务调用适配
|
||||
|
||||
```java
|
||||
// 源代码 (直接调用 UserService)
|
||||
@Autowired
|
||||
private UserService userService;
|
||||
User user = userService.getById(userId);
|
||||
|
||||
// 目标代码 (通过 Feign 调用 pigx-upms)
|
||||
@Autowired
|
||||
private RemoteUserService remoteUserService;
|
||||
R<SysUser> result = remoteUserService.selectById(userId);
|
||||
SysUser user = result.getData();
|
||||
```
|
||||
|
||||
### 2. 菜单和权限配置
|
||||
|
||||
**不迁移源系统的菜单和权限数据**,在 pigx 中重新配置:
|
||||
|
||||
#### 菜单配置 (sys_menu 表)
|
||||
|
||||
```sql
|
||||
-- 在 pigx 的 sys_menu 表中添加业务功能菜单
|
||||
INSERT INTO sys_menu (menu_id, name, permission, path, parent_id, icon, sort, type, tenant_id) VALUES
|
||||
(1000, '工单管理', 'workcase_menu', '/workcase', 0, 'workcase', 1, '0', 1),
|
||||
(1001, '工单列表', 'workcase_ticket_view', '/workcase/list', 1000, '', 1, '1', 1),
|
||||
(1002, '创建工单', 'workcase_ticket_add', '', 1000, '', 2, '2', 1),
|
||||
(1003, '编辑工单', 'workcase_ticket_edit', '', 1000, '', 3, '2', 1),
|
||||
(1004, '删除工单', 'workcase_ticket_del', '', 1000, '', 4, '2', 1),
|
||||
|
||||
(2000, '招标管理', 'bidding_menu', '/bidding', 0, 'bidding', 2, '0', 1),
|
||||
(2001, '招标项目', 'bidding_project_view', '/bidding/project', 2000, '', 1, '1', 1),
|
||||
(2002, '创建项目', 'bidding_project_add', '', 2000, '', 2, '2', 1),
|
||||
(2003, '编辑项目', 'bidding_project_edit', '', 2000, '', 3, '2', 1);
|
||||
```
|
||||
|
||||
#### 角色权限分配
|
||||
|
||||
使用 pigx 现有的角色管理功能,为角色分配新的业务权限:
|
||||
|
||||
```sql
|
||||
-- 为管理员角色分配所有业务权限
|
||||
INSERT INTO sys_role_menu (role_id, menu_id)
|
||||
SELECT 1, menu_id FROM sys_menu WHERE permission LIKE 'workcase_%' OR permission LIKE 'bidding_%';
|
||||
```
|
||||
|
||||
#### 数据权限适配
|
||||
|
||||
```java
|
||||
// 源代码 (可能没有租户隔离)
|
||||
@PostMapping("/list")
|
||||
public ResultDomain<TbWorkcaseDTO> getWorkcaseList(@RequestBody TbWorkcaseDTO filter) {
|
||||
return ResultDomain.success(workcaseService.list(filter));
|
||||
}
|
||||
|
||||
// 目标代码 (自动添加租户和部门过滤)
|
||||
@PostMapping("/list")
|
||||
@HasPermission("workcase_ticket_view")
|
||||
public R<List<TbWorkcaseDTO>> getWorkcaseList(@RequestBody TbWorkcaseDTO filter) {
|
||||
// pigx 会自动根据用户的租户ID和数据权限过滤数据
|
||||
return R.ok(workcaseService.list(filter));
|
||||
}
|
||||
```
|
||||
|
||||
```java
|
||||
// 源代码 (urbanLifelineServ 使用 ResultDomain)
|
||||
@GetMapping("/list")
|
||||
public ResultDomain<Workcase> list() {
|
||||
// ResultDomain 包含 dataList 字段
|
||||
return ResultDomain.success(workcaseService.list());
|
||||
}
|
||||
|
||||
// 目标代码 (使用 pigx R<T>)
|
||||
@GetMapping("/list")
|
||||
public R<List<Workcase>> list() {
|
||||
return R.ok(workcaseService.list());
|
||||
}
|
||||
|
||||
// 或者使用分页 (pigx IPage)
|
||||
@GetMapping("/page")
|
||||
public R<IPage<Workcase>> page(Page page) {
|
||||
return R.ok(workcaseService.page(page));
|
||||
}
|
||||
```
|
||||
|
||||
**响应格式映射**:
|
||||
| 源格式 (ResultDomain) | 目标格式 (R<T>) |
|
||||
|---------------------|----------------|
|
||||
| ResultDomain.success(data) | R.ok(data) |
|
||||
| ResultDomain.fail(msg) | R.failed(msg) |
|
||||
| dataList 字段 | data 字段 (直接返回List) |
|
||||
| code/message | code/msg |
|
||||
|
||||
### 3. 响应格式适配
|
||||
|
||||
```java
|
||||
// 源代码
|
||||
Long userId = JwtUtils.getUserId();
|
||||
|
||||
// 目标代码 (使用 pigx SecurityUtils)
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
Long userId = user.getId();
|
||||
Long tenantId = user.getTenantId();
|
||||
Long deptId = user.getDeptId();
|
||||
```
|
||||
|
||||
### 4. 文件上传适配
|
||||
|
||||
```java
|
||||
// 源代码 (MinIO直接调用)
|
||||
minioClient.putObject(bucket, objectName, inputStream);
|
||||
|
||||
// 目标代码 (使用 pigx OSS)
|
||||
@Autowired
|
||||
private OssTemplate ossTemplate;
|
||||
ossTemplate.putObject(bucket, objectName, inputStream);
|
||||
```
|
||||
|
||||
### 5. 前端 API 调用适配
|
||||
|
||||
```typescript
|
||||
// 源代码 (urbanLifelineWeb)
|
||||
import { request } from '@shared/utils/request'
|
||||
export const getWorkcaseList = () => request.get('/workcase/list')
|
||||
|
||||
// 目标代码 (pigx-ai-ui)
|
||||
import request from '/@/utils/request'
|
||||
export const getWorkcaseList = () => request.get('/app/workcase/list')
|
||||
```
|
||||
|
||||
### 6. pigx-dify 模块设计(新增)
|
||||
|
||||
#### 6.1 模块定位
|
||||
pigx-dify 是独立的 AI 服务模块,保留原 urbanLifeline 的 AI 功能和 Dify 平台集成,不与 pigx-knowledge 混合使用。
|
||||
|
||||
#### 6.2 核心组件
|
||||
|
||||
**DifyApiClient 保留原有功能:**
|
||||
```java
|
||||
@Component
|
||||
public class DifyApiClient {
|
||||
// 知识库管理
|
||||
public DatasetCreateResponse createDataset(DatasetCreateRequest request);
|
||||
public DatasetListResponse listDatasets(int page, int limit);
|
||||
|
||||
// 对话功能(保留流式和阻塞两种模式)
|
||||
public void streamChat(ChatRequest request, String apiKey, StreamCallback callback);
|
||||
public ChatResponse blockingChat(ChatRequest request, String apiKey);
|
||||
|
||||
// 工作流调用
|
||||
public WorkflowRunResponse runWorkflowBlocking(WorkflowRunRequest request, String apiKey);
|
||||
}
|
||||
```
|
||||
|
||||
**数据模型保持不变:**
|
||||
```java
|
||||
// 智能体配置
|
||||
@TableName("tb_agent")
|
||||
public class TbAgent {
|
||||
private String agentId;
|
||||
private String name;
|
||||
private String description;
|
||||
private String difyApiKey; // 保留 Dify API Key
|
||||
private String difyAgentId; // 保留 Dify Agent ID
|
||||
private Long tenantId; // 新增:租户ID
|
||||
}
|
||||
|
||||
// 聊天会话
|
||||
@TableName("tb_chat")
|
||||
public class TbChat {
|
||||
private String chatId;
|
||||
private String agentId;
|
||||
private String userId; // 关联 pigx sys_user
|
||||
private String conversationId; // Dify conversation ID
|
||||
private Long tenantId; // 新增:租户ID
|
||||
}
|
||||
|
||||
// 聊天消息(保持原有结构)
|
||||
@TableName("tb_chat_message")
|
||||
public class TbChatMessage {
|
||||
private String messageId;
|
||||
private String chatId;
|
||||
private String content;
|
||||
private String role; // user/ai/recipient
|
||||
private String difyMessageId; // 保留 Dify 消息ID
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.3 权限适配
|
||||
|
||||
```java
|
||||
@RestController
|
||||
@RequestMapping("/dify")
|
||||
public class ChatController {
|
||||
|
||||
@Autowired
|
||||
private DifyApiClient difyClient;
|
||||
|
||||
// 权限注解适配
|
||||
@HasPermission("dify_chat_create") // 原: @PreAuthorize("hasAuthority('ai:chat:create')")
|
||||
@PostMapping("/chat/stream")
|
||||
public SseEmitter streamChat(@RequestBody ChatRequest request) {
|
||||
PigxUser user = SecurityUtils.getUser();
|
||||
// 保留原有的流式响应逻辑
|
||||
return chatService.streamChat(request, user);
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
#### 6.4 配置管理
|
||||
|
||||
```yaml
|
||||
# application.yml
|
||||
dify:
|
||||
api:
|
||||
base-url: ${DIFY_API_BASE_URL:https://api.dify.ai}
|
||||
default-api-key: ${DIFY_DEFAULT_API_KEY}
|
||||
enabled: true
|
||||
```
|
||||
|
||||
## Data Models
|
||||
|
||||
### 数据库迁移策略
|
||||
|
||||
1. **表结构转换**: PostgreSQL DDL → MySQL DDL
|
||||
2. **添加租户字段**: 所有业务表添加 `tenant_id` 字段
|
||||
3. **用户关联**: `user_id` 关联到 pigx 的 `sys_user.user_id`
|
||||
|
||||
### 核心业务表
|
||||
|
||||
**工单模块 (workcase)**:
|
||||
```sql
|
||||
CREATE TABLE tb_workcase (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
title VARCHAR(255) NOT NULL COMMENT '工单标题',
|
||||
content TEXT COMMENT '工单内容',
|
||||
status TINYINT DEFAULT 0 COMMENT '状态',
|
||||
creator_id BIGINT COMMENT '创建人ID(关联sys_user)',
|
||||
assignee_id BIGINT COMMENT '处理人ID(关联sys_user)',
|
||||
tenant_id BIGINT DEFAULT 1 COMMENT '租户ID',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='工单表';
|
||||
```
|
||||
|
||||
**招标模块 (bidding)**:
|
||||
```sql
|
||||
CREATE TABLE tb_bidding_project (
|
||||
id BIGINT NOT NULL AUTO_INCREMENT COMMENT '主键',
|
||||
project_name VARCHAR(255) NOT NULL COMMENT '项目名称',
|
||||
project_code VARCHAR(64) COMMENT '项目编号',
|
||||
status TINYINT DEFAULT 0 COMMENT '状态',
|
||||
creator_id BIGINT COMMENT '创建人ID',
|
||||
tenant_id BIGINT DEFAULT 1 COMMENT '租户ID',
|
||||
create_time DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
update_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
|
||||
del_flag CHAR(1) DEFAULT '0',
|
||||
PRIMARY KEY (id)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='招标项目表';
|
||||
```
|
||||
|
||||
### 类型映射
|
||||
|
||||
| PostgreSQL | MySQL | 说明 |
|
||||
|-----------|-------|------|
|
||||
| SERIAL | INT AUTO_INCREMENT | 自增 |
|
||||
| BIGSERIAL | BIGINT AUTO_INCREMENT | 大整数自增 |
|
||||
| TEXT | TEXT | 文本 |
|
||||
| JSONB | JSON | JSON数据 |
|
||||
| BOOLEAN | TINYINT(1) | 布尔 |
|
||||
| TIMESTAMP | DATETIME | 时间戳 |
|
||||
|
||||
|
||||
|
||||
## Correctness Properties
|
||||
|
||||
*A property is a characteristic or behavior that should hold true across all valid executions of a system-essentially, a formal statement about what the system should do.*
|
||||
|
||||
### Property 1: 后端代码迁移完整性
|
||||
|
||||
*For any* 源项目中的业务模块(bidding, workcase, platform, message),迁移后的 pigx-app-server 应该包含对应的 Controller、Service、Mapper 层代码,且代码可以通过编译。
|
||||
|
||||
**Validates: Requirements 1.1, 2.1, 3.1, 5.1**
|
||||
|
||||
### Property 2: 权限注解适配正确性
|
||||
|
||||
*For any* 迁移后的 Controller 方法,应该使用 pigx 的 @HasPermission 注解而非 @PreAuthorize,且权限标识符合 pigx 命名规范(module_action 格式)。
|
||||
|
||||
**Validates: Requirements 1.2, 2.2, 11.1, 11.2**
|
||||
|
||||
### Property 3: 数据库迁移正确性
|
||||
|
||||
*For any* 源项目的 PostgreSQL 业务表,转换后的 MySQL DDL 应该:
|
||||
- 语法正确,可在 MySQL 中执行
|
||||
- 包含 tenant_id 租户字段
|
||||
- 用户关联字段正确引用 sys_user.user_id
|
||||
|
||||
**Validates: Requirements 1.3, 2.4, 3.3, 4.4, 5.4, 8.1, 8.2, 8.3, 8.4**
|
||||
|
||||
### Property 4: 前端页面迁移完整性
|
||||
|
||||
*For any* 源项目前端包中的页面组件,迁移后应该存在于 pigx-ai-ui 的对应目录下,且组件可以被正确导入。
|
||||
|
||||
**Validates: Requirements 1.4, 2.5, 3.4, 4.5**
|
||||
|
||||
### Property 5: API调用适配正确性
|
||||
|
||||
*For any* 迁移后的前端 API 调用代码,应该:
|
||||
- 使用 pigx 的 request 工具(从 /@/utils/request 导入)
|
||||
- API 路径符合 pigx 网关路由规则(/app/* 前缀)
|
||||
- 响应处理适配 R<T> 格式
|
||||
|
||||
**Validates: Requirements 1.5, 9.4**
|
||||
|
||||
### Property 6: 数据迁移完整性
|
||||
|
||||
*For any* 源数据库中的业务数据记录,迁移到目标数据库后,记录数量应该相等,关键字段值应该保持一致。
|
||||
|
||||
**Validates: Requirements 8.5**
|
||||
|
||||
### Property 8: 用户服务调用正确性
|
||||
|
||||
*For any* 迁移后的业务代码中涉及用户信息获取的地方,应该使用 SecurityUtils.getUser() 获取当前用户,或通过 RemoteUserService 进行 Feign 调用,而非原有的 UserService。
|
||||
|
||||
**Validates: Requirements 11.4, 11.5**
|
||||
|
||||
## Error Handling
|
||||
|
||||
### 迁移错误处理
|
||||
|
||||
1. **代码编译失败**: 记录编译错误,提供修复建议
|
||||
2. **类型转换失败**: 标记不兼容类型,提供替代方案
|
||||
3. **依赖缺失**: 自动添加缺失的 pigx 依赖
|
||||
4. **数据迁移失败**: 支持断点续传,记录失败记录
|
||||
|
||||
### 回滚策略
|
||||
|
||||
- 代码迁移:保留源文件,支持回退
|
||||
- 数据库迁移:生成回滚脚本
|
||||
- 配置变更:版本化管理
|
||||
|
||||
## Testing Strategy
|
||||
|
||||
### 单元测试
|
||||
|
||||
- Service 层业务逻辑测试
|
||||
- Mapper 层数据访问测试
|
||||
- 工具类函数测试
|
||||
|
||||
### 集成测试
|
||||
|
||||
- API 端到端测试
|
||||
- 用户认证流程测试
|
||||
- 文件上传下载测试
|
||||
|
||||
### 属性测试
|
||||
|
||||
使用 Java 的 jqwik 进行属性测试:
|
||||
|
||||
```java
|
||||
@Property
|
||||
void tenantIsolation(@ForAll @From("businessEntity") BusinessEntity entity) {
|
||||
// 验证查询结果只包含当前租户数据
|
||||
List<BusinessEntity> results = service.list();
|
||||
assertThat(results).allMatch(e -> e.getTenantId().equals(currentTenantId));
|
||||
}
|
||||
|
||||
@Property
|
||||
void ddlConversion(@ForAll @From("postgresTable") String pgDdl) {
|
||||
String mysqlDdl = converter.convert(pgDdl);
|
||||
assertThat(mysqlDdl).contains("tenant_id");
|
||||
assertThat(mysqlDdl).canExecuteOnMysql();
|
||||
}
|
||||
```
|
||||
|
||||
### 测试配置
|
||||
|
||||
- 属性测试最少运行 100 次迭代
|
||||
- 使用 Testcontainers 进行数据库测试
|
||||
- 前端测试使用 Vitest
|
||||
### Property 9: 租户隔离正确性
|
||||
|
||||
*For any* 迁移后的业务表查询,应该自动包含 tenant_id 条件,确保多租户数据隔离。
|
||||
|
||||
**Validates: Requirements 8.4, 11.6**
|
||||
Reference in New Issue
Block a user