Files
urbanLifeline/.kiro/specs/urbanlifeline-to-pigx-migration/design.md

547 lines
18 KiB
Markdown
Raw Normal View History

2026-01-14 15:42:26 +08:00
# 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**