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

18 KiB
Raw Blame History

Design Document

Overview

本设计文档描述了将 urbanLifelineServ 和 urbanLifelineWeb 的业务功能迁移到 pigx-ai 平台的技术方案。

核心原则

  • 只迁移业务代码招标、工单、平台管理、AI、消息等业务功能
  • 复用 pigx 基础设施:人员、部门、权限、认证完全使用 pigx 原生实现
  • 适配 pigx 规范:使用 PigxUser、R 响应格式、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 原生权限体系。

权限注解适配

// 源代码 (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🎫create workcase_ticket_add 工单创建
workcase🎫update workcase_ticket_edit 工单编辑
workcase🎫view workcase_ticket_view 工单查看
workcase🎫delete workcase_ticket_del 工单删除
bidding:project:create bidding_project_add 招标创建
bidding:project:view bidding_project_view 招标查看

用户信息获取适配

// 源代码 (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

用户服务调用适配

// 源代码 (直接调用 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 表)

-- 在 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 现有的角色管理功能,为角色分配新的业务权限:

-- 为管理员角色分配所有业务权限
INSERT INTO sys_role_menu (role_id, menu_id) 
SELECT 1, menu_id FROM sys_menu WHERE permission LIKE 'workcase_%' OR permission LIKE 'bidding_%';

数据权限适配

// 源代码 (可能没有租户隔离)
@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));
}
// 源代码 (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)
ResultDomain.success(data) R.ok(data)
ResultDomain.fail(msg) R.failed(msg)
dataList 字段 data 字段 (直接返回List)
code/message code/msg

3. 响应格式适配

// 源代码
Long userId = JwtUtils.getUserId();

// 目标代码 (使用 pigx SecurityUtils)
PigxUser user = SecurityUtils.getUser();
Long userId = user.getId();
Long tenantId = user.getTenantId();
Long deptId = user.getDeptId();

4. 文件上传适配

// 源代码 (MinIO直接调用)
minioClient.putObject(bucket, objectName, inputStream);

// 目标代码 (使用 pigx OSS)
@Autowired
private OssTemplate ossTemplate;
ossTemplate.putObject(bucket, objectName, inputStream);

5. 前端 API 调用适配

// 源代码 (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 保留原有功能:

@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);
}

数据模型保持不变:

// 智能体配置
@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 权限适配

@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 配置管理

# 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):

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):

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 格式

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 进行属性测试:

@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