diff --git a/.gitignore b/.gitignore index 0245deff..be6fc548 100644 --- a/.gitignore +++ b/.gitignore @@ -97,3 +97,7 @@ tongweb_62318.properties .superpowers/ tmp/ + +.codegraph/ + +.claude/ \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md deleted file mode 100644 index 4ef73186..00000000 --- a/CLAUDE.md +++ /dev/null @@ -1,669 +0,0 @@ -# CLAUDE.md - -This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository. - -## 快速参考 - -**启动项目:** -- 后端: `mvn spring-boot:run` 或运行 `ry.bat` -- 前端: `cd ruoyi-ui && npm run dev` - -**访问地址:** -- 前端: http://localhost:80 -- 后端: http://localhost:8080 -- Swagger: http://localhost:8080/swagger-ui/index.html -- Druid 监控: http://localhost:8080/druid/ (ruoyi/123456) - -**测试账号:** -- 用户名: `admin` -- 密码: `admin123` - -**获取 Token:** -```bash -POST http://localhost:8080/login/test?username=admin&password=admin123 -``` - ---- - -## 项目概述 - -**纪检初核系统** - 基于 **若依管理系统 v3.9.1** 构建的企业级前后端分离管理系统,用于员工异常行为风险识别。 - -### 技术栈版本 - -| 后端技术 | 版本 | 前端技术 | 版本 | -|-----------------------------|--------|------------|---------| -| Spring Boot | 3.5.8 | Vue.js | 2.6.12 | -| Java | 21 | Element UI | 2.15.14 | -| MyBatis Spring Boot Starter | 3.0.5 | Vuex | 3.6.0 | -| MySQL Connector | 8.2.0 | Vue Router | 3.4.9 | -| SpringDoc OpenAPI | 2.8.14 | Axios | 0.28.1 | -| EasyExcel | 3.3.4 | ECharts | 5.4.0 | -| Quartz | 2.5.2 | Sass | 1.32.13 | - ---- - -## 常用命令 - -### 后端 (Maven) - -```bash -# 编译项目 -mvn clean compile - -# 运行应用 (开发环境) -mvn spring-boot:run - -# 打包部署 -mvn clean package - -# Windows 启动 -ry.bat - -# Linux/Mac 启动 -./ry.sh start -``` - -### 前端 (npm) - -```bash -cd ruoyi-ui - -# 安装依赖 (推荐使用国内镜像) -npm install --registry=https://registry.npmmirror.com - -# 开发服务器 (端口 80) -npm run dev - -# 生产构建 -npm run build:prod - -# 预览生产构建 -npm run preview -``` - -### 数据库初始化 - -```bash -# 初始化若依框架基础表 -mysql -u root -p < sql/ry_20250522.sql - -# 初始化定时任务表 -mysql -u root -p < sql/quartz.sql - -# 导入业务表(根据需要执行) -mysql -u root -p ccdi < sql/dpc_employee.sql -mysql -u root -p ccdi < sql/dpc_intermediary_blacklist.sql -# ... 其他业务表脚本 -``` - -**注意:** -- 业务表脚本文件名以 `ccdi_` 或 `dpc_` 开头 -- 部分脚本包含菜单数据,需要按顺序执行 -- 数据库需要先创建(数据库名: `ccdi`) - ---- - -## 模块架构 - -``` -ccdi/ -├── ruoyi-admin/ # 主应用入口 (Spring Boot 启动类) -├── ruoyi-framework/ # 核心框架 (Security, Config, Filters) -├── ruoyi-system/ # 系统管理 (Users, Roles, Menus, Depts) -├── ruoyi-common/ # 通用工具 (annotations, utils, constants) -├── ruoyi-quartz/ # 定时任务 -├── ruoyi-generator/ # 代码生成器 -├── ccdi-info-collection/ # 【核心业务模块】信息采集 -├── ccdi-project/ # 【核心业务模块】项目管理 -├── ccdi-lsfx/ # 【核心业务模块】流水分析对接 -├── lsfx-mock-server/ # 流水分析模拟服务器 (Python) -├── ruoyi-ui/ # 前端 Vue 应用 -├── sql/ # 数据库脚本 -├── bin/ # 启动脚本 -└── doc/ # 项目文档 -``` - -### 模块依赖关系 - -``` -ruoyi-admin (启动模块) - ├── ruoyi-framework (核心安全配置) - ├── ruoyi-system (系统核心业务) - ├── ruoyi-common (共享工具) - ├── ruoyi-quartz (定时任务) - ├── ruoyi-generator (代码生成) - ├── ccdi-info-collection (信息采集模块) - │ └── 依赖 ruoyi-common - ├── ccdi-project (项目管理模块) - │ └── 依赖 ruoyi-common - └── ccdi-lsfx (流水分析对接模块) - └── 依赖 ruoyi-common -``` - -**添加新业务模块:** -1. 在根目录 `pom.xml` 的 `` 中添加新模块 -2. 在新模块的 `pom.xml` 中添加对 `ruoyi-common` 的依赖 -3. 在 `ruoyi-admin/pom.xml` 中添加对新模块的依赖 -4. 在新模块中按照分层规范创建 controller/service/mapper/domain 包 - -### ccdi-info-collection 业务模块 (核心) - -自定义业务模块,包含以下核心功能: - -| 功能 | Controller | 实体类 | -|----------|---------------------------------------|-----------------------------| -| 员工基础信息 | CcdiBaseStaffController | CcdiBaseStaff | -| 中介黑名单 | CcdiIntermediaryController | CcdiBizIntermediary | -| 员工家庭关系 | CcdiStaffFmyRelationController | CcdiStaffFmyRelation | -| 员工企业关系 | CcdiStaffEnterpriseRelationController | CcdiStaffEnterpriseRelation | -| 信贷客户家庭关系 | CcdiCustFmyRelationController | CcdiCustFmyRelation | -| 信贷客户企业关系 | CcdiCustEnterpriseRelationController | CcdiCustEnterpriseRelation | -| 员工调动记录 | CcdiStaffTransferController | CcdiStaffTransfer | -| 员工招聘记录 | CcdiStaffRecruitmentController | CcdiStaffRecruitment | -| 采购交易 | CcdiPurchaseTransactionController | CcdiPurchaseTransaction | - -**分层结构:** - -- Controller: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/` -- Service: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/` -- Mapper: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/` -- Domain: `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/domain/` - - dto/: 数据传输对象 - - vo/: 视图对象 - - excel/: Excel导入导出实体 -- XML映射: `ccdi-info-collection/src/main/resources/mapper/info/collection/` - -### ccdi-project 业务模块 (核心) - -项目管理模块,用于管理纪检初核项目的全生命周期: - -**核心功能:** -- 项目创建、更新、删除、查询 -- 项目状态管理 (进行中、已完成、已归档) -- 项目统计(按状态统计数量) -- 模型参数配置管理 - -**主要 Controller:** -- CcdiProjectController: 项目管理 -- CcdiModelParamController: 模型参数配置 - -**分层结构:** -- Controller: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/` -- Service: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/` -- Mapper: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/` -- Domain: `ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/` -- XML映射: `ccdi-project/src/main/resources/mapper/ccdi/project/` - -### ccdi-lsfx 业务模块 (核心) - -流水分析平台对接模块,用于与外部流水分析系统交互: - -**核心功能:** -- 获取访问令牌 (Token) -- 上传流水文件并解析 -- 拉取行内流水数据 -- 查询解析状态和结果 -- 获取银行流水明细 - -**主要组件:** -- LsfxAnalysisClient: 流水分析平台客户端 -- LsfxTestController: 测试接口 - -**配置项 (application-dev.yml):** -```yaml -lsfx: - api: - base-url: http://localhost:8000 # 流水分析平台地址 - app-id: your-app-id - app-secret: your-app-secret - client-id: your-client-id - endpoints: - get-token: /api/auth/token - upload-file: /api/files/upload - fetch-inner-flow: /api/flow/inner -``` - -**分层结构:** -- Client: `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/` -- Controller: `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/controller/` -- Domain: `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/domain/` - - request/: 请求对象 - - response/: 响应对象 -- Config: `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/config/` - -### lsfx-mock-server (开发测试工具) - -Python 实现的流水分析平台模拟服务器,用于本地开发和测试: - -**用途:** -- 模拟流水分析平台的 API 接口 -- 提供测试数据和模拟响应 -- 支持错误场景模拟 - -**启动方式:** -```bash -cd lsfx-mock-server -python app.py # 默认监听 http://localhost:8000 -``` - ---- - -## 后端开发规范 - -### 通用规范 - -- **新模块命名**: 项目英文名首字母集合 + 主要功能 (如 `ruoyi-info-collection`) -- **代码分离**: 新功能代码与若依框架自带代码分离,Controller 放在新模块中 -- **审计字段**: 实体类不继承 BaseEntity,单独添加审计字段,通过注释实现自动插入 - -### Java 代码风格 - -```java -// 使用 @Data 注解 -@Data -public class CcdiBaseStaff { - // 审计字段通过注释实现自动插入 - /** 创建者 */ - private String createBy; - /** 创建时间 */ - private Date createTime; - /** 更新者 */ - private String updateBy; - /** 更新时间 */ - private Date updateTime; -} - - // 服务层使用 @Resource 注入 - @Resource - private ICcdiBaseStaffService baseStaffService; -``` - -### 分层规范 - -- **Controller**: 所有接口添加 Swagger 注释,分页使用 MyBatis Plus Page -- **Service**: 简单 CRUD 用 MyBatis Plus 方法,复杂操作在 XML 写 SQL -- **DTO/VO**: 接口传参使用独立 DTO,返回使用独立 VO,不与 entity 混用 -- **Mapper**: 简单操作继承 BaseMapper,复杂操作在 XML 中定义 - -### 禁止事项 - -- **禁止使用全限定类名**: 必须使用 `import` 语句导入类,不要在代码中使用 `java.util.List` 这样的全限定名 -- **禁止使用 `extends ServiceImpl<>`**: Service 接口和实现类分离定义 -- **禁止 Entity 混用**: DTO、VO、Excel 类必须独立,不与 Entity 混用 -- **禁止缺少 `@Resource`**: Service 注入必须使用 `@Resource` 注解 - -### API 响应格式 - -```java -// 成功 -AjaxResult.success("操作成功", data); - -// 错误 -AjaxResult.error("操作失败"); - -// 分页 -Page page = new Page<>(pageNum, pageSize); -IPage result = baseStaffMapper.selectPage(page, queryWrapper); -return AjaxResult.success(result); -``` - ---- - -## 前端开发规范 - -### 目录结构 - -``` -ruoyi-ui/src/ -├── api/ # API 请求定义 (与后端 Controller 对应) -├── views/ # 页面组件 (按功能模块组织) -│ ├── ccdiBaseStaff/ -│ ├── ccdiIntermediary/ -│ └── ... -├── components/ # 可复用组件 (复杂组件需拆分) -├── router/ # 路由配置 -└── store/ # Vuex 状态管理 -``` - -### API 调用示例 - -```javascript -import request from '@/utils/request' - -export function listStaff(query) { - return request({ - url: '/ccdi/baseStaff/list', - method: 'get', - params: query - }) -} -``` - -### 菜单联动 - -添加页面和组件后,需要同步修改数据库中的菜单表 (`sys_menu`)。 - ---- - -## 特殊功能 - -### 异步导入 - -支持大数据量异步 Excel 导入,通过 taskId 查询导入状态: - -```java -@PostMapping("/import") -public AjaxResult asyncImport(@RequestParam("file") MultipartFile file) { - String taskId = asyncImportService.startImport(file); - return AjaxResult.success("导入任务已启动", taskId); -} - -@GetMapping("/import/status/{taskId}") -public AjaxResult getImportStatus(@PathVariable String taskId) { - return AjaxResult.success(asyncImportService.getStatus(taskId)); -} -``` - -**导入流程:** -1. 前端上传 Excel 文件 -2. 后端异步处理,返回 taskId -3. 前端轮询 `/import/status/{taskId}` 获取导入进度 -4. 导入完成后,可获取成功/失败数据统计 - -**导入结果处理:** -- 只返回导入失败的数据(含失败原因) -- 成功数据不返回,减少响应体积 -- 支持批量插入,提高性能 - -### EasyExcel 字典下拉框 - -导入模板支持字典下拉框配置,提升数据录入准确性。使用 `DictDropdownWriteHandler` 实现。 - -### 权限控制 - -基于 Spring Security + JWT 的角色菜单权限系统: - -- 权限格式: `system:user:edit`, `ccdi:staff:list` -- 数据权限: 支持全部、自定义、部门等范围 - ---- - -## 测试与验证 - -### 测试账号 - -- **用户名**: `admin` -- **密码**: `admin123` - -### 登录获取 Token - -```bash -# 登录接口 -POST /login/test?username=admin&password=admin123 -``` - -### API 文档 - -- **Swagger UI**: `/swagger-ui/index.html` -- **API Docs**: `/v3/api-docs` - -### 测试规范 - -- 不在命令行启动后端进行测试 -- 生成可执行的测试脚本进行验证 -- 测试完成后保存接口输出并生成测试用例报告 - -### 开发调试技巧 - -**使用 Swagger 测试接口:** -1. 访问 `/swagger-ui/index.html` -2. 点击接口展开详情 -3. 点击 "Try it out" 进行测试 -4. 填写参数后点击 "Execute" 执行 - -**查看 SQL 执行日志:** -- 在 `application.yml` 中设置日志级别: `com.ruoyi: debug` -- 使用 Druid 监控台查看慢 SQL - -**前端代理配置:** -前端开发服务器通过代理转发请求到后端: -- 前端地址: `http://localhost:80` -- 后端地址: `http://localhost:8080` -- 代理配置文件: `ruoyi-ui/vue.config.js` - ---- - -## 配置说明 - -| 配置项 | 值 | -|---------|-------------------| -| 后端端口 | 8080 | -| 前端开发端口 | 80 | -| 默认管理员 | admin/admin123 | -| JWT 有效期 | 30 分钟 | -| 文件上传限制 | 单文件 10MB, 总计 20MB | - -### 配置文件位置 - -| 配置 | 路径 | -|----------|------------------------------------------------------| -| 主配置 | `ruoyi-admin/src/main/resources/application.yml` | -| 开发环境 | `ruoyi-admin/src/main/resources/application-dev.yml` | -| 数据库连接 | `application-dev.yml` | -| Redis 配置 | `application-dev.yml` | - -### 数据源配置 - -项目使用 Druid 连接池,支持主从分离(默认关闭从库): - -- **数据库连接**: `jdbc:mysql://host:3306/ccdi` -- **初始连接数**: 5 -- **最小连接数**: 10 -- **最大连接数**: 20 -- **慢 SQL 记录**: 超过 1000ms 的 SQL 会被记录 - -### Redis 配置 - -- **默认端口**: 6379 -- **数据库索引**: 0 -- **连接超时**: 10s - -### 流水分析平台配置 - -项目集成了外部流水分析平台,配置项位于 `application-dev.yml`: - -```yaml -lsfx: - api: - base-url: http://localhost:8000 # 流水分析平台基础地址 - app-id: ccdi-app # 应用ID - app-secret: ccdi-secret-2024 # 应用密钥 - client-id: ccdi-client # 客户端ID - endpoints: - get-token: /api/auth/token # 获取令牌接口 - upload-file: /api/files/upload # 文件上传接口 - fetch-inner-flow: /api/flow/inner # 拉取行内流水接口 -``` - -**开发环境使用 Mock 服务器:** -- 本地开发时,将 `base-url` 设置为 `http://localhost:8000` -- 启动 `lsfx-mock-server` 提供模拟接口 -- 生产环境替换为真实的流水分析平台地址 - -### MCP 配置 - -项目使用 MCP (Model Context Protocol) 连接数据库,配置文件: `.mcp.json` - -```json -{ - "mcpServers": { - "mysql": { - "command": "npx", - "args": ["-y", "@fhuang/mcp-mysql-server"], - "env": { - "MYSQL_HOST": "116.62.17.81", - "MYSQL_PORT": "3306", - "MYSQL_USER": "root", - "MYSQL_PASSWORD": "Kfcx@1234", - "MYSQL_DATABASE": "ccdi" - } - } - } -} -``` - -**使用场景:** -- 通过 MCP 工具直接查询和操作数据库 -- 在开发过程中快速验证数据 -- 生成测试数据和调试 SQL - -### Druid 监控台 - -访问地址: `http://localhost:8080/druid/` -- 用户名: `ruoyi` -- 密码: `123456` - -用于监控 SQL 执行情况、连接池状态等。 - ---- - -## 重要文件路径 - -| 用途 | 路径 | -|---------------|--------------------------------------------------------------------------------| -| 应用入口 | `ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java` | -| 安全配置 | `ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java` | -| 信息采集 Controller | `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/controller/` | -| 信息采集 Mapper XML | `ccdi-info-collection/src/main/resources/mapper/info/collection/` | -| 项目管理 Controller | `ccdi-project/src/main/java/com/ruoyi/ccdi/project/controller/` | -| 项目管理 Mapper XML | `ccdi-project/src/main/resources/mapper/ccdi/project/` | -| 流水分析 Client | `ccdi-lsfx/src/main/java/com/ruoyi/lsfx/client/LsfxAnalysisClient.java` | -| Vue 路由 | `ruoyi-ui/src/router/index.js` | -| Vuex Store | `ruoyi-ui/src/store/` | -| 前端 API | `ruoyi-ui/src/api/` | - ---- - -## 数据库规范 - -- **新建表名**: 需要加上项目英文名首字母集合前缀 `ccdi_` (如 `ccdi_base_staff`) - ---- - -## 文档管理 - -- **文档语言**: 使用简体中文编写 .md 文档 -- **文档目录**: 所有生成的文档放在 `doc/` 目录下,按类型分类 -- **需求分析**: 在 `doc/` 目录下新建文件夹,以需求内容命名 - -### doc 目录结构 - -``` -doc/ -├── api-docs/ # API 文档 -├── database/ # 数据库相关 -├── design/ # 设计文档 -├── implementation/ # 实施文档 -├── requirements/ # 需求文档 -└── test-scripts/ # 测试脚本 -``` - ---- - -## OpenSpec 工作流 - -项目使用 OpenSpec 进行规范驱动开发,参考 `openspec/AGENTS.md`。 - -### 何时创建 Proposal - -**需要创建:** - -- 新功能或能力 -- 破坏性变更 (API, 数据库结构) -- 架构变更 -- 改变行为的性能优化 - -**无需创建:** - -- Bug 修复 (恢复预期行为) -- 拼写错误、格式、注释 -- 非破坏性依赖更新 -- 配置变更 - ---- - -## 沟通规范 - -- 永远使用简体中文进行思考和对话 - ---- - -## 常见问题排查 - -### 数据库连接失败 - -**检查项:** -1. 确认 MySQL 服务已启动 -2. 检查 `application-dev.yml` 中的数据库连接配置 -3. 确认数据库用户名和密码正确 -4. 检查数据库是否已创建(数据库名: `ccdi`) - -### Redis 连接失败 - -**检查项:** -1. 确认 Redis 服务已启动 -2. 检查 `application-dev.yml` 中的 Redis 配置 -3. 如果 Redis 不需要密码,将 `password` 配置注释掉 - -### 前端无法访问后端接口 - -**检查项:** -1. 确认后端已启动(端口 8080) -2. 检查前端代理配置(`ruoyi-ui/vue.config.js`) -3. 确认后端接口路径正确(查看 Controller 的 `@RequestMapping`) - -### 导入功能无响应 - -**检查项:** -1. 检查文件大小是否超过限制(默认 10MB) -2. 查看后端日志是否有异常 -3. 确认 Excel 模板格式正确 -4. 检查必填字段是否为空 - -### 流水分析平台连接失败 - -**检查项:** -1. 确认 `lsfx-mock-server` 已启动(开发环境) -2. 检查 `application-dev.yml` 中的 `lsfx.api.base-url` 配置 -3. 验证 app-id、app-secret、client-id 是否正确 -4. 检查网络连接和防火墙设置 -5. 查看后端日志中的 HTTP 请求错误信息 - ---- - -## MyBatis Plus 分页使用 - -```java -// Controller 层 -@GetMapping("/list") -public TableDataInfo list(QueryDTO queryDTO) { - PageDomain pageDomain = TableSupport.buildPageRequest(); - Page page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); - Page result = service.selectPage(page, queryDTO); - return getDataTable(result.getRecords(), result.getTotal()); -} - -// Service 层 -Page selectPage(Page page, QueryDTO queryDTO); - -// Mapper 层 (使用 XML) - -``` diff --git a/build_release_ccdi.sh b/build_release_ccdi.sh deleted file mode 100755 index b5f2d6c1..00000000 --- a/build_release_ccdi.sh +++ /dev/null @@ -1,92 +0,0 @@ -#!/bin/sh - -set -eu - -ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd) -DATE_STAMP=$(date "+%Y%m%d") -RELEASE_ZIP="$ROOT_DIR/ccdi_${DATE_STAMP}.zip" -STAGE_DIR="$ROOT_DIR/.deploy/ccdi-release-package" -WORK_DIR="$STAGE_DIR/files" -BACKEND_JAR_SOURCE="$ROOT_DIR/ruoyi-admin/target/ruoyi-admin.jar" -FRONTEND_DIR="$ROOT_DIR/ruoyi-ui" -FRONTEND_DIST_DIR="$FRONTEND_DIR/dist" -FRONTEND_DIST_ZIP="$WORK_DIR/dist.zip" - -log_info() { - printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" -} - -log_error() { - printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >&2 -} - -require_command() { - if ! command -v "$1" >/dev/null 2>&1; then - log_error "缺少命令: $1" - exit 1 - fi -} - -reset_stage_dir() { - rm -rf "$STAGE_DIR" - mkdir -p "$WORK_DIR" -} - -build_backend() { - log_info "开始构建后端生产 jar" - ( - cd "$ROOT_DIR" - mvn -pl ruoyi-admin -am clean package -DskipTests - ) - - if [ ! -f "$BACKEND_JAR_SOURCE" ]; then - log_error "未生成后端 jar: $BACKEND_JAR_SOURCE" - exit 1 - fi -} - -build_frontend() { - log_info "开始构建前端生产 dist" - FRONTEND_DIR="$FRONTEND_DIR" zsh -lic 'cd "$FRONTEND_DIR" && nvm use >/dev/null && npm run build:prod' - - if [ ! -f "$FRONTEND_DIST_DIR/index.html" ]; then - log_error "前端生产构建失败,未找到: $FRONTEND_DIST_DIR/index.html" - exit 1 - fi - - ( - cd "$FRONTEND_DIR" - zip -qr "$FRONTEND_DIST_ZIP" dist - ) - - if [ ! -f "$FRONTEND_DIST_ZIP" ]; then - log_error "未生成前端压缩包: $FRONTEND_DIST_ZIP" - exit 1 - fi -} - -package_release() { - cp "$BACKEND_JAR_SOURCE" "$WORK_DIR/ruoyi-admin.jar" - - rm -f "$RELEASE_ZIP" - ( - cd "$WORK_DIR" - zip -qr "$RELEASE_ZIP" ruoyi-admin.jar dist.zip - ) - - log_info "上线压缩包已生成: $RELEASE_ZIP" - log_info "压缩包根层内容: ruoyi-admin.jar, dist.zip" -} - -main() { - require_command mvn - require_command zsh - require_command zip - - reset_stage_dir - build_backend - build_frontend - package_release -} - -main "$@" diff --git a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAccountInfoMapper.xml b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAccountInfoMapper.xml index 51d0729a..73bf8a74 100644 --- a/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAccountInfoMapper.xml +++ b/ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAccountInfoMapper.xml @@ -84,6 +84,7 @@ WHERE 1 = 1 + AND ai.owner_type <> 'CREDIT_CUSTOMER' AND ( (ai.owner_type = 'EMPLOYEE' AND bs.name LIKE CONCAT('%', #{query.staffName}, '%')) diff --git a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAccountInfoMapperTest.java b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAccountInfoMapperTest.java index 3863c4b5..fe35c8ec 100644 --- a/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAccountInfoMapperTest.java +++ b/ccdi-info-collection/src/test/java/com/ruoyi/info/collection/mapper/CcdiAccountInfoMapperTest.java @@ -35,6 +35,7 @@ class CcdiAccountInfoMapperTest { assertTrue(sql.contains("ai.is_self_account as isactualcontrol"), sql); assertTrue(sql.contains("ai.monthly_avg_trans_count as avgmonthtxncount"), sql); assertTrue(sql.contains("ai.trans_risk_level as txnrisklevel"), sql); + assertTrue(sql.contains("ai.owner_type <> 'credit_customer'"), sql); } private MappedStatement loadMappedStatement(String statementId) throws Exception { diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/constants/CcdiProjectStatusConstants.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/constants/CcdiProjectStatusConstants.java index 3ba8b952..365aab4f 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/constants/CcdiProjectStatusConstants.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/constants/CcdiProjectStatusConstants.java @@ -9,6 +9,7 @@ public final class CcdiProjectStatusConstants { public static final String COMPLETED = "1"; public static final String ARCHIVED = "2"; public static final String TAGGING = "3"; + public static final String TAG_FAILED = "4"; private CcdiProjectStatusConstants() { } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/CcdiProject.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/CcdiProject.java index d00af936..10ffb22f 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/CcdiProject.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/CcdiProject.java @@ -32,7 +32,7 @@ public class CcdiProject implements Serializable { /** 配置方式:default-全局默认,custom-自定义 */ private String configType; - /** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中 */ + /** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败 */ private String status; /** 是否归档:0-未归档,1-已归档 */ diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java index db805d4f..8c291c5d 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectStatusCountsVO.java @@ -23,4 +23,7 @@ public class CcdiProjectStatusCountsVO { /** 打标中项目数(状态3) */ private Long status3; + + /** 打标失败项目数(状态4) */ + private Long status4; } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectVO.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectVO.java index dda5df2b..424a85e0 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectVO.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/domain/vo/CcdiProjectVO.java @@ -50,6 +50,12 @@ public class CcdiProjectVO { /** 更新时间 */ private Date updateTime; + /** 最近一次打标失败原因 */ + private String latestTagTaskErrorMessage; + + /** 最近一次打标失败结束时间 */ + private Date latestTagTaskEndTime; + /** 创建者(用户名) */ private String createBy; diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java index 8c277f10..6b1f4fdf 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/mapper/CcdiBankTagTaskMapper.java @@ -32,4 +32,12 @@ public interface CcdiBankTagTaskMapper extends BaseMapper { * @return 任务实体 */ CcdiBankTagTask selectRunningTaskByProjectId(@Param("projectId") Long projectId); + + /** + * 查询项目最近一次失败任务 + * + * @param projectId 项目ID + * @return 任务实体 + */ + CcdiBankTagTask selectLatestFailedTaskByProjectId(@Param("projectId") Long projectId); } diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java index 1665f81f..879c5474 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImpl.java @@ -153,7 +153,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService { task.setUpdateBy(operator); task.setUpdateTime(new Date()); updateFailedTaskSafely(task, ex); - projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.PROCESSING, operator); + projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAG_FAILED, operator); log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}", task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex); throw ex; diff --git a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java index 57444f96..4c24c505 100644 --- a/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java +++ b/ccdi-project/src/main/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImpl.java @@ -7,10 +7,12 @@ import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; +import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask; import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; +import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.ccdi.project.service.ICcdiProjectService; import com.ruoyi.common.exception.ServiceException; @@ -43,6 +45,9 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { @Resource private CcdiProjectMapper projectMapper; + @Resource + private CcdiBankTagTaskMapper bankTagTaskMapper; + @Resource private LsfxAnalysisClient lsfxAnalysisClient; @@ -77,6 +82,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { // 5. 返回VO CcdiProjectVO vo = new CcdiProjectVO(); BeanUtils.copyProperties(project, vo); + fillLatestTagFailure(project, vo); return vo; } @@ -116,6 +122,7 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { } CcdiProjectVO vo = new CcdiProjectVO(); BeanUtils.copyProperties(project, vo); + fillLatestTagFailure(project, vo); return vo; } @@ -183,6 +190,12 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { ); vo.setStatus3(status3Count); + Long status4Count = projectMapper.selectCount( + new LambdaQueryWrapper() + .eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED) + ); + vo.setStatus4(status4Count); + return vo; } @@ -263,10 +276,23 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService { case CcdiProjectStatusConstants.COMPLETED -> "已完成"; case CcdiProjectStatusConstants.ARCHIVED -> "已归档"; case CcdiProjectStatusConstants.TAGGING -> "打标中"; + case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败"; default -> "未知"; }; } + private void fillLatestTagFailure(CcdiProject project, CcdiProjectVO vo) { + if (!CcdiProjectStatusConstants.TAG_FAILED.equals(project.getStatus())) { + return; + } + CcdiBankTagTask latestFailedTask = bankTagTaskMapper.selectLatestFailedTaskByProjectId(project.getProjectId()); + if (latestFailedTask == null) { + return; + } + vo.setLatestTagTaskErrorMessage(latestFailedTask.getErrorMessage()); + vo.setLatestTagTaskEndTime(latestFailedTask.getEndTime()); + } + private String resolveOperator(String operator) { return StringUtils.hasText(operator) ? operator : "system"; } diff --git a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml index d46bff28..852d1797 100644 --- a/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml +++ b/ccdi-project/src/main/resources/mapper/ccdi/project/CcdiBankTagTaskMapper.xml @@ -65,4 +65,15 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN" limit 1 + + diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java index 0ecf92f8..bec51b38 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceImplTest.java @@ -317,7 +317,7 @@ class CcdiBankTagServiceImplTest { } @Test - void shouldRollbackProjectStatusToProcessingWhenRebuildFails() { + void shouldMarkProjectTagFailedWhenRebuildFails() { ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run); CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易", @@ -329,7 +329,7 @@ class CcdiBankTagServiceImplTest { assertThrows(RuntimeException.class, () -> service.rebuildProject(40L, null, "tester", TriggerType.MANUAL)); - verify(projectService).updateProjectStatus(40L, "0", "tester"); + verify(projectService).updateProjectStatus(40L, "4", "tester"); } @Test diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java index a22dc898..652d16f8 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiBankTagServiceRiskCountRefreshTest.java @@ -112,7 +112,7 @@ class CcdiBankTagServiceRiskCountRefreshTest { verify(taskMapper).updateTask(argThat(task -> "FAILED".equals(task.getStatus()) && "refresh failed".equals(task.getErrorMessage()))); - verify(projectService).updateProjectStatus(40L, "0", "tester"); + verify(projectService).updateProjectStatus(40L, "4", "tester"); } private CcdiBankTagRule buildRule() { diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java index ec9fa589..f5db9a21 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/service/impl/CcdiProjectServiceImplTest.java @@ -7,10 +7,12 @@ import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; +import com.ruoyi.ccdi.project.domain.entity.CcdiBankTagTask; import com.ruoyi.ccdi.project.domain.event.CcdiProjectHistoryImportSubmittedEvent; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; +import com.ruoyi.ccdi.project.mapper.CcdiBankTagTaskMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.common.exception.ServiceException; import com.ruoyi.lsfx.client.LsfxAnalysisClient; @@ -25,11 +27,14 @@ import org.slf4j.LoggerFactory; import org.springframework.context.ApplicationEventPublisher; import org.springframework.transaction.support.TransactionSynchronizationManager; +import java.util.Date; import java.util.List; import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertDoesNotThrow; import static org.junit.jupiter.api.Assertions.assertFalse; import static org.junit.jupiter.api.Assertions.assertNotNull; +import static org.junit.jupiter.api.Assertions.assertNull; import static org.junit.jupiter.api.Assertions.assertThrows; import static org.junit.jupiter.api.Assertions.assertTrue; import static org.mockito.ArgumentMatchers.any; @@ -47,6 +52,9 @@ class CcdiProjectServiceImplTest { @Mock private CcdiProjectMapper projectMapper; + @Mock + private CcdiBankTagTaskMapper bankTagTaskMapper; + @Mock private LsfxAnalysisClient lsfxAnalysisClient; @@ -55,13 +63,55 @@ class CcdiProjectServiceImplTest { @Test void shouldCountTaggingProjectsSeparately() { - when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L); + when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L, 0L); CcdiProjectStatusCountsVO counts = service.getStatusCounts(); assertEquals(1L, counts.getStatus3()); } + @Test + void shouldCountTagFailedProjectsSeparately() { + when(projectMapper.selectCount(any())).thenReturn(10L, 3L, 4L, 2L, 1L, 5L); + + CcdiProjectStatusCountsVO counts = service.getStatusCounts(); + + assertEquals(5L, counts.getStatus4()); + } + + @Test + void shouldReturnLatestFailedTagTaskOnFailedProjectDetail() { + Date endTime = new Date(); + CcdiProject project = new CcdiProject(); + project.setProjectId(40L); + project.setStatus("4"); + when(projectMapper.selectById(40L)).thenReturn(project); + + CcdiBankTagTask failedTask = new CcdiBankTagTask(); + failedTask.setErrorMessage("threshold missing"); + failedTask.setEndTime(endTime); + when(bankTagTaskMapper.selectLatestFailedTaskByProjectId(40L)).thenReturn(failedTask); + + CcdiProjectVO result = service.getProjectById(40L); + + assertEquals("threshold missing", result.getLatestTagTaskErrorMessage()); + assertEquals(endTime, result.getLatestTagTaskEndTime()); + } + + @Test + void shouldNotReturnLatestFailedTagTaskWhenProjectIsNotFailed() { + CcdiProject project = new CcdiProject(); + project.setProjectId(40L); + project.setStatus("0"); + when(projectMapper.selectById(40L)).thenReturn(project); + + CcdiProjectVO result = service.getProjectById(40L); + + assertNotNull(result); + assertNull(result.getLatestTagTaskErrorMessage()); + verify(bankTagTaskMapper, never()).selectLatestFailedTaskByProjectId(any()); + } + @Test void shouldRejectUpdatingArchivedProjectToTagging() { CcdiProject archived = new CcdiProject(); @@ -84,6 +134,16 @@ class CcdiProjectServiceImplTest { () -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数")); } + @Test + void shouldAllowWritingWhenProjectTagFailed() { + CcdiProject tagFailed = new CcdiProject(); + tagFailed.setProjectId(40L); + tagFailed.setStatus("4"); + when(projectMapper.selectById(40L)).thenReturn(tagFailed); + + assertDoesNotThrow(() -> service.ensureProjectWritable(40L, "当前项目正在进行银行流水打标,暂不允许修改参数")); + } + @Test void shouldArchiveCompletedProject() { CcdiProject project = new CcdiProject(); @@ -110,6 +170,16 @@ class CcdiProjectServiceImplTest { assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester")); } + @Test + void shouldRejectArchivingProjectWhenStatusIsTagFailed() { + CcdiProject project = new CcdiProject(); + project.setProjectId(41L); + project.setStatus("4"); + when(projectMapper.selectById(41L)).thenReturn(project); + + assertThrows(ServiceException.class, () -> service.archiveProject(41L, "tester")); + } + @Test void shouldRejectWritingWhenProjectIsArchived() { CcdiProject archived = new CcdiProject(); diff --git a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiProjectStatusSqlTest.java b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiProjectStatusSqlTest.java index ff580f3c..eed03bf1 100644 --- a/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiProjectStatusSqlTest.java +++ b/ccdi-project/src/test/java/com/ruoyi/ccdi/project/sql/CcdiProjectStatusSqlTest.java @@ -14,12 +14,25 @@ class CcdiProjectStatusSqlTest { void shouldContainTaggingStatusInInitAndMigrationSql() throws IOException { Path repoRoot = Path.of(".."); String initSql = Files.readString(repoRoot.resolve("sql/ccdi_project.sql")); + String prodInitSql = Files.readString(repoRoot.resolve("sql/ccdi_prod_init.sql")); String migrationSql = Files.readString(repoRoot.resolve("sql/migration/2026-03-18-add-project-tagging-status.sql")); + String tagFailedMigrationSql = + Files.readString(repoRoot.resolve("sql/migration/2026-05-27-add-project-tag-failed-status.sql")); assertTrue(initSql.contains("打标中")); assertTrue(initSql.contains("'3'")); assertTrue(migrationSql.contains("ccdi_project_status")); assertTrue(migrationSql.contains("打标中")); assertTrue(migrationSql.contains("'3'")); + + assertTrue(initSql.contains("打标失败")); + assertTrue(initSql.contains("'4'")); + assertTrue(prodInitSql.contains("打标失败")); + assertTrue(prodInitSql.contains("'4','ccdi_project_status'")); + assertTrue(tagFailedMigrationSql.contains("ccdi_project_status")); + assertTrue(tagFailedMigrationSql.contains("打标失败")); + assertTrue(tagFailedMigrationSql.contains("'4'")); + assertTrue(tagFailedMigrationSql.contains("latest_task.status = 'FAILED'")); + assertTrue(tagFailedMigrationSql.contains("project.status IN ('0', '3')")); } } diff --git a/docs/plans/backend/2026-05-27-account-info-exclude-credit-customer-backend-implementation.md b/docs/plans/backend/2026-05-27-account-info-exclude-credit-customer-backend-implementation.md new file mode 100644 index 00000000..1d81a857 --- /dev/null +++ b/docs/plans/backend/2026-05-27-account-info-exclude-credit-customer-backend-implementation.md @@ -0,0 +1,29 @@ +# 账号库列表排除信贷客户后端实施计划 + +## 1. 目标 + +账号库管理列表不展示 `ccdi_account_info.owner_type = 'CREDIT_CUSTOMER'` 的信贷客户账号,避免信贷客户账号批量导入后进入页面列表并影响查询性能。 + +## 2. 实施范围 + +- 后端账号库列表查询 SQL +- 账号库导出查询复用同一筛选条件 +- 本次不调整前端筛选项、接口参数、返回结构、新增编辑导入校验 + +## 3. 实施步骤 + +1. 在 `CcdiAccountInfoMapper.xml` 的 `AccountInfoWhereClause` 增加固定条件: + `AND ai.owner_type <> 'CREDIT_CUSTOMER'` +2. 保持现有 `ownerType` 动态筛选逻辑不变,使 `ownerType=CREDIT_CUSTOMER` 查询自然返回空结果。 +3. 不新增前端“信贷客户”筛选项,不扩展账号库维护端归属类型。 + +## 4. 验证要点 + +- 无筛选条件时列表不返回 `CREDIT_CUSTOMER` 数据。 +- `ownerType=EMPLOYEE`、`RELATION`、`INTERMEDIARY`、`EXTERNAL` 时仍按原逻辑查询。 +- `ownerType=CREDIT_CUSTOMER` 时返回空结果。 +- 账号库导出与列表使用同一排除口径。 + +## 5. 前提 + +信贷客户账号导入 `ccdi_account_info` 时,`owner_type` 必须固定写入 `CREDIT_CUSTOMER`。 diff --git a/docs/plans/backend/2026-05-27-project-tag-failed-status-backend-implementation.md b/docs/plans/backend/2026-05-27-project-tag-failed-status-backend-implementation.md new file mode 100644 index 00000000..a79d726d --- /dev/null +++ b/docs/plans/backend/2026-05-27-project-tag-failed-status-backend-implementation.md @@ -0,0 +1,24 @@ +# 项目打标失败状态后端实施计划 + +## 保存路径确认 + +- 后端计划:`docs/plans/backend/2026-05-27-project-tag-failed-status-backend-implementation.md` +- 实施记录:`docs/reports/implementation/2026-05-27-project-tag-failed-status-implementation.md` + +## 目标 + +新增正式项目状态 `4-打标失败`,打标任务失败后项目状态停留在失败态;项目详情接口在失败态下返回最近失败任务错误信息,列表接口不返回完整错误。 + +## 实施步骤 + +1. 扩展项目状态常量、实体注释、状态文案和状态统计 VO,新增 `TAG_FAILED = "4"` 与 `status4`。 +2. 修改打标失败流转:`CcdiBankTagServiceImpl.rebuildProject` 捕获异常后保留任务失败信息,并将项目状态更新为 `4`。 +3. 新增 `CcdiBankTagTaskMapper.selectLatestFailedTaskByProjectId`,按 `id desc limit 1` 查询项目最近失败任务。 +4. 扩展 `CcdiProjectVO`,只新增 `latestTagTaskErrorMessage`、`latestTagTaskEndTime`;`getProjectById` 仅在状态为 `4` 时组装失败任务信息。 +5. 补充 SQL 初始化与迁移脚本,新增 `ccdi_project_status` 字典值 `4-打标失败`,并回填未归档且最新打标任务失败的 `0/3` 项目。 +6. 补充后端单测覆盖失败状态流转、详情失败信息、`status4` 统计、`4` 状态可写和 SQL/Mapper 契约。 + +## 验证 + +- 执行本次相关后端测试类。 +- 执行 `mvn -pl ccdi-project -am test` 观察全量状态并记录非本次问题。 diff --git a/docs/plans/frontend/2026-05-27-project-tag-failed-status-frontend-implementation.md b/docs/plans/frontend/2026-05-27-project-tag-failed-status-frontend-implementation.md new file mode 100644 index 00000000..7504a7bf --- /dev/null +++ b/docs/plans/frontend/2026-05-27-project-tag-failed-status-frontend-implementation.md @@ -0,0 +1,28 @@ +# 项目打标失败状态前端实施计划 + +## 保存路径确认 + +- 前端计划:`docs/plans/frontend/2026-05-27-project-tag-failed-status-frontend-implementation.md` +- 实施记录:`docs/reports/implementation/2026-05-27-project-tag-failed-status-implementation.md` + +## 目标 + +前端支持 `4-打标失败` 状态展示;项目列表只展示失败状态和进入项目入口;项目详情页展示失败提示,并通过详情接口字段查看完整错误。 + +## 实施步骤 + +1. 在项目列表、项目详情、历史导入状态映射中增加 `4-打标失败`,使用失败红色样式。 +2. 在 `SearchBar` 和项目首页状态统计中增加 `4` 筛选与 `status4` 计数。 +3. 在项目详情页头部下方增加打标失败提示,仅当 `projectInfo.projectStatus === "4"` 且存在 `latestTagTaskErrorMessage` 时展示。 +4. 详情失败提示提供完整错误弹窗,内容只使用详情接口返回的 `latestTagTaskErrorMessage` 与 `latestTagTaskEndTime`。 +5. 项目状态轮询在状态脱离 `3-打标中` 后停止,因此遇到 `4` 自动停止并展示失败信息。 +6. `UploadData.vue` 将 `4` 按 `0-进行中` 处理:允许上传、拉取、征信导入,禁用查看报告入口。 +7. `ParamConfig.vue` 维持只锁定 `3-打标中` 和 `2-已归档`,因此 `4` 状态允许保存参数并触发重新打标。 +8. 补充静态单测覆盖状态映射、详情失败提示、列表不展示完整错误、筛选计数和失败态操作口径。 + +## 验证 + +- 前端命令执行前先通过 `nvm use` 切换到项目 Node 版本。 +- 执行相关静态单测。 +- 执行 `npm run build:prod`。 +- 在真实业务页面路由中验证列表和详情页显示效果,不打开 prototype 页面。 diff --git a/docs/reports/implementation/2026-05-22-production-security-group-network-ip.md b/docs/reports/implementation/2026-05-22-production-security-group-network-ip.md new file mode 100644 index 00000000..aee6a548 --- /dev/null +++ b/docs/reports/implementation/2026-05-22-production-security-group-network-ip.md @@ -0,0 +1,48 @@ +# 2026-05-22 生产安全组网络访问清单 + +## 保存路径确认 + +- 目标目录:`docs/reports/implementation/` +- 文档用途:根据 `ruoyi-admin/src/main/resources/application-pro.yml` 生成生产运行所需网络 IP 与端口清单,供服务器安全组配置使用。 +- 路径检查结果:符合仓库实施记录归档规范。 + +## 配置来源 + +- 后端监听端口:`server.port=62318` +- 生产文件公开访问基址:`credit-parse.api.file-public-base-url=http://64.116.19.153` +- 生产 MySQL:`64.116.19.156:3306` +- 生产 Redis:`64.116.19.155:6379` +- 流水分析平台:`http://64.202.32.176/c4c3` +- 征信解析平台:`http://64.202.32.40:8083` + +说明:本文只记录网络地址和端口,不记录生产账号、密码、密钥等敏感配置。 + +## 生产运行安全组放行清单 + +### 入站规则 + +| 目标服务器 | 来源 | 协议/端口 | 用途 | 配置依据 | +| --- | --- | --- | --- | --- | +| `64.116.19.153/32` | 业务访问源 IP 段或前置代理/SLB | TCP `62318` | 访问后端服务 | `server.port=62318` | +| `64.116.19.153/32` | `64.202.32.40/32` | TCP `80` | 征信解析平台读取已上传 HTML 文件 | `file-public-base-url=http://64.116.19.153`,HTTP 默认端口为 `80` | + +### 出站规则 + +| 源服务器 | 目标 | 协议/端口 | 用途 | 配置依据 | +| --- | --- | --- | --- | --- | +| `64.116.19.153/32` | `64.116.19.156/32` | TCP `3306` | 后端连接生产 MySQL | `spring.datasource.druid.master.url` | +| `64.116.19.153/32` | `64.116.19.155/32` | TCP `6379` | 后端连接生产 Redis | `spring.data.redis.host` / `port` | +| `64.116.19.153/32` | `64.202.32.176/32` | TCP `80` | 后端调用流水分析平台接口 | `lsfx.api.base-url=http://64.202.32.176/c4c3` | +| `64.116.19.153/32` | `64.202.32.40/32` | TCP `8083` | 后端调用征信解析发起与结果查询接口 | `credit-parse.api.url` / `result-url` | + +## 配置校验点 + +1. 生产服务若按 `pro` 配置运行,启动参数需要使用 `--spring.profiles.active=pro`。 +2. `file-public-base-url` 当前未带端口,因此征信解析平台回读文件时访问的是 `64.116.19.153:80`;如果服务器只开放 `62318`,外部平台无法按当前 `pro` 配置读取 `/profile/credit-html/**` 文件。 +3. `/profile/**` 在后端资源映射和安全配置中允许匿名 GET 访问,因此安全组应确保征信解析平台到文件公开入口的网络链路可达。 + +## 本次验证 + +- 已检查 `ruoyi-admin/src/main/resources/application-pro.yml` 中生产端口、MySQL、Redis、流水分析平台、征信解析平台和文件公开访问基址。 +- 已检查 `ccdi-info-collection/src/main/java/com/ruoyi/info/collection/service/support/CreditHtmlStorageService.java`,确认远程文件地址由 `file-public-base-url` 拼接 `/profile/credit-html/**` 生成。 +- 已检查 `ruoyi-framework/src/main/java/com/ruoyi/framework/config/ResourcesConfig.java` 与 `SecurityConfig.java`,确认 `/profile/**` 对应本地上传目录并允许匿名 GET 访问。 diff --git a/docs/reports/implementation/2026-05-27-account-info-exclude-credit-customer-implementation.md b/docs/reports/implementation/2026-05-27-account-info-exclude-credit-customer-implementation.md new file mode 100644 index 00000000..f86f8198 --- /dev/null +++ b/docs/reports/implementation/2026-05-27-account-info-exclude-credit-customer-implementation.md @@ -0,0 +1,26 @@ +# 账号库列表排除信贷客户实施记录 + +## 1. 本次实施内容 + +- 在 `CcdiAccountInfoMapper.xml` 的公共查询条件 `AccountInfoWhereClause` 中增加 `AND ai.owner_type <> 'CREDIT_CUSTOMER'`。 +- 账号库列表分页与导出查询共用该条件,因此两处均不再返回信贷客户账号。 +- 在 `CcdiAccountInfoMapperTest` 中补充 SQL 渲染断言,覆盖信贷客户排除条件。 +- 前端页面、筛选项、接口参数和账号库新增编辑导入校验未调整。 + +## 2. 影响范围 + +- 影响接口:`/ccdi/accountInfo/list` +- 影响查询:`selectAccountInfoPage`、`selectAccountInfoListForExport` +- 不影响账号详情、新增、编辑、删除、导入模板和导入处理。 + +## 3. 验证记录 + +- 已确认 Mapper XML 包含固定排除条件。 +- 已确认 `ownerType=CREDIT_CUSTOMER` 会与固定排除条件组合为空结果。 +- 已执行 `git diff --check -- ccdi-info-collection/src/main/resources/mapper/info/collection/CcdiAccountInfoMapper.xml docs/plans/backend/2026-05-27-account-info-exclude-credit-customer-backend-implementation.md docs/reports/implementation/2026-05-27-account-info-exclude-credit-customer-implementation.md`,无空白问题。 +- 已执行 `mvn -pl ccdi-info-collection -am -DskipTests compile`,结果 `BUILD SUCCESS`。 +- 已执行 `mvn -pl ccdi-info-collection -am test`,结果 `BUILD SUCCESS`,共运行 171 个测试,失败 0、错误 0。 + +## 4. 前提说明 + +信贷客户账号需要以 `owner_type = 'CREDIT_CUSTOMER'` 写入 `ccdi_account_info`,否则本次列表排除条件无法识别。 diff --git a/docs/reports/implementation/2026-05-27-fullstack-prod-package-skill-project-code.md b/docs/reports/implementation/2026-05-27-fullstack-prod-package-skill-project-code.md new file mode 100644 index 00000000..3840b8e9 --- /dev/null +++ b/docs/reports/implementation/2026-05-27-fullstack-prod-package-skill-project-code.md @@ -0,0 +1,39 @@ +# 生产打包技能命名优化实施记录 + +## 基本信息 + +- 实施日期:2026-05-27 +- 实施对象:`/Users/wkc/.codex/skills/fullstack-prod-package` +- 实施内容:优化生产打包技能,使最终发布压缩包文件名包含项目英文代码 + +## 修改内容 + +1. 更新打包脚本 `scripts/package_fullstack_prod.py`: + - 新增 `--project-code` 参数,用作最终 zip 文件名前缀 + - 未传入 `--project-code` 时,默认使用后端项目目录名作为项目英文代码 + - 对项目英文代码进行规范化处理,仅保留英文、数字、点、下划线和连字符 + - 最终压缩包命名从 `YYYYMMDD-HHMMSS.zip` 调整为 `projectcode-YYYYMMDD-HHMMSS.zip` + - 打包完成输出增加 `PROJECT_CODE` + +2. 更新技能说明 `SKILL.md`: + - 调整技能描述,保持触发条件清晰 + - 标准命令增加 `--project-code projectcode` + - 说明默认推断规则和验证要求 + +## 影响范围 + +- 后续使用 `fullstack-prod-package` 生成生产包时,最终 zip 文件名会包含项目英文代码。 +- 生产包内部内容不变,仍仅包含: + - `dist.zip` + - 后端运行 Jar + +## 验证结果 + +- `python3 -m py_compile` 通过 +- `--help` 输出已包含 `--project-code` +- 使用现有 `ruoyi-ui/dist` 与 `ruoyi-admin.jar` 在临时目录执行轻量打包验证成功 +- 验证生成文件名:`ccdi-20260527-152829.zip` +- 验证 zip 内容仍仅包含: + - `dist.zip` + - `ruoyi-admin.jar` +- 临时验证目录已删除 diff --git a/docs/reports/implementation/2026-05-27-fullstack-prod-package.md b/docs/reports/implementation/2026-05-27-fullstack-prod-package.md new file mode 100644 index 00000000..05cec845 --- /dev/null +++ b/docs/reports/implementation/2026-05-27-fullstack-prod-package.md @@ -0,0 +1,50 @@ +# 全栈生产包生成实施记录 + +## 基本信息 + +- 实施日期:2026-05-27 +- 实施内容:生成前端 `dist.zip` 与后端可运行 Jar 的生产发布压缩包 +- 最终产物:`/Users/wkc/Downloads/20260527-150234.zip` + +## 执行内容 + +1. 核对项目构建配置: + - 前端目录:`ruoyi-ui` + - 前端 Node 版本:通过 `.nvmrc` 使用 `v14.21.3` + - 前端生产构建命令:`npm run build:prod` + - 后端构建命令:`mvn clean package -DskipTests` + - 后端运行 Jar:`ruoyi-admin/target/ruoyi-admin.jar` + +2. 执行后端生产构建: + - 在仓库根目录执行 `mvn clean package -DskipTests` + - Maven Reactor 全模块构建成功 + - 生成后端运行包 `ruoyi-admin.jar` + +3. 执行前端生产构建与发布包生成: + - 通过 `nvm use` 切换至 Node `v14.21.3` + - 执行 `npm run build:prod` + - 生成 `ruoyi-ui/dist` + - 将 `dist` 压缩为 `dist.zip` + - 将 `dist.zip` 与 `ruoyi-admin.jar` 合并为最终发布包 + +## 影响范围 + +- 更新构建产物目录: + - `ruoyi-ui/dist` + - 各后端模块 `target` +- 新增本地发布产物: + - `/Users/wkc/Downloads/20260527-150234.zip` + +## 验证结果 + +- Node 版本已确认:`v14.21.3` +- Java 版本已确认:`21.0.9` +- Maven 版本已确认:`3.9.14` +- 后端构建结果:成功 +- 前端构建结果:成功,存在前端资源体积 warning,不影响构建完成 +- `ruoyi-ui/dist` 文件数:377 +- 后端 Jar:`ruoyi-admin.jar`,约 100 MB +- 最终压缩包:`20260527-150234.zip`,约 94 MB +- 最终压缩包内容已通过 `unzip -l` 验证,仅包含: + - `dist.zip` + - `ruoyi-admin.jar` diff --git a/docs/reports/implementation/2026-05-27-project-tag-failed-status-implementation.md b/docs/reports/implementation/2026-05-27-project-tag-failed-status-implementation.md new file mode 100644 index 00000000..b621f96a --- /dev/null +++ b/docs/reports/implementation/2026-05-27-project-tag-failed-status-implementation.md @@ -0,0 +1,26 @@ +# 项目打标失败状态实施记录 + +## 修改内容 + +- 后端新增项目状态 `4-打标失败`,扩展状态统计 `status4` 和状态文案。 +- 打标失败后项目状态由原先回退 `0-进行中` 改为写入 `4-打标失败`,任务表仍记录 `FAILED/error_message`。 +- 项目详情接口在失败态下返回最近失败任务的 `latestTagTaskErrorMessage` 与 `latestTagTaskEndTime`;项目列表不返回完整错误。 +- SQL 初始化和迁移脚本新增 `ccdi_project_status` 字典值 `4-打标失败`,并提供生产数据回填 SQL。 +- 前端列表、筛选、计数和详情页支持 `4-打标失败`;详情页提供失败提示和完整错误弹窗。 +- `UploadData.vue` 与 `ParamConfig.vue` 将 `4` 按进行中口径处理,允许重新上传、拉取、修改参数和重新分析,不开放报告查看/归档入口。 + +## 影响范围 + +- 后端:项目状态模型、打标任务失败流转、项目详情接口、状态统计接口、打标任务 Mapper、SQL 初始化和迁移。 +- 前端:项目首页列表与筛选、项目详情头部提示、上传数据页报告入口权限、状态映射。 +- 数据:迁移脚本会将未归档、当前状态为 `0/3`、最新打标任务为 `FAILED` 的项目更新为 `4`。 + +## 验证结果 + +- 通过:`mvn -pl ccdi-project -am -Dtest=CcdiBankTagServiceImplTest,CcdiBankTagServiceRiskCountRefreshTest,CcdiProjectServiceImplTest,CcdiProjectStatusSqlTest,CcdiBankTagTaskMapperXmlTest -Dsurefire.failIfNoSpecifiedTests=false test` +- 全量后端:`mvn -pl ccdi-project -am test` 未通过,剩余失败为既有问题: + - 多个测试使用 static mock,但当前 `SubclassByteBuddyMockMaker` 不支持 static mocks。 + - `CcdiProjectOverviewControllerContractTest.shouldExposeOverviewReportExportEndpointContract` 期望摘要为“一键导出结果总览报告”,实际为“导出结果总览报告”。 +- 通过:`cd ruoyi-ui && nvm use && node tests/unit/project-tag-failed-status.test.js && node tests/unit/project-detail-tagging-polling.test.js && node tests/unit/project-archive-readonly-guard.test.js && node tests/unit/upload-data-disabled-cards.test.js` +- 通过:`cd ruoyi-ui && nvm use && npm run build:prod`,仅有既有资源体积 warning。 +- 真实页面验证:`browser-use` 工具不可用;已使用 Playwright 打开真实业务页面路由,并通过本地 mock 后端构造失败项目数据,验证列表只显示“打标失败”且不泄露完整错误,详情页可查看完整错误,失败状态下上传/拉取入口可见。 diff --git a/docs/reports/implementation/2026-05-27-pull-bank-info-one-year-date-range-implementation.md b/docs/reports/implementation/2026-05-27-pull-bank-info-one-year-date-range-implementation.md new file mode 100644 index 00000000..eee0f85a --- /dev/null +++ b/docs/reports/implementation/2026-05-27-pull-bank-info-one-year-date-range-implementation.md @@ -0,0 +1,32 @@ +# 拉取本行信息近一年日期范围实施记录 + +## 修改内容 + +- 将“拉取本行信息”弹窗的时间跨度最早可选日期由固定 `2025-01-01` 调整为动态近一年窗口。 +- 日期可选范围按当前日期滚动计算: + - 最晚可选日期:昨天。 + - 最早可选日期:最晚可选日期往前一年。 +- 提交前校验同步使用同一套最早、最晚日期边界。 +- 校验提示调整为“时间跨度仅支持近一年内日期,且最晚只能选择到昨天”。 +- 补充前端单测断言,防止日期范围再次退回固定日期。 + +## 影响范围 + +- 前端页面: + - `ruoyi-ui/src/views/ccdiProject/components/detail/UploadData.vue` +- 前端测试: + - `ruoyi-ui/tests/unit/upload-data-pull-bank-info-date-limit.test.js` +- 不涉及后端接口、不改数据库。 + +## 验证情况 + +- 已执行 `source ~/.nvm/nvm.sh && nvm use && node tests/unit/upload-data-pull-bank-info-date-limit.test.js`,通过。 +- 已执行 `source ~/.nvm/nvm.sh && nvm use && npm run build:prod`,通过;仅存在项目既有资源体积告警。 +- 已在真实项目详情页验证“拉取本行信息”弹窗: + - 验证页面:`http://localhost:8090/ccdiProject/detail/90336`。 + - 当前系统日期:`2026-05-27`。 + - 默认开始日期:`2025-05-26`。 + - 默认结束日期:`2026-05-26`。 + - 日期面板中 `2025-05-25` 禁用,`2025-05-26` 可选。 + - 日期面板中 `2026-05-26` 可选,`2026-05-27` 及之后日期禁用。 +- 浏览器验证截图已保存到 `output/browser-use/2026-05-27-pull-bank-info-date-range.png`,该文件为本地验证产物,不提交 Git。 diff --git a/docs/reports/implementation/2026-05-28-bank-tag-union-collation-fix.md b/docs/reports/implementation/2026-05-28-bank-tag-union-collation-fix.md new file mode 100644 index 00000000..62dedc2b --- /dev/null +++ b/docs/reports/implementation/2026-05-28-bank-tag-union-collation-fix.md @@ -0,0 +1,32 @@ +# 银行流水打标 UNION 排序规则冲突修复记录 + +## 问题现象 + +- 生产执行银行流水打标规则 `ABNORMAL_CUSTOMER_TRANSACTION` 时失败。 +- 异常为 `Illegal mix of collations for operation 'UNION'`。 +- 报错位置为 `CcdiBankTagAnalysisMapper.xml` 中 `selectAbnormalCustomerTransactionStatements` 查询。 + +## 根因 + +- 该规则会将 `ccdi_base_staff` 与 `ccdi_staff_fmy_relation` 组装为同一个人员主体派生表,并继续与银行流水、账户库、中介库、企业库做多分支 `UNION ALL`。 +- 生产表字段排序规则确认无异常后,问题收敛到应用数据库连接会话。 +- 生产 JDBC URL 只设置了 `characterEncoding=UTF-8`,未固定 `connectionCollation`。现场查询显示生产 MySQL 8.0.36 的 `@@character_set_connection` 为 `utf8mb3`,`@@collation_connection` 与 `@@collation_server` 均为 `utf8mb3_general_ci`;SQL 字符串字面量与 `CAST(... AS CHAR)` 也解析为 `utf8mb3_general_ci`,与项目字段统一使用的 `utf8mb4_general_ci` 不一致,容易在 `UNION` 合并字符串列时触发排序规则冲突。 + +## 修改内容 + +- 生产数据源 JDBC URL 增加 `connectionCollation=utf8mb4_general_ci`,固定应用连接会话排序规则。 + +## 影响范围 + +- 数据库结构:无变更。 +- 后端配置:仅影响生产 profile 的 MySQL 连接会话排序规则。 +- 后端代码:未修改 Java 和 MyBatis SQL 业务逻辑。 +- 前端:无影响。 + +## 验证情况 + +- 已确认异常 SQL 对应规则为 `ABNORMAL_CUSTOMER_TRANSACTION`。 +- 已确认生产 JDBC URL 未固定 `connectionCollation`。 +- 现场查询 `@@character_set_connection` 返回 `utf8mb3`,`@@collation_connection` 与 `@@collation_server` 返回 `utf8mb3_general_ci`。 +- 现场查询 `COLLATION('本人')` 与 `COLLATION(CAST(1 AS CHAR))` 返回 `utf8mb3_general_ci`。 +- 未连接生产数据库执行验证;生产发布并重启后需确认应用连接中的 `@@collation_connection` 为 `utf8mb4_general_ci`,再重新触发失败项目的银行流水打标任务验证。 diff --git a/docs/reports/implementation/2026-05-28-fullstack-prod-package-095235.md b/docs/reports/implementation/2026-05-28-fullstack-prod-package-095235.md new file mode 100644 index 00000000..115124a6 --- /dev/null +++ b/docs/reports/implementation/2026-05-28-fullstack-prod-package-095235.md @@ -0,0 +1,40 @@ +# 2026-05-28 全栈生产包实施记录 + +## 保存路径确认 + +- 文档目录:`docs/reports/implementation/` +- 本文档:`docs/reports/implementation/2026-05-28-fullstack-prod-package-095235.md` + +## 实施内容 + +- 按 `fullstack-prod-package` 技能执行全栈生产打包。 +- 前端目录:`ruoyi-ui` +- 后端目录:`ruoyi-admin`,构建命令在仓库根目录执行。 +- 项目英文编码:`ccdi` +- 最终生产包:`/Users/wkc/Downloads/ccdi-20260528-095235.zip` + +## 构建命令 + +```bash +python3 /Users/wkc/.codex/skills/fullstack-prod-package/scripts/package_fullstack_prod.py \ + --frontend-dir /Users/wkc/Desktop/ccdi/ccdi/ruoyi-ui \ + --backend-dir /Users/wkc/Desktop/ccdi/ccdi/ruoyi-admin \ + --backend-build-command 'cd /Users/wkc/Desktop/ccdi/ccdi && mvn clean package -DskipTests' \ + --project-code ccdi +``` + +## 验证结果 + +- 前端 `npm run build:prod` 执行成功,使用 `.nvmrc` 指定的 Node `14.21.3`。 +- 前端 `ruoyi-ui/dist` 已生成,目录大小约 `8.7M`。 +- 后端 `mvn clean package -DskipTests` 执行成功,生成 `ruoyi-admin/target/ruoyi-admin.jar`,大小约 `100M`。 +- 最终压缩包已生成,大小约 `94M`。 +- `unzip -l /Users/wkc/Downloads/ccdi-20260528-095235.zip` 验证通过,根目录仅包含: + - `dist.zip` + - `ruoyi-admin.jar` + +## 注意事项 + +- 前端构建存在 Vue CLI 默认资源体积告警,未导致构建失败。 +- Maven 构建跳过测试,符合生产打包命令参数 `-DskipTests`。 +- 本次打包基于执行时当前工作区内容,执行前工作区已存在未提交变更。 diff --git a/ruoyi-admin/src/main/resources/application-dev.yml b/ruoyi-admin/src/main/resources/application-dev.yml index d8085cd1..feb6ede9 100644 --- a/ruoyi-admin/src/main/resources/application-dev.yml +++ b/ruoyi-admin/src/main/resources/application-dev.yml @@ -34,7 +34,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://116.62.17.81:3307/ccdi?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://116.62.17.81:3307/ccdi?useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_general_ci&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: root password: Kfcx@1234 # 从库数据源 diff --git a/ruoyi-admin/src/main/resources/application-pro.yml b/ruoyi-admin/src/main/resources/application-pro.yml index 143af674..b73a062e 100644 --- a/ruoyi-admin/src/main/resources/application-pro.yml +++ b/ruoyi-admin/src/main/resources/application-pro.yml @@ -35,7 +35,7 @@ spring: druid: # 主库数据源 master: - url: jdbc:mysql://64.116.19.156:3306/ccdi?useUnicode=true&characterEncoding=UTF-8&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 + url: jdbc:mysql://64.116.19.156:3306/ccdi?useUnicode=true&characterEncoding=UTF-8&connectionCollation=utf8mb4_general_ci&zeroDateTimeBehavior=convertToNull&useSSL=true&serverTimezone=GMT%2B8 username: lx_ai password: lx-ai@9520 # 从库数据源 diff --git a/ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue b/ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue index 70504e71..4403fd01 100644 --- a/ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue +++ b/ruoyi-ui/src/views/ccdiProject/components/ImportHistoryDialog.vue @@ -176,7 +176,8 @@ export default { '0': 'primary', '1': 'success', '2': 'info', - '3': 'warning' + '3': 'warning', + '4': 'danger' } return statusMap[status] || 'info' }, @@ -185,7 +186,8 @@ export default { '0': '进行中', '1': '已完成', '2': '已归档', - '3': '打标中' + '3': '打标中', + '4': '打标失败' } return statusMap[status] || '未知' }, diff --git a/ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue b/ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue index 1cc7edce..d6558043 100644 --- a/ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue +++ b/ruoyi-ui/src/views/ccdiProject/components/ProjectTable.vue @@ -98,7 +98,7 @@ > @@ -126,10 +158,13 @@ export default { warningCount: 0, warningThreshold: 60, projectStatus: "0", + latestTagTaskErrorMessage: "", + latestTagTaskEndTime: "", }, evidenceConfirmVisible: false, evidenceDrawerVisible: false, evidencePayload: {}, + tagFailureDialogVisible: false, projectStatusPollingTimer: null, projectStatusPollingInterval: 1000, projectStatusPollingLoading: false, @@ -139,6 +174,13 @@ export default { isProjectArchived() { return String(this.projectInfo.projectStatus) === "2"; }, + isProjectTagFailed() { + return String(this.projectInfo.projectStatus) === "4"; + }, + tagFailureSummary() { + const message = this.projectInfo.latestTagTaskErrorMessage || ""; + return message.length > 120 ? `${message.slice(0, 120)}...` : message; + }, }, watch: { "$route.params.projectId"(newId) { @@ -346,6 +388,7 @@ export default { 1: "success", // 已完成 2: "info", // 已归档 3: "warning", // 打标中 + 4: "danger", // 打标失败 }; return statusMap[status] || "info"; }, @@ -356,9 +399,13 @@ export default { 1: "已完成", 2: "已归档", 3: "打标中", + 4: "打标失败", }; return statusMap[status] || "未知"; }, + openTagFailureDialog() { + this.tagFailureDialogVisible = true; + }, /** 获取配置类型标签文字 */ getConfigTypeLabel(configType) { const configTypeMap = { @@ -606,6 +653,67 @@ export default { } } +.tag-failure-alert { + display: flex; + align-items: center; + justify-content: space-between; + gap: 16px; + margin-bottom: 16px; + padding: 12px 16px; + color: #9f3a38; + background: #fff2f0; + border: 1px solid #ffd6d1; + border-radius: 8px; +} + +.tag-failure-alert__content { + display: flex; + align-items: flex-start; + min-width: 0; + gap: 10px; + + i { + margin-top: 2px; + font-size: 18px; + color: #f56c6c; + } +} + +.tag-failure-alert__title { + font-size: 14px; + font-weight: 600; + line-height: 20px; +} + +.tag-failure-alert__message { + margin-top: 2px; + font-size: 13px; + line-height: 20px; + word-break: break-word; +} + +.tag-failure-dialog__time { + margin-bottom: 12px; + color: #606266; + font-size: 13px; +} + +.tag-failure-dialog__message { + max-height: 420px; + margin: 0; + padding: 12px; + overflow: auto; + white-space: pre-wrap; + word-break: break-word; + color: #303133; + background: #f8f9fb; + border: 1px solid #ebeef5; + border-radius: 6px; + font-family: Menlo, Monaco, Consolas, "Courier New", monospace; + font-size: 12px; + line-height: 1.6; +} + .info-card { margin-bottom: 16px; diff --git a/ruoyi-ui/src/views/ccdiProject/index.vue b/ruoyi-ui/src/views/ccdiProject/index.vue index 31222786..61974096 100644 --- a/ruoyi-ui/src/views/ccdiProject/index.vue +++ b/ruoyi-ui/src/views/ccdiProject/index.vue @@ -103,7 +103,8 @@ export default { '0': 0, '1': 0, '2': 0, - '3': 0 + '3': 0, + '4': 0 }, // 新增/编辑弹窗 addDialogVisible: false, @@ -142,7 +143,8 @@ export default { '0': counts.status0 || 0, '1': counts.status1 || 0, '2': counts.status2 || 0, - '3': counts.status3 || 0 + '3': counts.status3 || 0, + '4': counts.status4 || 0 } this.loading = false diff --git a/ruoyi-ui/tests/unit/upload-data-pull-bank-info-date-limit.test.js b/ruoyi-ui/tests/unit/upload-data-pull-bank-info-date-limit.test.js index adc6d86d..60626914 100644 --- a/ruoyi-ui/tests/unit/upload-data-pull-bank-info-date-limit.test.js +++ b/ruoyi-ui/tests/unit/upload-data-pull-bank-info-date-limit.test.js @@ -23,4 +23,14 @@ assert( "组件脚本中应提供“最晚可选日期”为昨天的统一 helper" ); +assert( + /getPullBankInfoMinSelectableDate\(\)[\s\S]*?getPullBankInfoMaxSelectableDate\(\)[\s\S]*?setFullYear\([^)]*getFullYear\(\) - 1\)/.test(source), + "拉取本行信息最早可选日期应跟随最晚可选日期滚动到近一年内" +); + +assert( + !/return new Date\(2025, 0, 1\)/.test(source), + "拉取本行信息日期范围不应再固定从 2025-01-01 开始" +); + console.log("upload-data-pull-bank-info-date-limit test passed"); diff --git a/sql/ccdi_prod_init.sql b/sql/ccdi_prod_init.sql index cf22ae6a..2eb29b34 100644 --- a/sql/ccdi_prod_init.sql +++ b/sql/ccdi_prod_init.sql @@ -805,7 +805,7 @@ CREATE TABLE `ccdi_project` ( `project_name` varchar(200) COLLATE utf8mb4_general_ci NOT NULL COMMENT '项目名称', `description` varchar(500) COLLATE utf8mb4_general_ci DEFAULT NULL COMMENT '项目描述', `config_type` varchar(20) COLLATE utf8mb4_general_ci NOT NULL DEFAULT 'default' COMMENT '配置方式:default-全局默认,custom-自定义', - `status` char(1) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中', + `status` char(1) COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败', `is_archived` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否归档:0-未归档,1-已归档', `target_count` int(11) NOT NULL DEFAULT '0' COMMENT '目标人数', `high_risk_count` int(11) NOT NULL DEFAULT '0' COMMENT '高风险人数', @@ -1671,7 +1671,7 @@ UNLOCK TABLES; LOCK TABLES `sys_dict_data` WRITE; /*!40000 ALTER TABLE `sys_dict_data` DISABLE KEYS */; -INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1,1,'男','0','sys_user_sex','','','Y','0','admin','2026-02-04 07:12:54','',NULL,'性别男'),(2,2,'女','1','sys_user_sex','','','N','0','admin','2026-02-04 07:12:54','',NULL,'性别女'),(3,3,'未知','2','sys_user_sex','','','N','0','admin','2026-02-04 07:12:54','',NULL,'性别未知'),(4,1,'显示','0','sys_show_hide','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'显示菜单'),(5,2,'隐藏','1','sys_show_hide','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'隐藏菜单'),(6,1,'正常','0','sys_normal_disable','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'正常状态'),(7,2,'停用','1','sys_normal_disable','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'停用状态'),(8,1,'正常','0','sys_job_status','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'正常状态'),(9,2,'暂停','1','sys_job_status','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'停用状态'),(10,1,'默认','DEFAULT','sys_job_group','','','Y','0','admin','2026-02-04 07:12:54','',NULL,'默认分组'),(11,2,'系统','SYSTEM','sys_job_group','','','N','0','admin','2026-02-04 07:12:55','',NULL,'系统分组'),(12,1,'是','Y','sys_yes_no','','primary','Y','0','admin','2026-02-04 07:12:55','',NULL,'系统默认是'),(13,2,'否','N','sys_yes_no','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'系统默认否'),(14,1,'通知','1','sys_notice_type','','warning','Y','0','admin','2026-02-04 07:12:55','',NULL,'通知'),(15,2,'公告','2','sys_notice_type','','success','N','0','admin','2026-02-04 07:12:55','',NULL,'公告'),(16,1,'正常','0','sys_notice_status','','primary','Y','0','admin','2026-02-04 07:12:55','',NULL,'正常状态'),(17,2,'关闭','1','sys_notice_status','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'关闭状态'),(18,99,'其他','0','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'其他操作'),(19,1,'新增','1','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'新增操作'),(20,2,'修改','2','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'修改操作'),(21,3,'删除','3','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'删除操作'),(22,4,'授权','4','sys_oper_type','','primary','N','0','admin','2026-02-04 07:12:55','',NULL,'授权操作'),(23,5,'导出','5','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:55','',NULL,'导出操作'),(24,6,'导入','6','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:55','',NULL,'导入操作'),(25,7,'强退','7','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'强退操作'),(26,8,'生成代码','8','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:56','',NULL,'生成操作'),(27,9,'清空数据','9','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'清空操作'),(28,1,'成功','0','sys_common_status','','primary','N','0','admin','2026-02-04 07:12:56','',NULL,'正常状态'),(29,2,'失败','1','sys_common_status','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'停用状态'),(100,1,'中介','中介','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(101,2,'职业背债人','职业背债人','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(102,3,'房产中介','房产中介','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(103,1,'本人','本人','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(104,2,'配偶','配偶','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(105,3,'子女','子女','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(106,6,'其他','其他','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(107,1,'男','M','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(108,2,'女','F','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(109,3,'其他','O','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(110,1,'身份证','身份证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(111,2,'护照','护照','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(112,3,'港澳通行证','港澳通行证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(113,4,'台胞证','台胞证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(114,5,'军官证','军官证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(115,1,'有限责任公司','有限责任公司','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(116,2,'股份有限公司','股份有限公司','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(117,3,'合伙企业','合伙企业','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(118,4,'个体工商户','个体工商户','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(119,5,'外资企业','外资企业','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(120,1,'国企','国企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(121,2,'民企','民企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(122,3,'外企','外企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(123,4,'合资','合资','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(124,9,'其他','其他','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(125,1,'手动录入','MANUAL','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(126,2,'系统同步','SYSTEM','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(127,3,'批量导入','IMPORT','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(128,4,'接口获取','API','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(134,1,'配偶','配偶','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(135,2,'父亲','父亲','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(136,3,'母亲','母亲','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(137,4,'子女','子女','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(138,5,'兄弟','兄弟','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(139,6,'姐妹','姐妹','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(140,7,'合伙人','合伙人','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(141,8,'同事','同事','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(142,9,'其他','其他','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(143,1,'在职','1','ccdi_employee_status','','primary','N','0','admin','2026-02-05 06:02:23','',NULL,'在职状态'),(144,2,'离职','0','ccdi_employee_status','','danger','N','0','admin','2026-02-05 06:02:23','',NULL,'离职状态'),(145,1,'录用','录用','ccdi_admit_status',NULL,'primary','N','0','admin','2026-02-06 06:04:44','',NULL,'已录用该候选人'),(146,2,'未录用','未录用','ccdi_admit_status',NULL,'danger','N','0','admin','2026-02-06 06:04:44','',NULL,'未录用该候选人'),(147,3,'放弃','放弃','ccdi_admit_status',NULL,'info','N','0','admin','2026-02-06 06:04:44','',NULL,'候选人放弃'),(148,2,'无效','0','ccdi_relation_status',NULL,'danger','N','0','admin','2026-02-09 11:47:53','',NULL,'关系状态:无效'),(149,1,'有效','1','ccdi_relation_status',NULL,'primary','Y','0','admin','2026-02-09 11:47:56','',NULL,'关系状态:有效'),(150,1,'升职','PROMOTION','ccdi_transfer_type','','primary','N','0','admin','2026-02-10 05:33:20','',NULL,'员工升职'),(151,2,'降职','DEMOPTION','ccdi_transfer_type','','danger','N','0','admin','2026-02-10 05:33:20','',NULL,'员工降职'),(152,3,'平调','LATERAL','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'平级调动'),(153,4,'轮岗','ROTATION','ccdi_transfer_type','','warning','N','0','admin','2026-02-10 05:33:20','',NULL,'岗位轮换'),(154,5,'借调','SECONDMENT','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'临时借调到其他部门'),(155,6,'部门调动','DEPARTMENT_CHANGE','ccdi_transfer_type','','success','N','0','admin','2026-02-10 05:33:20','',NULL,'部门之间调动'),(156,7,'职位调整','POSITION_CHANGE','ccdi_transfer_type','','primary','N','0','admin','2026-02-10 05:33:20','',NULL,'职位调整'),(157,8,'返岗','RETURN','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'返回原岗位'),(158,9,'离职','TERMINATION','ccdi_transfer_type','','danger','N','0','admin','2026-02-10 05:33:20','',NULL,'员工离职'),(159,10,'其他','OTHER','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'其他类型调动'),(160,1,'进行中','0','ccdi_project_status','','primary','Y','0','admin','2026-02-26 08:42:58','',NULL,NULL),(161,2,'已完成','1','ccdi_project_status','','success','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(162,3,'已归档','2','ccdi_project_status','','info','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(163,1,'全局默认模型参数配置','default','ccdi_config_type','','primary','Y','0','admin','2026-02-26 08:42:58','',NULL,NULL),(164,2,'自定义项目规则参数配置','custom','ccdi_config_type','','warning','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(165,1,'正常','正常','ccdi_asset_status','','success','Y','0','admin','2026-03-13 07:14:06','',NULL,NULL),(166,2,'冻结','冻结','ccdi_asset_status','','warning','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(167,3,'处置中','处置中','ccdi_asset_status','','primary','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(168,4,'报废','报废','ccdi_asset_status','','info','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(169,4,'打标中','3','ccdi_project_status','','warning','N','0','admin','2026-03-18 07:56:00','',NULL,NULL),(170,1,'是','1','ccdi_yes_no_flag','','primary','N','0','admin','2026-04-17 02:51:09','',NULL,'是'),(171,2,'否','0','ccdi_yes_no_flag','','danger','Y','0','admin','2026-04-17 02:51:09','',NULL,'否'),(172,4,'父母','父母','ccdi_person_sub_type','','default','N','0','codex','2026-04-20 02:11:36','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(173,5,'兄弟姐妹','兄弟姐妹','ccdi_person_sub_type','','default','N','0','codex','2026-04-20 02:11:36','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'); +INSERT INTO `sys_dict_data` (`dict_code`, `dict_sort`, `dict_label`, `dict_value`, `dict_type`, `css_class`, `list_class`, `is_default`, `status`, `create_by`, `create_time`, `update_by`, `update_time`, `remark`) VALUES (1,1,'男','0','sys_user_sex','','','Y','0','admin','2026-02-04 07:12:54','',NULL,'性别男'),(2,2,'女','1','sys_user_sex','','','N','0','admin','2026-02-04 07:12:54','',NULL,'性别女'),(3,3,'未知','2','sys_user_sex','','','N','0','admin','2026-02-04 07:12:54','',NULL,'性别未知'),(4,1,'显示','0','sys_show_hide','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'显示菜单'),(5,2,'隐藏','1','sys_show_hide','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'隐藏菜单'),(6,1,'正常','0','sys_normal_disable','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'正常状态'),(7,2,'停用','1','sys_normal_disable','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'停用状态'),(8,1,'正常','0','sys_job_status','','primary','Y','0','admin','2026-02-04 07:12:54','',NULL,'正常状态'),(9,2,'暂停','1','sys_job_status','','danger','N','0','admin','2026-02-04 07:12:54','',NULL,'停用状态'),(10,1,'默认','DEFAULT','sys_job_group','','','Y','0','admin','2026-02-04 07:12:54','',NULL,'默认分组'),(11,2,'系统','SYSTEM','sys_job_group','','','N','0','admin','2026-02-04 07:12:55','',NULL,'系统分组'),(12,1,'是','Y','sys_yes_no','','primary','Y','0','admin','2026-02-04 07:12:55','',NULL,'系统默认是'),(13,2,'否','N','sys_yes_no','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'系统默认否'),(14,1,'通知','1','sys_notice_type','','warning','Y','0','admin','2026-02-04 07:12:55','',NULL,'通知'),(15,2,'公告','2','sys_notice_type','','success','N','0','admin','2026-02-04 07:12:55','',NULL,'公告'),(16,1,'正常','0','sys_notice_status','','primary','Y','0','admin','2026-02-04 07:12:55','',NULL,'正常状态'),(17,2,'关闭','1','sys_notice_status','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'关闭状态'),(18,99,'其他','0','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'其他操作'),(19,1,'新增','1','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'新增操作'),(20,2,'修改','2','sys_oper_type','','info','N','0','admin','2026-02-04 07:12:55','',NULL,'修改操作'),(21,3,'删除','3','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:55','',NULL,'删除操作'),(22,4,'授权','4','sys_oper_type','','primary','N','0','admin','2026-02-04 07:12:55','',NULL,'授权操作'),(23,5,'导出','5','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:55','',NULL,'导出操作'),(24,6,'导入','6','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:55','',NULL,'导入操作'),(25,7,'强退','7','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'强退操作'),(26,8,'生成代码','8','sys_oper_type','','warning','N','0','admin','2026-02-04 07:12:56','',NULL,'生成操作'),(27,9,'清空数据','9','sys_oper_type','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'清空操作'),(28,1,'成功','0','sys_common_status','','primary','N','0','admin','2026-02-04 07:12:56','',NULL,'正常状态'),(29,2,'失败','1','sys_common_status','','danger','N','0','admin','2026-02-04 07:12:56','',NULL,'停用状态'),(100,1,'中介','中介','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(101,2,'职业背债人','职业背债人','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(102,3,'房产中介','房产中介','ccdi_person_type','','default','N','0','admin','2026-02-04 07:46:03','',NULL,NULL),(103,1,'本人','本人','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(104,2,'配偶','配偶','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(105,3,'子女','子女','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(106,6,'其他','其他','ccdi_person_sub_type','','default','N','0','admin','2026-02-04 07:46:04','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(107,1,'男','M','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(108,2,'女','F','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(109,3,'其他','O','ccdi_indiv_gender','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(110,1,'身份证','身份证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(111,2,'护照','护照','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(112,3,'港澳通行证','港澳通行证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(113,4,'台胞证','台胞证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(114,5,'军官证','军官证','ccdi_certificate_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(115,1,'有限责任公司','有限责任公司','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(116,2,'股份有限公司','股份有限公司','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(117,3,'合伙企业','合伙企业','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(118,4,'个体工商户','个体工商户','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(119,5,'外资企业','外资企业','ccdi_entity_type','','default','N','0','admin','2026-02-04 07:46:04','',NULL,NULL),(120,1,'国企','国企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(121,2,'民企','民企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(122,3,'外企','外企','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(123,4,'合资','合资','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(124,9,'其他','其他','ccdi_enterprise_nature','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(125,1,'手动录入','MANUAL','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(126,2,'系统同步','SYSTEM','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(127,3,'批量导入','IMPORT','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(128,4,'接口获取','API','ccdi_data_source','','default','N','0','admin','2026-02-04 07:46:05','',NULL,NULL),(134,1,'配偶','配偶','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(135,2,'父亲','父亲','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(136,3,'母亲','母亲','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(137,4,'子女','子女','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(138,5,'兄弟','兄弟','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(139,6,'姐妹','姐妹','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(140,7,'合伙人','合伙人','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(141,8,'同事','同事','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(142,9,'其他','其他','ccdi_relation_type','','default','N','0','admin','2026-02-05 02:54:55','',NULL,NULL),(143,1,'在职','1','ccdi_employee_status','','primary','N','0','admin','2026-02-05 06:02:23','',NULL,'在职状态'),(144,2,'离职','0','ccdi_employee_status','','danger','N','0','admin','2026-02-05 06:02:23','',NULL,'离职状态'),(145,1,'录用','录用','ccdi_admit_status',NULL,'primary','N','0','admin','2026-02-06 06:04:44','',NULL,'已录用该候选人'),(146,2,'未录用','未录用','ccdi_admit_status',NULL,'danger','N','0','admin','2026-02-06 06:04:44','',NULL,'未录用该候选人'),(147,3,'放弃','放弃','ccdi_admit_status',NULL,'info','N','0','admin','2026-02-06 06:04:44','',NULL,'候选人放弃'),(148,2,'无效','0','ccdi_relation_status',NULL,'danger','N','0','admin','2026-02-09 11:47:53','',NULL,'关系状态:无效'),(149,1,'有效','1','ccdi_relation_status',NULL,'primary','Y','0','admin','2026-02-09 11:47:56','',NULL,'关系状态:有效'),(150,1,'升职','PROMOTION','ccdi_transfer_type','','primary','N','0','admin','2026-02-10 05:33:20','',NULL,'员工升职'),(151,2,'降职','DEMOPTION','ccdi_transfer_type','','danger','N','0','admin','2026-02-10 05:33:20','',NULL,'员工降职'),(152,3,'平调','LATERAL','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'平级调动'),(153,4,'轮岗','ROTATION','ccdi_transfer_type','','warning','N','0','admin','2026-02-10 05:33:20','',NULL,'岗位轮换'),(154,5,'借调','SECONDMENT','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'临时借调到其他部门'),(155,6,'部门调动','DEPARTMENT_CHANGE','ccdi_transfer_type','','success','N','0','admin','2026-02-10 05:33:20','',NULL,'部门之间调动'),(156,7,'职位调整','POSITION_CHANGE','ccdi_transfer_type','','primary','N','0','admin','2026-02-10 05:33:20','',NULL,'职位调整'),(157,8,'返岗','RETURN','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'返回原岗位'),(158,9,'离职','TERMINATION','ccdi_transfer_type','','danger','N','0','admin','2026-02-10 05:33:20','',NULL,'员工离职'),(159,10,'其他','OTHER','ccdi_transfer_type','','info','N','0','admin','2026-02-10 05:33:20','',NULL,'其他类型调动'),(160,1,'进行中','0','ccdi_project_status','','primary','Y','0','admin','2026-02-26 08:42:58','',NULL,NULL),(161,2,'已完成','1','ccdi_project_status','','success','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(162,3,'已归档','2','ccdi_project_status','','info','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(163,1,'全局默认模型参数配置','default','ccdi_config_type','','primary','Y','0','admin','2026-02-26 08:42:58','',NULL,NULL),(164,2,'自定义项目规则参数配置','custom','ccdi_config_type','','warning','N','0','admin','2026-02-26 08:42:58','',NULL,NULL),(165,1,'正常','正常','ccdi_asset_status','','success','Y','0','admin','2026-03-13 07:14:06','',NULL,NULL),(166,2,'冻结','冻结','ccdi_asset_status','','warning','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(167,3,'处置中','处置中','ccdi_asset_status','','primary','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(168,4,'报废','报废','ccdi_asset_status','','info','N','0','admin','2026-03-13 07:14:06','',NULL,NULL),(169,4,'打标中','3','ccdi_project_status','','warning','N','0','admin','2026-03-18 07:56:00','',NULL,NULL),(174,5,'打标失败','4','ccdi_project_status','','danger','N','0','admin','2026-05-27 00:00:00','',NULL,NULL),(170,1,'是','1','ccdi_yes_no_flag','','primary','N','0','admin','2026-04-17 02:51:09','',NULL,'是'),(171,2,'否','0','ccdi_yes_no_flag','','danger','Y','0','admin','2026-04-17 02:51:09','',NULL,'否'),(172,4,'父母','父母','ccdi_person_sub_type','','default','N','0','codex','2026-04-20 02:11:36','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'),(173,5,'兄弟姐妹','兄弟姐妹','ccdi_person_sub_type','','default','N','0','codex','2026-04-20 02:11:36','admin','2026-04-20 07:15:16','中介黑名单-人员子类型'); /*!40000 ALTER TABLE `sys_dict_data` ENABLE KEYS */; UNLOCK TABLES; diff --git a/sql/ccdi_project.sql b/sql/ccdi_project.sql index 88a6a365..b8f1e6c2 100644 --- a/sql/ccdi_project.sql +++ b/sql/ccdi_project.sql @@ -11,7 +11,7 @@ CREATE TABLE `ccdi_project` ( `project_name` VARCHAR(200) NOT NULL COMMENT '项目名称', `description` VARCHAR(500) DEFAULT NULL COMMENT '项目描述', `config_type` VARCHAR(20) NOT NULL DEFAULT 'default' COMMENT '配置方式:default-全局默认,custom-自定义', - `status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中', + `status` CHAR(1) NOT NULL DEFAULT '0' COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败', `is_archived` TINYINT(1) NOT NULL DEFAULT 0 COMMENT '是否归档:0-未归档,1-已归档', `target_count` INT NOT NULL DEFAULT 0 COMMENT '目标人数', `high_risk_count` INT NOT NULL DEFAULT 0 COMMENT '高风险人数', @@ -42,7 +42,8 @@ VALUES (1, '进行中', '0', 'ccdi_project_status', '', 'primary', 'Y', '0', 'admin', NOW()), (2, '已完成', '1', 'ccdi_project_status', '', 'success', 'N', '0', 'admin', NOW()), (3, '已归档', '2', 'ccdi_project_status', '', 'info', 'N', '0', 'admin', NOW()), -(4, '打标中', '3', 'ccdi_project_status', '', 'warning', 'N', '0', 'admin', NOW()); +(4, '打标中', '3', 'ccdi_project_status', '', 'warning', 'N', '0', 'admin', NOW()), +(5, '打标失败', '4', 'ccdi_project_status', '', 'danger', 'N', '0', 'admin', NOW()); -- ---------------------------- -- 4. 插入配置方式字典 diff --git a/sql/migration/2026-05-27-add-project-tag-failed-status.sql b/sql/migration/2026-05-27-add-project-tag-failed-status.sql new file mode 100644 index 00000000..36728938 --- /dev/null +++ b/sql/migration/2026-05-27-add-project-tag-failed-status.sql @@ -0,0 +1,53 @@ +ALTER TABLE ccdi_project + MODIFY COLUMN status CHAR(1) CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci NOT NULL DEFAULT '0' + COMMENT '项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败'; + +INSERT INTO sys_dict_data ( + dict_sort, + dict_label, + dict_value, + dict_type, + css_class, + list_class, + is_default, + status, + create_by, + create_time +) +SELECT + 5, + '打标失败', + '4', + 'ccdi_project_status', + '', + 'danger', + 'N', + '0', + 'admin', + NOW() +WHERE NOT EXISTS ( + SELECT 1 + FROM sys_dict_data + WHERE dict_type = 'ccdi_project_status' + AND dict_value = '4' +); + +UPDATE ccdi_project project +JOIN ( + SELECT latest.project_id, latest.status + FROM ccdi_bank_tag_task latest + JOIN ( + SELECT project_id, MAX(id) AS latest_id + FROM ccdi_bank_tag_task + GROUP BY project_id + ) latest_id + ON latest.id = latest_id.latest_id +) latest_task + ON latest_task.project_id = project.project_id +SET project.status = '4', + project.update_by = 'system', + project.update_time = NOW() +WHERE latest_task.status = 'FAILED' + AND project.status IN ('0', '3') + AND (project.is_archived IS NULL OR project.is_archived = 0) + AND project.del_flag = '0';