Files
urbanLifeline/.kiro/specs/urbanlifeline-to-pigx-migration/design.md
2026-01-14 15:42:26 +08:00

547 lines
18 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# 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**