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