Compare commits

..

34 Commits

Author SHA1 Message Date
wkc
0bf73a923f 生产配置 2026-05-09 10:28:00 +08:00
wkc
ec67794f88 新增生产统一部署脚本 2026-05-08 13:32:07 +08:00
wkc
3ef45bc398 Fix PDF font loading for project overview reports 2026-05-08 10:51:42 +08:00
wkc
37e17ac903 新增专项排查图谱展示 2026-05-08 10:22:00 +08:00
wkc
d561d068d6 新增专项排查图谱前端实施计划 2026-05-07 18:53:00 +08:00
wkc
43bc0e4f65 新增专项排查图谱嵌入设计 2026-05-07 18:41:55 +08:00
wkc
3fe78d8d3a 展示员工亲属实体关联统信码 2026-05-07 09:20:06 +08:00
wkc
4c58966529 调整招聘信息毕业年月选择控件 2026-05-07 01:07:52 +08:00
wkc
3bc60fedeb 完善招聘信息主键关联与工作经历维护 2026-05-07 01:04:23 +08:00
wkc
4d1acc7484 招聘导入模板增加招聘类型下拉框 2026-05-07 00:13:00 +08:00
wkc
402a0c3e2f 修复导入模板格式和必填标记 2026-05-07 00:01:27 +08:00
wkc
5980ed0790 Update import templates and relation query fields 2026-05-06 23:37:32 +08:00
wkc
75cb8967da 回测四类导入自动补入实体库 2026-05-06 23:31:43 +08:00
wkc
90a5c42313 合并实体库自动补入与双Sheet导入修复 2026-05-06 20:53:29 +08:00
wkc
356bcdd6de 修复双Sheet资产单独导入任务ID 2026-05-06 20:50:09 +08:00
wkc
9a60371a8f uat配置更新 2026-05-06 20:33:40 +08:00
wkc
380f9b4e7a 移除.DS_Store跟踪 2026-05-06 20:33:01 +08:00
wkc
928f65dfca 修订: 中介实体补入无需机构名称 2026-05-06 18:30:03 +08:00
wkc
c64146ac40 调整信息维护页面并修复项目概览统计 2026-05-06 18:22:26 +08:00
wkc
0541ce0ac6 计划: 员工资产导入与实体库自动补入修复 2026-05-06 18:02:19 +08:00
wkc
26c639134e 修订: 明确资产Sheet单独导入规则 2026-05-06 17:29:32 +08:00
wkc
0f7b57e824 修订: 完善员工资产导入与实体库补入设计 2026-05-06 17:24:25 +08:00
wkc
104e8697fe 设计: 员工资产导入与实体库自动补入修复 2026-05-06 17:18:21 +08:00
wkc
bbc6a2050b 统一项目分析弹窗圆角样式 2026-05-06 17:03:55 +08:00
wkc
bf7a4c0538 Merge remote-tracking branch 'origin/dev-ui' into dev 2026-05-06 16:15:20 +08:00
wkc
b2e177dd24 修复流水上传原始文件名保持 2026-05-06 15:05:36 +08:00
wkc
2071d04c08 修复流水分析上传文件名传递 2026-05-06 14:49:47 +08:00
wkc
4988ab5944 设计: 保持上传流水原始文件名 2026-05-06 14:22:05 +08:00
wkc
c00d5475e6 Add import dropdown validation 2026-05-06 14:04:21 +08:00
wkc
0b64532959 新增导入下拉框校验实施计划 2026-04-30 16:58:34 +08:00
wkc
9f0ad4ce87 完善导入下拉框校验设计 2026-04-30 16:39:58 +08:00
wkc
75b5989774 新增导入下拉框校验设计文档 2026-04-30 16:36:52 +08:00
wkc
d8c069a836 uat配置文件 2026-04-30 09:36:34 +08:00
wkc
26be75adad 实现关联业务自动补入实体库 2026-04-26 17:23:47 +08:00
281 changed files with 1998 additions and 20894 deletions

4
.gitignore vendored
View File

@@ -97,7 +97,3 @@ tongweb_62318.properties
.superpowers/ .superpowers/
tmp/ tmp/
.codegraph/
.claude/

View File

@@ -67,7 +67,6 @@
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本 - 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
- 测试结束后,自动关闭测试过程中启动的前后端进程 - 测试结束后,自动关闭测试过程中启动的前后端进程
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh` - 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
- 禁止在前端源码、配置、示例数据或页面默认值中硬编码或预填真实账号密码;登录页不得将密码保存到 Cookie、localStorage 或 sessionStorage
--- ---
@@ -258,7 +257,6 @@ return AjaxResult.success(result);
- 请求统一使用 `@/utils/request` - 请求统一使用 `@/utils/request`
- 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识 - 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识
- 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录 - 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录
- 登录页只能在用户主动选择时保存用户名,不允许保存密码或预填默认密码
### 导入功能规范 ### 导入功能规范

669
CLAUDE.md Normal file
View File

@@ -0,0 +1,669 @@
# 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``<modules>` 中添加新模块
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<CcdiBaseStaff> page = new Page<>(pageNum, pageSize);
IPage<CcdiBaseStaff> 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<VO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<VO> result = service.selectPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
// Service 层
Page<VO> selectPage(Page<VO> page, QueryDTO queryDTO);
// Mapper 层 (使用 XML)
<select id="selectPage" resultType="VO">
SELECT * FROM table_name
<where>
<if test="queryDTO.name != null">
AND name LIKE CONCAT('%', #{queryDTO.name}, '%')
</if>
</where>
</select>
```

92
build_release_ccdi.sh Executable file
View File

@@ -0,0 +1,92 @@
#!/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 "$@"

View File

@@ -14,10 +14,8 @@ import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper; import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper; import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
import com.ruoyi.info.collection.service.ICcdiCreditInfoService; import com.ruoyi.info.collection.service.ICcdiCreditInfoService;
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler; import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.domain.response.CreditParsePayload; import com.ruoyi.lsfx.domain.response.CreditParsePayload;
import com.ruoyi.lsfx.domain.response.CreditParseResponse; import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -25,6 +23,8 @@ import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile; import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
@@ -36,16 +36,9 @@ import java.util.Map;
@Service @Service
public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService { public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
private static final int CREDIT_PARSE_SUCCESS_CODE = 10000;
private static final int CREDIT_PARSE_SUCCESS_STATUS = 1;
private static final int CREDIT_PARSE_SUCCESS_REASON_CODE = 200;
@Resource @Resource
private CreditParseClient creditParseClient; private CreditParseClient creditParseClient;
@Resource
private CreditHtmlStorageService creditHtmlStorageService;
@Resource @Resource
private CreditInfoPayloadAssembler assembler; private CreditInfoPayloadAssembler assembler;
@@ -148,9 +141,10 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
} }
} }
private void handleSingleFile(MultipartFile multipartFile, String userName) throws Exception { private void handleSingleFile(MultipartFile multipartFile, String userName) throws IOException {
CreditHtmlStorageService.StoredCreditHtml storedHtml = creditHtmlStorageService.save(multipartFile); File tempFile = createTempFile(multipartFile);
CreditParseInvokeResponse response = creditParseClient.parse(storedHtml.remotePath()); try {
CreditParseResponse response = creditParseClient.parse("LXCUSTALL", "PERSON", tempFile);
CreditParsePayload payload = requireResponse(response).getPayload(); CreditParsePayload payload = requireResponse(response).getPayload();
Map<String, Object> header = requireHeader(payload); Map<String, Object> header = requireHeader(payload);
String personId = stringValue(header.get("query_cert_no")); String personId = stringValue(header.get("query_cert_no"));
@@ -162,6 +156,22 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
List<CcdiDebtsInfo> debts = assembler.buildDebts(personId, personName, queryDate, payload); List<CcdiDebtsInfo> debts = assembler.buildDebts(personId, personName, queryDate, payload);
CcdiCreditNegativeInfo negative = assembler.buildNegative(personId, personName, queryDate, payload); CcdiCreditNegativeInfo negative = assembler.buildNegative(personId, personName, queryDate, payload);
replaceEmployeeCredit(personId, debts, negative, userName); replaceEmployeeCredit(personId, debts, negative, userName);
} finally {
if (tempFile.exists()) {
tempFile.delete();
}
}
}
private File createTempFile(MultipartFile multipartFile) throws IOException {
String originalFilename = multipartFile.getOriginalFilename();
String suffix = ".html";
if (originalFilename != null && originalFilename.contains(".")) {
suffix = originalFilename.substring(originalFilename.lastIndexOf('.'));
}
File tempFile = File.createTempFile("credit-info-", suffix);
multipartFile.transferTo(tempFile);
return tempFile;
} }
private void validateHtmlFile(MultipartFile file) { private void validateHtmlFile(MultipartFile file) {
@@ -175,41 +185,14 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
} }
} }
private CreditParseResponse requireResponse(CreditParseInvokeResponse response) { private CreditParseResponse requireResponse(CreditParseResponse response) {
if (response == null || response.getData() == null || response.getData().getMappingOutputFields() == null) { if (response == null || response.getPayload() == null) {
throw new RuntimeException("征信解析结果为空"); throw new RuntimeException("征信解析结果为空");
} }
CreditParseResponse mappingOutputFields = response.getData().getMappingOutputFields(); if (!"0".equals(response.getStatusCode())) {
if (!Boolean.TRUE.equals(response.getSuccess())) { throw new RuntimeException(stringValue(response.getMessage(), "征信解析失败"));
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败"));
} }
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_CODE).equals(response.getCode())) { return response;
throw new RuntimeException("征信解析平台状态码异常: " + response.getCode());
}
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_STATUS).equals(response.getData().getStatus())) {
throw new RuntimeException(parseErrorMessage(response, "征信解析状态异常: " + response.getData().getStatus()));
}
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_REASON_CODE).equals(response.getData().getReasonCode())) {
throw new RuntimeException(parseErrorMessage(response, "征信解析原因码异常: " + response.getData().getReasonCode()));
}
if (mappingOutputFields.getPayload() == null) {
throw new RuntimeException("征信解析结果为空");
}
return mappingOutputFields;
}
private String parseErrorMessage(CreditParseInvokeResponse response, String defaultValue) {
if (response == null || response.getData() == null) {
return defaultValue;
}
String reasonMessage = stringValue(response.getData().getReasonMessage());
if (!isBlank(reasonMessage)) {
return reasonMessage;
}
if (response.getData().getMappingOutputFields() != null) {
return stringValue(response.getData().getMappingOutputFields().getMessage(), defaultValue);
}
return defaultValue;
} }
private Map<String, Object> requireHeader(CreditParsePayload payload) { private Map<String, Object> requireHeader(CreditParsePayload payload) {

View File

@@ -1,44 +0,0 @@
package com.ruoyi.info.collection.service.support;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.file.FileUploadUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
/**
* 征信 HTML 服务器落盘与远程访问地址生成。
*/
@Component
public class CreditHtmlStorageService {
private static final String CREDIT_HTML_DIR = "credit-html";
private static final String[] HTML_EXTENSIONS = {"html", "htm"};
@Value("${credit-parse.api.file-public-base-url}")
private String filePublicBaseUrl;
public StoredCreditHtml save(MultipartFile file) throws Exception {
String profilePath = FileUploadUtils.upload(getCreditHtmlBaseDir(), file, HTML_EXTENSIONS);
return new StoredCreditHtml(profilePath, buildRemotePath(profilePath));
}
private String getCreditHtmlBaseDir() {
return RuoYiConfig.getProfile() + File.separator + CREDIT_HTML_DIR;
}
private String buildRemotePath(String profilePath) {
if (StringUtils.isBlank(filePublicBaseUrl)) {
throw new IllegalStateException("征信HTML公开访问地址未配置");
}
String normalizedBaseUrl = StringUtils.stripEnd(filePublicBaseUrl.trim(), "/");
String normalizedProfilePath = profilePath.startsWith("/") ? profilePath : "/" + profilePath;
return normalizedBaseUrl + normalizedProfilePath;
}
public record StoredCreditHtml(String profilePath, String remotePath) {
}
}

View File

@@ -19,8 +19,6 @@ import java.util.Objects;
@Component @Component
public class CreditInfoPayloadAssembler { public class CreditInfoPayloadAssembler {
private static final BigDecimal MISSING_SENTINEL = new BigDecimal("-9999");
private static final List<DebtMapping> DEBT_MAPPINGS = List.of( private static final List<DebtMapping> DEBT_MAPPINGS = List.of(
new DebtMapping("uncle_bank_house", "银行", "住房贷款", "银行", "未结清银行住房贷款"), new DebtMapping("uncle_bank_house", "银行", "住房贷款", "银行", "未结清银行住房贷款"),
new DebtMapping("uncle_bank_car", "银行", "汽车贷款", "银行", "未结清银行汽车贷款"), new DebtMapping("uncle_bank_car", "银行", "汽车贷款", "银行", "未结清银行汽车贷款"),
@@ -28,7 +26,7 @@ public class CreditInfoPayloadAssembler {
new DebtMapping("uncle_bank_consume", "银行", "消费贷款", "银行", "未结清银行消费贷款"), new DebtMapping("uncle_bank_consume", "银行", "消费贷款", "银行", "未结清银行消费贷款"),
new DebtMapping("uncle_bank_other", "银行", "其他贷款", "银行", "未结清银行其他贷款"), new DebtMapping("uncle_bank_other", "银行", "其他贷款", "银行", "未结清银行其他贷款"),
new DebtMapping("uncle_not_bank", "非银", "非银行贷款", "非银", "未结清非银行贷款"), new DebtMapping("uncle_not_bank", "非银", "非银行贷款", "非银", "未结清非银行贷款"),
new DebtMapping("uncle_credit_card", "银行", "信用卡", "银行", "未结清信用卡") new DebtMapping("uncle_credit_cart", "银行", "信用卡", "银行", "未结清信用卡")
); );
public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) { public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) {
@@ -63,13 +61,9 @@ public class CreditInfoPayloadAssembler {
private CcdiDebtsInfo buildDebtRow(String personId, String personName, LocalDate queryDate, private CcdiDebtsInfo buildDebtRow(String personId, String personName, LocalDate queryDate,
Map<String, Object> source, DebtMapping mapping) { Map<String, Object> source, DebtMapping mapping) {
Object stateValue = source.get(mapping.prefix() + "_state");
if (isMissingSentinel(stateValue)) {
return null;
}
BigDecimal principalBalance = toBigDecimal(source.get(mapping.prefix() + "_bal")); BigDecimal principalBalance = toBigDecimal(source.get(mapping.prefix() + "_bal"));
BigDecimal debtTotalAmount = toBigDecimal(source.get(mapping.prefix() + "_lmt")); BigDecimal debtTotalAmount = toBigDecimal(source.get(mapping.prefix() + "_lmt"));
String debtStatus = toStringValue(stateValue); String debtStatus = toStringValue(source.get(mapping.prefix() + "_state"));
if (isEmptyMetrics(principalBalance, debtTotalAmount, debtStatus)) { if (isEmptyMetrics(principalBalance, debtTotalAmount, debtStatus)) {
return null; return null;
} }
@@ -103,9 +97,6 @@ public class CreditInfoPayloadAssembler {
if (value == null) { if (value == null) {
return null; return null;
} }
if (isMissingSentinel(value)) {
return null;
}
if (value instanceof BigDecimal decimal) { if (value instanceof BigDecimal decimal) {
return decimal; return decimal;
} }
@@ -120,28 +111,10 @@ public class CreditInfoPayloadAssembler {
if (value == null) { if (value == null) {
return null; return null;
} }
if (isMissingSentinel(value)) {
return null;
}
String text = Objects.toString(value, "").trim(); String text = Objects.toString(value, "").trim();
return text.isEmpty() ? null : text; return text.isEmpty() ? null : text;
} }
private boolean isMissingSentinel(Object value) {
if (value == null) {
return false;
}
String text = Objects.toString(value, "").trim();
if (text.isEmpty()) {
return false;
}
try {
return new BigDecimal(text).compareTo(MISSING_SENTINEL) == 0;
} catch (NumberFormatException e) {
return false;
}
}
private boolean isBlank(String value) { private boolean isBlank(String value) {
return value == null || value.trim().isEmpty(); return value == null || value.trim().isEmpty();
} }

View File

@@ -84,7 +84,6 @@
<sql id="AccountInfoWhereClause"> <sql id="AccountInfoWhereClause">
WHERE 1 = 1 WHERE 1 = 1
AND ai.owner_type &lt;&gt; 'CREDIT_CUSTOMER'
<if test="query.staffName != null and query.staffName != ''"> <if test="query.staffName != null and query.staffName != ''">
AND ( AND (
(ai.owner_type = 'EMPLOYEE' AND bs.name LIKE CONCAT('%', #{query.staffName}, '%')) (ai.owner_type = 'EMPLOYEE' AND bs.name LIKE CONCAT('%', #{query.staffName}, '%'))

View File

@@ -35,7 +35,6 @@ class CcdiAccountInfoMapperTest {
assertTrue(sql.contains("ai.is_self_account as isactualcontrol"), sql); 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.monthly_avg_trans_count as avgmonthtxncount"), sql);
assertTrue(sql.contains("ai.trans_risk_level as txnrisklevel"), 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 { private MappedStatement loadMappedStatement(String statementId) throws Exception {

View File

@@ -1,6 +1,5 @@
package com.ruoyi.info.collection.service; package com.ruoyi.info.collection.service;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo; import com.ruoyi.info.collection.domain.CcdiCreditNegativeInfo;
import com.ruoyi.info.collection.domain.CcdiDebtsInfo; import com.ruoyi.info.collection.domain.CcdiDebtsInfo;
import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO; import com.ruoyi.info.collection.domain.vo.CreditInfoUploadResultVO;
@@ -8,33 +7,26 @@ import com.ruoyi.info.collection.mapper.CcdiCreditInfoQueryMapper;
import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper; import com.ruoyi.info.collection.mapper.CcdiCreditNegativeInfoMapper;
import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper; import com.ruoyi.info.collection.mapper.CcdiDebtsInfoMapper;
import com.ruoyi.info.collection.service.impl.CcdiCreditInfoServiceImpl; import com.ruoyi.info.collection.service.impl.CcdiCreditInfoServiceImpl;
import com.ruoyi.info.collection.service.support.CreditHtmlStorageService;
import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler; import com.ruoyi.info.collection.service.support.CreditInfoPayloadAssembler;
import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeData;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.domain.response.CreditParsePayload; import com.ruoyi.lsfx.domain.response.CreditParsePayload;
import com.ruoyi.lsfx.domain.response.CreditParseResponse; import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.junit.jupiter.api.io.TempDir;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.mock.web.MockMultipartFile; import org.springframework.mock.web.MockMultipartFile;
import org.springframework.test.util.ReflectionTestUtils;
import java.io.File;
import java.math.BigDecimal; import java.math.BigDecimal;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.time.LocalDate; import java.time.LocalDate;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.any;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
@@ -49,9 +41,6 @@ class CcdiCreditInfoServiceImplTest {
@Mock @Mock
private CreditParseClient creditParseClient; private CreditParseClient creditParseClient;
@Mock
private CreditHtmlStorageService creditHtmlStorageService;
@Mock @Mock
private CreditInfoPayloadAssembler assembler; private CreditInfoPayloadAssembler assembler;
@@ -65,15 +54,11 @@ class CcdiCreditInfoServiceImplTest {
private CcdiCreditInfoQueryMapper queryMapper; private CcdiCreditInfoQueryMapper queryMapper;
@Test @Test
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() throws Exception { void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() {
MockMultipartFile file = new MockMultipartFile( MockMultipartFile file = new MockMultipartFile(
"files", "family.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8)); "files", "family.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
when(creditHtmlStorageService.save(any())) when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
"/profile/credit-html/2026/05/12/family_1.html",
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html"));
when(creditParseClient.parse(anyString()))
.thenReturn(successResponse("330101199202020022", "李四", "2026-03-24")); .thenReturn(successResponse("330101199202020022", "李四", "2026-03-24"));
when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class))) when(assembler.buildDebts(anyString(), anyString(), any(LocalDate.class), any(CreditParsePayload.class)))
.thenReturn(List.of(buildDebt("330101199202020022"))); .thenReturn(List.of(buildDebt("330101199202020022")));
@@ -84,20 +69,15 @@ class CcdiCreditInfoServiceImplTest {
assertEquals(1, result.getSuccessCount()); assertEquals(1, result.getSuccessCount());
assertEquals(0, result.getFailureCount()); assertEquals(0, result.getFailureCount());
verify(creditParseClient).parse("http://127.0.0.1:62318/profile/credit-html/2026/05/12/family_1.html");
verify(debtsInfoMapper).deleteByPersonId("330101199202020022"); verify(debtsInfoMapper).deleteByPersonId("330101199202020022");
verify(negativeInfoMapper).deleteByPersonId("330101199202020022"); verify(negativeInfoMapper).deleteByPersonId("330101199202020022");
} }
@Test @Test
void uploadHtmlFiles_shouldRejectOlderReportDate() throws Exception { void uploadHtmlFiles_shouldRejectOlderReportDate() {
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8)); MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
when(creditHtmlStorageService.save(any())) when(creditParseClient.parse(anyString(), anyString(), any(File.class)))
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
"/profile/credit-html/2026/05/12/a_1.html",
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
when(creditParseClient.parse(anyString()))
.thenReturn(successResponse("330101199001010011", "张三", "2026-03-03")); .thenReturn(successResponse("330101199001010011", "张三", "2026-03-03"));
when(queryMapper.selectLatestQueryDate("330101199001010011")) when(queryMapper.selectLatestQueryDate("330101199001010011"))
.thenReturn(LocalDate.parse("2026-03-05")); .thenReturn(LocalDate.parse("2026-03-05"));
@@ -108,68 +88,7 @@ class CcdiCreditInfoServiceImplTest {
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason()); assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
} }
@Test private CreditParseResponse successResponse(String personId, String personName, String reportTime) {
void uploadHtmlFiles_shouldRejectInvalidPlatformCode() throws Exception {
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
when(creditHtmlStorageService.save(any()))
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
"/profile/credit-html/2026/05/12/a_1.html",
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
response.setCode(99999);
when(creditParseClient.parse(anyString())).thenReturn(response);
CreditInfoUploadResultVO result = service.upload(List.of(file));
assertEquals(0, result.getSuccessCount());
assertEquals("征信解析平台状态码异常: 99999", result.getFailures().get(0).getReason());
}
@Test
void uploadHtmlFiles_shouldRejectInvalidResultStatus() throws Exception {
MockMultipartFile file = new MockMultipartFile("files", "a.html", "text/html", "<html>a</html>".getBytes(StandardCharsets.UTF_8));
when(creditHtmlStorageService.save(any()))
.thenReturn(new CreditHtmlStorageService.StoredCreditHtml(
"/profile/credit-html/2026/05/12/a_1.html",
"http://127.0.0.1:62318/profile/credit-html/2026/05/12/a_1.html"));
CreditParseInvokeResponse response = successResponse("330101199001010011", "张三", "2026-03-03");
response.getData().setStatus(0);
response.getData().setReasonCode(500);
response.getData().setReasonMessage("结果解析失败");
when(creditParseClient.parse(anyString())).thenReturn(response);
CreditInfoUploadResultVO result = service.upload(List.of(file));
assertEquals(0, result.getSuccessCount());
assertEquals("结果解析失败", result.getFailures().get(0).getReason());
}
@Test
void creditHtmlStorage_shouldStoreHtmlUnderProfileAndBuildRemotePath(@TempDir Path profileDir) throws Exception {
String oldProfile = RuoYiConfig.getProfile();
new RuoYiConfig().setProfile(profileDir.toString());
try {
CreditHtmlStorageService storageService = new CreditHtmlStorageService();
ReflectionTestUtils.setField(storageService, "filePublicBaseUrl", "http://127.0.0.1:62318/");
MockMultipartFile file = new MockMultipartFile(
"files", "credit.html", "text/html", "<html>ok</html>".getBytes(StandardCharsets.UTF_8));
CreditHtmlStorageService.StoredCreditHtml storedHtml = storageService.save(file);
assertTrue(storedHtml.profilePath().startsWith("/profile/credit-html/"));
assertTrue(storedHtml.profilePath().endsWith(".html"));
assertEquals("http://127.0.0.1:62318" + storedHtml.profilePath(), storedHtml.remotePath());
Path savedFile = profileDir.resolve(storedHtml.profilePath().substring("/profile/".length()));
assertTrue(Files.exists(savedFile));
} finally {
new RuoYiConfig().setProfile(oldProfile);
}
}
private CreditParseInvokeResponse successResponse(String personId, String personName, String reportTime) {
CreditParsePayload payload = new CreditParsePayload(); CreditParsePayload payload = new CreditParsePayload();
Map<String, Object> header = new HashMap<>(); Map<String, Object> header = new HashMap<>();
header.put("query_cert_no", personId); header.put("query_cert_no", personId);
@@ -180,20 +99,9 @@ class CcdiCreditInfoServiceImplTest {
payload.setLxPublictype(Map.of("civil_cnt", 1)); payload.setLxPublictype(Map.of("civil_cnt", 1));
CreditParseResponse response = new CreditParseResponse(); CreditParseResponse response = new CreditParseResponse();
response.setMessage("成功"); response.setStatusCode("0");
response.setStatusCode("ERR_SHOULD_IGNORE");
response.setPayload(payload); response.setPayload(payload);
return response;
CreditParseInvokeData data = new CreditParseInvokeData();
data.setMappingOutputFields(response);
data.setStatus(1);
data.setReasonCode(200);
CreditParseInvokeResponse invokeResponse = new CreditParseInvokeResponse();
invokeResponse.setSuccess(true);
invokeResponse.setCode(10000);
invokeResponse.setData(data);
return invokeResponse;
} }
private CcdiDebtsInfo buildDebt(String personId) { private CcdiDebtsInfo buildDebt(String personId) {

View File

@@ -12,7 +12,6 @@ import java.util.List;
import java.util.Map; import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNull;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
class CreditInfoPayloadAssemblerTest { class CreditInfoPayloadAssemblerTest {
@@ -29,18 +28,13 @@ class CreditInfoPayloadAssemblerTest {
debt.put("uncle_not_bank_bal", "2000"); debt.put("uncle_not_bank_bal", "2000");
debt.put("uncle_not_bank_lmt", "3000"); debt.put("uncle_not_bank_lmt", "3000");
debt.put("uncle_not_bank_state", "逾期"); debt.put("uncle_not_bank_state", "逾期");
debt.put("uncle_credit_card_bal", "100");
debt.put("uncle_credit_card_lmt", "500");
debt.put("uncle_credit_card_state", "正常");
payload.setLxDebt(debt); payload.setLxDebt(debt);
List<CcdiDebtsInfo> rows = assembler.buildDebts("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload); List<CcdiDebtsInfo> rows = assembler.buildDebts("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload);
assertEquals(3, rows.size()); assertEquals(2, rows.size());
assertEquals("住房贷款", rows.get(0).getDebtSubType()); assertEquals("住房贷款", rows.get(0).getDebtSubType());
assertEquals("非银", rows.get(1).getCreditorType()); assertEquals("非银", rows.get(1).getCreditorType());
assertEquals("信用卡", rows.get(2).getDebtSubType());
assertEquals(new BigDecimal("500"), rows.get(2).getDebtTotalAmount());
} }
@Test @Test
@@ -57,42 +51,6 @@ class CreditInfoPayloadAssemblerTest {
assertEquals(new BigDecimal("9800"), info.getCivilLmt()); assertEquals(new BigDecimal("9800"), info.getCivilLmt());
} }
@Test
void shouldSkipDebtTypeWhenStateIsMissingSentinel() {
CreditParsePayload payload = new CreditParsePayload();
Map<String, Object> debt = new HashMap<>();
debt.put("uncle_bank_house_bal", "50000");
debt.put("uncle_bank_house_lmt", "100000");
debt.put("uncle_bank_house_state", "-9999");
debt.put("uncle_not_bank_bal", "2000");
debt.put("uncle_not_bank_lmt", "3000");
debt.put("uncle_not_bank_state", "正常");
payload.setLxDebt(debt);
List<CcdiDebtsInfo> rows = assembler.buildDebts("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload);
assertEquals(1, rows.size());
assertEquals("非银行贷款", rows.get(0).getDebtSubType());
}
@Test
void shouldTreatNegativeRiskMissingSentinelAsEmptyValue() {
CreditParsePayload payload = new CreditParsePayload();
Map<String, Object> publictype = new HashMap<>();
publictype.put("civil_cnt", "-9999");
publictype.put("civil_lmt", "-9999.0");
publictype.put("enforce_cnt", 1);
publictype.put("enforce_lmt", "1200");
payload.setLxPublictype(publictype);
CcdiCreditNegativeInfo info = assembler.buildNegative("330101199001010011", "张三", LocalDate.parse("2026-03-01"), payload);
assertEquals(0, info.getCivilCnt());
assertNull(info.getCivilLmt());
assertEquals(1, info.getEnforceCnt());
assertEquals(new BigDecimal("1200"), info.getEnforceLmt());
}
@Test @Test
void shouldSkipDebtRowWhenAllMetricsAreEmpty() { void shouldSkipDebtRowWhenAllMetricsAreEmpty() {
CreditParsePayload payload = new CreditParsePayload(); CreditParsePayload payload = new CreditParsePayload();

View File

@@ -1,9 +1,6 @@
package com.ruoyi.lsfx.client; package com.ruoyi.lsfx.client;
import com.fasterxml.jackson.databind.ObjectMapper; import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.common.utils.uuid.IdUtils;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
import com.ruoyi.lsfx.exception.LsfxApiException; import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.lsfx.util.HttpUtil; import com.ruoyi.lsfx.util.HttpUtil;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
@@ -11,6 +8,7 @@ import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value; import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.io.File;
import java.util.HashMap; import java.util.HashMap;
import java.util.Map; import java.util.Map;
@@ -18,191 +16,32 @@ import java.util.Map;
@Component @Component
public class CreditParseClient { public class CreditParseClient {
private static final int PLATFORM_SUCCESS_CODE = 10000;
private static final int INITIATE_SUCCESS_STATUS = 1;
private static final int INITIATE_SUCCESS_REASON_CODE = 200;
private static final int RESULT_QUERY_MAX_ATTEMPTS = 5;
private static final long RESULT_QUERY_INTERVAL_MILLIS = 2000L;
@Resource @Resource
private HttpUtil httpUtil; private HttpUtil httpUtil;
@Resource
private ObjectMapper objectMapper;
@Value("${credit-parse.api.url}") @Value("${credit-parse.api.url}")
private String creditParseUrl; private String creditParseUrl;
@Value("${credit-parse.api.result-url}") public CreditParseResponse parse(String model, String hType, File file) {
private String creditParseResultUrl;
@Value("${credit-parse.api.org-code:999000}")
private String orgCode;
@Value("${credit-parse.api.run-type:1}")
private String runType;
@Value("${credit-parse.api.model:LXCUSTALL}")
private String defaultModel;
public CreditParseInvokeResponse parse(String remotePath) {
return parse(defaultModel, remotePath);
}
public CreditParseInvokeResponse parse(String model, String remotePath) {
long startTime = System.currentTimeMillis(); long startTime = System.currentTimeMillis();
String actualModel = StringUtils.isBlank(model) ? defaultModel : model; log.info("【征信解析】开始调用: fileName={}, model={}, hType={}", file.getName(), model, hType);
String serialNum = buildSerialNum();
try {
Map<String, Object> initiateParams = buildInitiateParams(serialNum, actualModel, remotePath);
CreditParseInvokeResponse initiateResponse = request(creditParseUrl, initiateParams, "发起接口");
requireSuccessfulInitiateResponse(initiateResponse, "征信解析发起接口");
CreditParseInvokeResponse response = queryResult(serialNum); try {
Map<String, Object> params = new HashMap<>();
params.put("model", model);
params.put("hType", hType);
params.put("file", file);
CreditParseResponse response = httpUtil.uploadFile(creditParseUrl, params, null, CreditParseResponse.class);
long elapsed = System.currentTimeMillis() - startTime; long elapsed = System.currentTimeMillis() - startTime;
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms", log.info("【征信解析】调用完成: statusCode={}, cost={}ms",
response != null ? response.getSuccess() : null, response != null ? response.getStatusCode() : null, elapsed);
response != null ? response.getCode() : null,
response != null && response.getData() != null && response.getData().getMappingOutputFields() != null
? response.getData().getMappingOutputFields().getStatusCode() : null,
elapsed);
return response; return response;
} catch (LsfxApiException e) {
log.error("【征信解析】调用失败: serialNum={}, model={}, remotePath={}, error={}",
serialNum, actualModel, remotePath, e.getMessage(), e);
throw e;
} catch (Exception e) { } catch (Exception e) {
log.error("【征信解析】调用失败: serialNum={}, model={}, remotePath={}, error={}", log.error("【征信解析】调用失败: fileName={}, model={}, hType={}, error={}",
serialNum, actualModel, remotePath, e.getMessage(), e); file.getName(), model, hType, e.getMessage(), e);
throw new LsfxApiException("征信解析调用失败: " + e.getMessage(), e); throw new LsfxApiException("征信解析调用失败: " + e.getMessage(), e);
} }
} }
private Map<String, Object> buildInitiateParams(String serialNum, String model, String remotePath) {
Map<String, Object> params = buildBaseParams(serialNum);
params.put("remotePath", remotePath);
params.put("model", model);
return params;
}
private Map<String, Object> buildBaseParams(String serialNum) {
Map<String, Object> params = new HashMap<>();
params.put("serialNum", serialNum);
params.put("orgCode", orgCode);
params.put("runType", runType);
return params;
}
private CreditParseInvokeResponse queryResult(String serialNum) {
Map<String, Object> params = buildBaseParams(serialNum);
for (int attempt = 1; attempt <= RESULT_QUERY_MAX_ATTEMPTS; attempt++) {
CreditParseInvokeResponse response = request(creditParseResultUrl, params,
"结果接口第" + attempt + "次查询");
requireSuccessfulServiceResponse(response, "征信解析结果接口");
if (response.getData() == null || response.getData().getMappingOutputFields() == null) {
waitForNextResult(serialNum, attempt);
continue;
}
if (response.getData().getMappingOutputFields().getPayload() != null) {
return response;
}
waitForNextResult(serialNum, attempt);
}
throw new LsfxApiException("征信解析结果未返回");
}
private void waitForNextResult(String serialNum, int attempt) {
if (attempt >= RESULT_QUERY_MAX_ATTEMPTS) {
return;
}
log.info("【征信解析】结果未返回: serialNum={}, attempt={}/{}, {}ms后重试",
serialNum, attempt, RESULT_QUERY_MAX_ATTEMPTS, RESULT_QUERY_INTERVAL_MILLIS);
try {
sleepBeforeNextResultQuery(RESULT_QUERY_INTERVAL_MILLIS);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new LsfxApiException("征信解析结果查询被中断", e);
}
}
protected void sleepBeforeNextResultQuery(long intervalMillis) throws InterruptedException {
Thread.sleep(intervalMillis);
}
private CreditParseInvokeResponse request(String url, Map<String, Object> params, String stage) {
try {
log.info("【征信解析】{}请求: url={}, params={}", stage, url, toJson(params));
String responseJson = httpUtil.postUrlEncodedFormForString(url, params, null);
log.info("【征信解析】{}返回JSON: {}", stage, responseJson);
return objectMapper.readValue(responseJson, CreditParseInvokeResponse.class);
} catch (LsfxApiException e) {
throw e;
} catch (Exception e) {
throw new LsfxApiException("征信解析" + stage + "调用失败: " + e.getMessage(), e);
}
}
private void requireSuccessfulInitiateResponse(CreditParseInvokeResponse response, String stage) {
requireSuccessfulServiceResponse(response, stage);
}
private void requireSuccessfulServiceResponse(CreditParseInvokeResponse response, String stage) {
requireSuccessfulPlatformResponse(response, stage);
if (response.getData() == null) {
throw new LsfxApiException(stage + "返回结果为空");
}
if (!Integer.valueOf(INITIATE_SUCCESS_STATUS).equals(response.getData().getStatus())) {
throw new LsfxApiException(serviceErrorMessage(response, stage + "状态异常: " + response.getData().getStatus()));
}
if (!Integer.valueOf(INITIATE_SUCCESS_REASON_CODE).equals(response.getData().getReasonCode())) {
throw new LsfxApiException(serviceErrorMessage(response, stage + "原因码异常: " + response.getData().getReasonCode()));
}
}
private void requireSuccessfulPlatformResponse(CreditParseInvokeResponse response, String stage) {
if (response == null) {
throw new LsfxApiException(stage + "返回结果为空");
}
if (!Boolean.TRUE.equals(response.getSuccess())) {
throw new LsfxApiException(stage + "平台调用失败");
}
if (!Integer.valueOf(PLATFORM_SUCCESS_CODE).equals(response.getCode())) {
throw new LsfxApiException(stage + "平台状态码异常: " + response.getCode());
}
}
private String buildSerialNum() {
return "CCDI_CREDIT_" + System.currentTimeMillis() + "_" + IdUtils.fastSimpleUUID();
}
private String stringValue(Object value, String defaultValue) {
if (value == null) {
return defaultValue;
}
String text = value.toString().trim();
return text.isEmpty() ? defaultValue : text;
}
private String serviceErrorMessage(CreditParseInvokeResponse response, String defaultValue) {
if (response == null || response.getData() == null) {
return defaultValue;
}
String reasonMessage = stringValue(response.getData().getReasonMessage(), null);
if (reasonMessage != null) {
return reasonMessage;
}
if (response.getData().getMappingOutputFields() != null) {
return stringValue(response.getData().getMappingOutputFields().getMessage(), defaultValue);
}
return defaultValue;
}
private String toJson(Object value) {
try {
return objectMapper.writeValueAsString(value);
} catch (Exception e) {
return String.valueOf(value);
}
}
} }

View File

@@ -4,7 +4,7 @@ import com.ruoyi.common.annotation.Anonymous;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils; import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse; import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import com.ruoyi.lsfx.exception.LsfxApiException; import com.ruoyi.lsfx.exception.LsfxApiException;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter; import io.swagger.v3.oas.annotations.Parameter;
@@ -14,6 +14,13 @@ import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController; import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.StandardCopyOption;
@Tag(name = "征信解析接口测试", description = "用于测试征信解析接口") @Tag(name = "征信解析接口测试", description = "用于测试征信解析接口")
@Anonymous @Anonymous
@@ -22,27 +29,56 @@ import org.springframework.web.bind.annotation.RestController;
public class CreditParseController { public class CreditParseController {
private static final String DEFAULT_MODEL = "LXCUSTALL"; private static final String DEFAULT_MODEL = "LXCUSTALL";
private static final String DEFAULT_HTYPE = "PERSON";
@Resource @Resource
private CreditParseClient creditParseClient; private CreditParseClient creditParseClient;
@Operation(summary = "解析征信HTML", description = "征信HTML远程地址并调用外部解析服务") @Operation(summary = "解析征信HTML", description = "传征信HTML文件并调用外部解析服务")
@PostMapping("/parse") @PostMapping("/parse")
public AjaxResult parse(@Parameter(description = "征信HTML远程访问地址") @RequestParam("remotePath") String remotePath, public AjaxResult parse(@Parameter(description = "征信HTML文件") @RequestParam("file") MultipartFile file,
@Parameter(description = "模型编码默认LXCUSTALL") @RequestParam(required = false) String model) { @Parameter(description = "解析模型默认LXCUSTALL") @RequestParam(required = false) String model,
if (StringUtils.isBlank(remotePath)) { @Parameter(description = "主体类型默认PERSON") @RequestParam(required = false) String hType) {
return AjaxResult.error("征信HTML远程地址不能为空"); if (file == null || file.isEmpty()) {
return AjaxResult.error("征信HTML文件不能为空");
}
String originalFilename = file.getOriginalFilename();
if (StringUtils.isBlank(originalFilename)) {
return AjaxResult.error("文件名不能为空");
}
String lowerCaseName = originalFilename.toLowerCase();
if (!lowerCaseName.endsWith(".html") && !lowerCaseName.endsWith(".htm")) {
return AjaxResult.error("仅支持 HTML 格式文件");
} }
String actualModel = StringUtils.isBlank(model) ? DEFAULT_MODEL : model; String actualModel = StringUtils.isBlank(model) ? DEFAULT_MODEL : model;
String actualHType = StringUtils.isBlank(hType) ? DEFAULT_HTYPE : hType;
Path tempFile = null;
try { try {
CreditParseInvokeResponse response = creditParseClient.parse(actualModel, remotePath); String suffix = lowerCaseName.endsWith(".htm") ? ".htm" : ".html";
tempFile = Files.createTempFile("credit_parse_", suffix);
Files.copy(file.getInputStream(), tempFile, StandardCopyOption.REPLACE_EXISTING);
File convertedFile = tempFile.toFile();
CreditParseResponse response = creditParseClient.parse(actualModel, actualHType, convertedFile);
return AjaxResult.success(response); return AjaxResult.success(response);
} catch (LsfxApiException e) { } catch (LsfxApiException e) {
return AjaxResult.error(e.getMessage()); return AjaxResult.error(e.getMessage());
} catch (IOException e) {
return AjaxResult.error("文件转换失败:" + e.getMessage());
} catch (Exception e) { } catch (Exception e) {
return AjaxResult.error("征信解析失败:" + e.getMessage()); return AjaxResult.error("征信解析失败:" + e.getMessage());
} finally {
if (tempFile != null) {
try {
Files.deleteIfExists(tempFile);
} catch (IOException ignored) {
// 忽略临时文件删除失败,避免影响主流程返回
}
}
} }
} }
} }

View File

@@ -1,17 +0,0 @@
package com.ruoyi.lsfx.domain.response;
import com.fasterxml.jackson.annotation.JsonIgnoreProperties;
import lombok.Data;
@Data
@JsonIgnoreProperties(ignoreUnknown = true)
public class CreditParseInvokeData {
private CreditParseResponse mappingOutputFields;
private Integer status;
private Integer reasonCode;
private String reasonMessage;
}

View File

@@ -1,13 +0,0 @@
package com.ruoyi.lsfx.domain.response;
import lombok.Data;
@Data
public class CreditParseInvokeResponse {
private Boolean success;
private Integer code;
private CreditParseInvokeData data;
}

View File

@@ -1,33 +0,0 @@
package com.ruoyi.lsfx.domain.response;
import com.fasterxml.jackson.core.JsonParser;
import com.fasterxml.jackson.core.ObjectCodec;
import com.fasterxml.jackson.databind.JsonDeserializer;
import com.fasterxml.jackson.databind.JsonMappingException;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.DeserializationContext;
import java.io.IOException;
public class CreditParsePayloadDeserializer extends JsonDeserializer<CreditParsePayload> {
@Override
public CreditParsePayload deserialize(JsonParser parser, DeserializationContext context) throws IOException {
ObjectCodec codec = parser.getCodec();
JsonNode node = codec.readTree(parser);
if (node == null || node.isNull()) {
return null;
}
if (node.isTextual()) {
String payloadText = node.asText();
if (payloadText == null || payloadText.trim().isEmpty()) {
return null;
}
node = codec.readTree(codec.getFactory().createParser(payloadText));
}
if (!node.isObject()) {
throw JsonMappingException.from(parser, "征信解析payload格式不支持");
}
return codec.treeToValue(node, CreditParsePayload.class);
}
}

View File

@@ -1,7 +1,6 @@
package com.ruoyi.lsfx.domain.response; package com.ruoyi.lsfx.domain.response;
import com.fasterxml.jackson.annotation.JsonProperty; import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
import lombok.Data; import lombok.Data;
@Data @Data
@@ -12,6 +11,5 @@ public class CreditParseResponse {
@JsonProperty("status_code") @JsonProperty("status_code")
private String statusCode; private String statusCode;
@JsonDeserialize(using = CreditParsePayloadDeserializer.class)
private CreditParsePayload payload; private CreditParsePayload payload;
} }

View File

@@ -206,86 +206,6 @@ public class HttpUtil {
} }
} }
/**
* 发送POST请求application/x-www-form-urlencoded格式带请求头
* @param url 请求URL
* @param params 表单参数
* @param headers 请求头
* @param responseType 响应类型
* @return 响应对象
*/
public <T> T postUrlEncodedForm(String url, Map<String, Object> params, Map<String, String> headers, Class<T> responseType) {
try {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach((key, value) -> {
if (value != null) {
body.add(key, value.toString());
}
});
}
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, httpHeaders);
ResponseEntity<T> response = restTemplate.postForEntity(url, requestEntity, responseType);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("API调用失败HTTP状态码: " + response.getStatusCode());
}
T responseBody = response.getBody();
if (responseBody == null) {
throw new LsfxApiException("API返回数据为空");
}
return responseBody;
} catch (RestClientException e) {
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
}
}
/**
* 发送POST请求application/x-www-form-urlencoded格式并返回原始JSON字符串
* @param url 请求URL
* @param params 表单参数
* @param headers 请求头
* @return 原始响应内容
*/
public String postUrlEncodedFormForString(String url, Map<String, Object> params, Map<String, String> headers) {
try {
HttpHeaders httpHeaders = createHeaders(headers);
httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
MultiValueMap<String, String> body = new LinkedMultiValueMap<>();
if (params != null) {
params.forEach((key, value) -> {
if (value != null) {
body.add(key, value.toString());
}
});
}
HttpEntity<MultiValueMap<String, String>> requestEntity = new HttpEntity<>(body, httpHeaders);
ResponseEntity<String> response = restTemplate.postForEntity(url, requestEntity, String.class);
if (!response.getStatusCode().is2xxSuccessful()) {
throw new LsfxApiException("API调用失败HTTP状态码: " + response.getStatusCode());
}
String responseBody = response.getBody();
if (responseBody == null) {
throw new LsfxApiException("API返回数据为空");
}
return responseBody;
} catch (RestClientException e) {
throw new LsfxApiException("网络请求失败: " + e.getMessage(), e);
}
}
/** /**
* 上传文件Multipart格式 * 上传文件Multipart格式
* @param url 请求URL * @param url 请求URL

View File

@@ -1,34 +1,24 @@
package com.ruoyi.lsfx.controller; package com.ruoyi.lsfx.controller;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.lsfx.client.CreditParseClient; import com.ruoyi.lsfx.client.CreditParseClient;
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse; import com.ruoyi.lsfx.domain.response.CreditParseResponse;
import com.ruoyi.lsfx.exception.LsfxApiException; import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.lsfx.util.HttpUtil;
import org.junit.jupiter.api.Test; import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith; import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.ArgumentCaptor;
import org.mockito.InjectMocks; import org.mockito.InjectMocks;
import org.mockito.Mock; import org.mockito.Mock;
import org.mockito.junit.jupiter.MockitoExtension; import org.mockito.junit.jupiter.MockitoExtension;
import org.springframework.test.util.ReflectionTestUtils; import org.springframework.mock.web.MockMultipartFile;
import java.util.ArrayList; import java.io.File;
import java.util.List; import java.nio.charset.StandardCharsets;
import java.util.Map;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertSame; import static org.junit.jupiter.api.Assertions.assertSame;
import static org.junit.jupiter.api.Assertions.assertThrows; import static org.mockito.ArgumentMatchers.any;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.mockito.ArgumentMatchers.anyString; import static org.mockito.ArgumentMatchers.anyString;
import static org.mockito.ArgumentMatchers.eq; import static org.mockito.ArgumentMatchers.eq;
import static org.mockito.ArgumentMatchers.isNull;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.times;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.when; import static org.mockito.Mockito.when;
@ExtendWith(MockitoExtension.class) @ExtendWith(MockitoExtension.class)
@@ -41,21 +31,31 @@ class CreditParseControllerTest {
private CreditParseController controller; private CreditParseController controller;
@Test @Test
void parse_shouldRejectBlankRemotePath() { void parse_shouldRejectEmptyFile() {
AjaxResult result = controller.parse(null, null); AjaxResult result = controller.parse(null, null, null);
assertEquals(500, result.get("code")); assertEquals(500, result.get("code"));
} }
@Test @Test
void shouldUseDefaultModelWhenMissing() { void parse_shouldRejectNonHtmlFile() {
CreditParseInvokeResponse response = new CreditParseInvokeResponse(); MockMultipartFile file = new MockMultipartFile(
response.setSuccess(true); "file", "credit.pdf", "application/pdf", "x".getBytes(StandardCharsets.UTF_8)
response.setCode(10000); );
AjaxResult result = controller.parse(file, null, null);
assertEquals(500, result.get("code"));
}
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html"; @Test
when(client.parse(eq("LXCUSTALL"), eq(remotePath))).thenReturn(response); void shouldUseDefaultModelAndTypeWhenMissing() {
MockMultipartFile file = new MockMultipartFile(
"file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
);
CreditParseResponse response = new CreditParseResponse();
response.setStatusCode("0");
AjaxResult result = controller.parse(remotePath, null); when(client.parse(eq("LXCUSTALL"), eq("PERSON"), any(File.class))).thenReturn(response);
AjaxResult result = controller.parse(file, null, null);
assertEquals(200, result.get("code")); assertEquals(200, result.get("code"));
assertSame(response, result.get("data")); assertSame(response, result.get("data"));
@@ -63,230 +63,14 @@ class CreditParseControllerTest {
@Test @Test
void shouldReturnAjaxErrorWhenClientThrows() { void shouldReturnAjaxErrorWhenClientThrows() {
when(client.parse(anyString(), anyString())) MockMultipartFile file = new MockMultipartFile(
"file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
);
when(client.parse(anyString(), anyString(), any(File.class)))
.thenThrow(new LsfxApiException("超时")); .thenThrow(new LsfxApiException("超时"));
AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", null); AjaxResult result = controller.parse(file, null, null);
assertEquals(500, result.get("code")); assertEquals(500, result.get("code"));
} }
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
void creditParseClient_shouldInitiateAndQueryResultWithSameSerialNum() throws Exception {
HttpUtil httpUtil = mock(HttpUtil.class);
CreditParseClient parseClient = new CreditParseClient();
ObjectMapper objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
ReflectionTestUtils.setField(parseClient, "runType", "1");
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"},\"lx_debt\":{\"uncle_bank_house_bal\":\"1\"},\"lx_publictype\":{\"civil_cnt\":1}}";
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(initiateSuccessResponse());
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE"));
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
CreditParseInvokeResponse actual = parseClient.parse(remotePath);
assertEquals(true, actual.getSuccess());
assertEquals(10000, actual.getCode());
assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
.getPayload().getLxHeader().get("query_cert_no"));
ArgumentCaptor<Map<String, Object>> initiateParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
verify(httpUtil).postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
initiateParamsCaptor.capture(),
isNull()
);
ArgumentCaptor<Map<String, Object>> resultParamsCaptor = ArgumentCaptor.forClass((Class) Map.class);
verify(httpUtil).postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
resultParamsCaptor.capture(),
isNull()
);
Map<String, Object> initiateParams = initiateParamsCaptor.getValue();
Map<String, Object> resultParams = resultParamsCaptor.getValue();
assertNotNull(initiateParams.get("serialNum"));
assertTrue(initiateParams.get("serialNum").toString().startsWith("CCDI_CREDIT_"));
assertEquals(initiateParams.get("serialNum"), resultParams.get("serialNum"));
assertEquals("999000", initiateParams.get("orgCode"));
assertEquals("999000", resultParams.get("orgCode"));
assertEquals("1", initiateParams.get("runType"));
assertEquals("1", resultParams.get("runType"));
assertEquals(remotePath, initiateParams.get("remotePath"));
assertEquals("LXCUSTALL", initiateParams.get("model"));
assertEquals(false, resultParams.containsKey("remotePath"));
assertEquals(false, resultParams.containsKey("model"));
}
@Test
@SuppressWarnings({"unchecked", "rawtypes"})
void creditParseClient_shouldRetryEmptyPayloadFiveTimesWithTwoSecondInterval() throws Exception {
HttpUtil httpUtil = mock(HttpUtil.class);
TestableCreditParseClient parseClient = new TestableCreditParseClient();
ObjectMapper objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
ReflectionTestUtils.setField(parseClient, "runType", "1");
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
String emptyPayloadResponse = "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200,\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"ERR_SHOULD_IGNORE\"}}}";
String payload = "{\"lx_header\":{\"query_cert_no\":\"330101199001010011\",\"query_cust_name\":\"张三\",\"report_time\":\"2026-03-24\"}}";
String resultResponse = resultSuccessResponse(objectMapper, payload, "ERR_SHOULD_IGNORE");
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(initiateSuccessResponse());
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, emptyPayloadResponse, resultResponse);
CreditParseInvokeResponse actual = parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html");
assertEquals("330101199001010011", actual.getData().getMappingOutputFields()
.getPayload().getLxHeader().get("query_cert_no"));
verify(httpUtil, times(5)).postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
);
assertEquals(List.of(2000L, 2000L, 2000L, 2000L), parseClient.getSleepIntervals());
}
@Test
void creditParseClient_shouldRejectInvalidOuterCode() throws Exception {
HttpUtil httpUtil = mock(HttpUtil.class);
TestableCreditParseClient parseClient = new TestableCreditParseClient();
ObjectMapper objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
ReflectionTestUtils.setField(parseClient, "runType", "1");
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(initiateSuccessResponse());
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn("{\"success\":true,\"code\":99999,\"data\":{\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"0\"}}}");
LsfxApiException exception = assertThrows(LsfxApiException.class,
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
assertTrue(exception.getMessage().contains("平台状态码异常"));
}
@Test
void creditParseClient_shouldRejectFailedResultStatus() throws Exception {
HttpUtil httpUtil = mock(HttpUtil.class);
TestableCreditParseClient parseClient = new TestableCreditParseClient();
ObjectMapper objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
ReflectionTestUtils.setField(parseClient, "runType", "1");
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn(initiateSuccessResponse());
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"reasonMessage\":\"解析失败\",\"reasonCode\":500,\"status\":0,\"mappingOutputFields\":{\"message\":\"结果异常\"}}}");
LsfxApiException exception = assertThrows(LsfxApiException.class,
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
assertTrue(exception.getMessage().contains("解析失败"));
}
@Test
void creditParseClient_shouldRejectFailedInitiateStatus() throws Exception {
HttpUtil httpUtil = mock(HttpUtil.class);
TestableCreditParseClient parseClient = new TestableCreditParseClient();
ObjectMapper objectMapper = new ObjectMapper();
ReflectionTestUtils.setField(parseClient, "httpUtil", httpUtil);
ReflectionTestUtils.setField(parseClient, "creditParseUrl", "http://tz/api/service/interface/invokeService/xfeature");
ReflectionTestUtils.setField(parseClient, "creditParseResultUrl", "http://tz/api/service/interface/invokeService/xfeatureResult");
ReflectionTestUtils.setField(parseClient, "orgCode", "999000");
ReflectionTestUtils.setField(parseClient, "runType", "1");
ReflectionTestUtils.setField(parseClient, "defaultModel", "LXCUSTALL");
ReflectionTestUtils.setField(parseClient, "objectMapper", objectMapper);
when(httpUtil.postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeature"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
)).thenReturn("{\"success\":true,\"code\":10000,\"data\":{\"mappingOutputFields\":{\"message\":\"文件写入失败\"},\"reasonMessage\":\"文件写入失败\",\"reasonCode\":500,\"status\":0}}");
LsfxApiException exception = assertThrows(LsfxApiException.class,
() -> parseClient.parse("http://127.0.0.1:62318/profile/credit-html/a.html"));
assertTrue(exception.getMessage().contains("文件写入失败"));
verify(httpUtil, times(0)).postUrlEncodedFormForString(
eq("http://tz/api/service/interface/invokeService/xfeatureResult"),
org.mockito.ArgumentMatchers.<Map<String, Object>>any(),
isNull()
);
}
private static String initiateSuccessResponse() {
return "{\"success\":true,\"code\":10000,\"data\":{\"traceId\":\"TRACE-001\","
+ "\"mappingOutputFields\":{\"message\":\"文件写入成功,流水号为: CCDI_CREDIT_TEST\"},"
+ "\"reasonMessage\":\"Running successfully\",\"procCode\":\"999000\","
+ "\"reasonCode\":200,\"status\":1}}";
}
private static String resultSuccessResponse(ObjectMapper objectMapper, String payload, String statusCode) throws Exception {
return "{\"success\":true,\"code\":10000,\"data\":{\"status\":1,\"reasonCode\":200,"
+ "\"mappingOutputFields\":{\"message\":\"\",\"status_code\":\"" + statusCode + "\",\"payload\":"
+ objectMapper.writeValueAsString(payload) + "}}}";
}
private static class TestableCreditParseClient extends CreditParseClient {
private final List<Long> sleepIntervals = new ArrayList<>();
@Override
protected void sleepBeforeNextResultQuery(long intervalMillis) {
sleepIntervals.add(intervalMillis);
}
private List<Long> getSleepIntervals() {
return sleepIntervals;
}
}
} }

View File

@@ -9,7 +9,6 @@ public final class CcdiProjectStatusConstants {
public static final String COMPLETED = "1"; public static final String COMPLETED = "1";
public static final String ARCHIVED = "2"; public static final String ARCHIVED = "2";
public static final String TAGGING = "3"; public static final String TAGGING = "3";
public static final String TAG_FAILED = "4";
private CcdiProjectStatusConstants() { private CcdiProjectStatusConstants() {
} }

View File

@@ -6,7 +6,6 @@ import com.ruoyi.ccdi.project.domain.excel.CcdiBankStatementExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementFilterOptionsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiBankStatementService; import com.ruoyi.ccdi.project.service.ICcdiBankStatementService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -40,16 +39,13 @@ public class CcdiBankStatementController extends BaseController {
@Resource @Resource
private ICcdiBankStatementService bankStatementService; private ICcdiBankStatementService bankStatementService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 分页查询流水明细 * 分页查询流水明细
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "分页查询流水明细") @Operation(summary = "分页查询流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public TableDataInfo list(CcdiBankStatementQueryDTO queryDTO) { public TableDataInfo list(CcdiBankStatementQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiBankStatementListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiBankStatementListVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiBankStatementListVO> result = bankStatementService.selectStatementPage(page, queryDTO); Page<CcdiBankStatementListVO> result = bankStatementService.selectStatementPage(page, queryDTO);
@@ -61,8 +57,8 @@ public class CcdiBankStatementController extends BaseController {
*/ */
@GetMapping("/options") @GetMapping("/options")
@Operation(summary = "查询项目级筛选项") @Operation(summary = "查询项目级筛选项")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getOptions(Long projectId) { public AjaxResult getOptions(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiBankStatementFilterOptionsVO options = bankStatementService.getFilterOptions(projectId); CcdiBankStatementFilterOptionsVO options = bankStatementService.getFilterOptions(projectId);
return AjaxResult.success(options); return AjaxResult.success(options);
} }
@@ -72,8 +68,8 @@ public class CcdiBankStatementController extends BaseController {
*/ */
@GetMapping("/detail/{bankStatementId}") @GetMapping("/detail/{bankStatementId}")
@Operation(summary = "查询流水详情") @Operation(summary = "查询流水详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getDetail(@PathVariable Long bankStatementId) { public AjaxResult getDetail(@PathVariable Long bankStatementId) {
projectAccessService.assertCanReadByBankStatementId(bankStatementId);
CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(bankStatementId); CcdiBankStatementDetailVO detail = bankStatementService.getStatementDetail(bankStatementId);
return AjaxResult.success(detail); return AjaxResult.success(detail);
} }
@@ -85,7 +81,6 @@ public class CcdiBankStatementController extends BaseController {
@Operation(summary = "导出流水明细") @Operation(summary = "导出流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:export')") @PreAuthorize("@ss.hasPermi('ccdi:project:export')")
public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) { public void export(HttpServletResponse response, CcdiBankStatementQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO); List<CcdiBankStatementExcel> list = bankStatementService.selectStatementListForExport(queryDTO);
ExcelUtil<CcdiBankStatementExcel> util = new ExcelUtil<>(CcdiBankStatementExcel.class); ExcelUtil<CcdiBankStatementExcel> util = new ExcelUtil<>(CcdiBankStatementExcel.class);
util.exportExcel(response, list, "流水明细"); util.exportExcel(response, list, "流水明细");

View File

@@ -1,7 +1,6 @@
package com.ruoyi.ccdi.project.controller; package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiBankTagRebuildDTO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiBankTagService; import com.ruoyi.ccdi.project.service.ICcdiBankTagService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -10,7 +9,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RequestBody;
@@ -29,17 +27,12 @@ public class CcdiBankTagController extends BaseController {
@Resource @Resource
private ICcdiBankTagService bankTagService; private ICcdiBankTagService bankTagService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 手动提交流水标签重算任务 * 手动提交流水标签重算任务
*/ */
@Operation(summary = "手动重算项目流水标签") @Operation(summary = "手动重算项目流水标签")
@PostMapping("/rebuild") @PostMapping("/rebuild")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) { public AjaxResult rebuild(@Validated @RequestBody CcdiBankTagRebuildDTO dto) {
projectAccessService.assertCanOperate(dto.getProjectId());
String operator = SecurityUtils.getUsername(); String operator = SecurityUtils.getUsername();
log.info("【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={}", log.info("【流水标签】收到手动重算请求: projectId={}, modelCode={}, operator={}",
dto.getProjectId(), dto.getModelCode(), operator); dto.getProjectId(), dto.getModelCode(), operator);

View File

@@ -3,7 +3,6 @@ package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceSaveDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiEvidenceSaveDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiEvidenceVO; import com.ruoyi.ccdi.project.domain.vo.CcdiEvidenceVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiEvidenceService; import com.ruoyi.ccdi.project.service.ICcdiEvidenceService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -35,17 +34,13 @@ public class CcdiEvidenceController extends BaseController {
@Resource @Resource
private ICcdiEvidenceService evidenceService; private ICcdiEvidenceService evidenceService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 保存证据 * 保存证据
*/ */
@PostMapping @PostMapping
@Operation(summary = "保存证据") @Operation(summary = "保存证据")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult saveEvidence(@Validated @RequestBody CcdiEvidenceSaveDTO dto) { public AjaxResult saveEvidence(@Validated @RequestBody CcdiEvidenceSaveDTO dto) {
projectAccessService.assertCanOperate(dto.getProjectId());
CcdiEvidenceVO vo = evidenceService.saveEvidence(dto, SecurityUtils.getUsername()); CcdiEvidenceVO vo = evidenceService.saveEvidence(dto, SecurityUtils.getUsername());
return AjaxResult.success("证据入库成功", vo); return AjaxResult.success("证据入库成功", vo);
} }
@@ -55,8 +50,8 @@ public class CcdiEvidenceController extends BaseController {
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询项目证据列表") @Operation(summary = "查询项目证据列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listEvidence(CcdiEvidenceQueryDTO queryDTO) { public AjaxResult listEvidence(CcdiEvidenceQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiEvidenceVO> list = evidenceService.listEvidence(queryDTO); List<CcdiEvidenceVO> list = evidenceService.listEvidence(queryDTO);
return AjaxResult.success(list); return AjaxResult.success(list);
} }
@@ -66,8 +61,8 @@ public class CcdiEvidenceController extends BaseController {
*/ */
@GetMapping("/{evidenceId}") @GetMapping("/{evidenceId}")
@Operation(summary = "查询证据详情") @Operation(summary = "查询证据详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEvidence(@PathVariable Long evidenceId) { public AjaxResult getEvidence(@PathVariable Long evidenceId) {
projectAccessService.assertCanReadByEvidenceId(evidenceId);
CcdiEvidenceVO vo = evidenceService.getEvidence(evidenceId); CcdiEvidenceVO vo = evidenceService.getEvidence(evidenceId);
return AjaxResult.success(vo); return AjaxResult.success(vo);
} }

View File

@@ -6,7 +6,6 @@ import com.ruoyi.ccdi.project.domain.dto.CcdiPullBankInfoSubmitDTO;
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord; import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiFileUploadStatisticsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiIdCardParseVO; import com.ruoyi.ccdi.project.domain.vo.CcdiIdCardParseVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiFileUploadService; import com.ruoyi.ccdi.project.service.ICcdiFileUploadService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -18,7 +17,6 @@ import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.util.CollectionUtils; import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -42,22 +40,17 @@ public class CcdiFileUploadController extends BaseController {
@Resource @Resource
private ICcdiFileUploadService fileUploadService; private ICcdiFileUploadService fileUploadService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 批量上传文件(异步) * 批量上传文件(异步)
*/ */
@PostMapping("/batch") @PostMapping("/batch")
@Operation(summary = "批量上传文件", description = "异步批量上传流水文件") @Operation(summary = "批量上传文件", description = "异步批量上传流水文件")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult batchUpload(@RequestParam Long projectId, public AjaxResult batchUpload(@RequestParam Long projectId,
@RequestParam MultipartFile[] files) { @RequestParam MultipartFile[] files) {
// 参数校验 // 参数校验
if (projectId == null) { if (projectId == null) {
return AjaxResult.error("项目ID不能为空"); return AjaxResult.error("项目ID不能为空");
} }
projectAccessService.assertCanOperate(projectId);
if (files == null || files.length == 0) { if (files == null || files.length == 0) {
return AjaxResult.error("请选择要上传的文件"); return AjaxResult.error("请选择要上传的文件");
} }
@@ -78,9 +71,9 @@ public class CcdiFileUploadController extends BaseController {
return AjaxResult.error("文件名不能为空"); return AjaxResult.error("文件名不能为空");
} }
String lowerFileName = fileName.toLowerCase(); String lowerFileName = fileName.toLowerCase();
if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".csv") if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".xls")
&& !lowerFileName.endsWith(".pdf")) { && !lowerFileName.endsWith(".csv") && !lowerFileName.endsWith(".pdf")) {
return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, XLSX 文件"); return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, Excel 文件");
} }
} }
@@ -102,7 +95,6 @@ public class CcdiFileUploadController extends BaseController {
*/ */
@PostMapping("/parse-id-card-file") @PostMapping("/parse-id-card-file")
@Operation(summary = "解析身份证文件", description = "解析首个sheet第一列的身份证号") @Operation(summary = "解析身份证文件", description = "解析首个sheet第一列的身份证号")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) { public AjaxResult parseIdCardFile(@RequestParam MultipartFile file) {
if (file == null || file.isEmpty()) { if (file == null || file.isEmpty()) {
return AjaxResult.error("身份证文件不能为空"); return AjaxResult.error("身份证文件不能为空");
@@ -116,12 +108,10 @@ public class CcdiFileUploadController extends BaseController {
*/ */
@PostMapping("/pull-bank-info") @PostMapping("/pull-bank-info")
@Operation(summary = "拉取本行信息", description = "按身份证号批量提交拉取本行信息任务") @Operation(summary = "拉取本行信息", description = "按身份证号批量提交拉取本行信息任务")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) { public AjaxResult pullBankInfo(@RequestBody CcdiPullBankInfoSubmitDTO dto) {
if (dto == null || dto.getProjectId() == null) { if (dto == null || dto.getProjectId() == null) {
return AjaxResult.error("项目ID不能为空"); return AjaxResult.error("项目ID不能为空");
} }
projectAccessService.assertCanOperate(dto.getProjectId());
if (CollectionUtils.isEmpty(dto.getIdCards())) { if (CollectionUtils.isEmpty(dto.getIdCards())) {
return AjaxResult.error("身份证号不能为空"); return AjaxResult.error("身份证号不能为空");
} }
@@ -148,7 +138,6 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询上传记录列表", description = "分页查询文件上传记录") @Operation(summary = "查询上传记录列表", description = "分页查询文件上传记录")
public TableDataInfo list(CcdiFileUploadQueryDTO queryDTO) { public TableDataInfo list(CcdiFileUploadQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiFileUploadRecord> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiFileUploadRecord> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiFileUploadRecord> result = fileUploadService.selectPage(page, queryDTO); Page<CcdiFileUploadRecord> result = fileUploadService.selectPage(page, queryDTO);
@@ -161,7 +150,6 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/statistics/{projectId}") @GetMapping("/statistics/{projectId}")
@Operation(summary = "查询上传统计", description = "统计各状态的文件数量") @Operation(summary = "查询上传统计", description = "统计各状态的文件数量")
public AjaxResult getStatistics(@PathVariable Long projectId) { public AjaxResult getStatistics(@PathVariable Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiFileUploadStatisticsVO statistics = fileUploadService.countByStatus(projectId); CcdiFileUploadStatisticsVO statistics = fileUploadService.countByStatus(projectId);
return AjaxResult.success(statistics); return AjaxResult.success(statistics);
} }
@@ -172,7 +160,6 @@ public class CcdiFileUploadController extends BaseController {
@GetMapping("/detail/{id}") @GetMapping("/detail/{id}")
@Operation(summary = "查询记录详情", description = "根据ID查询文件上传记录详情") @Operation(summary = "查询记录详情", description = "根据ID查询文件上传记录详情")
public AjaxResult getDetail(@PathVariable Long id) { public AjaxResult getDetail(@PathVariable Long id) {
projectAccessService.assertCanReadByFileRecordId(id);
CcdiFileUploadRecord record = fileUploadService.getById(id); CcdiFileUploadRecord record = fileUploadService.getById(id);
return AjaxResult.success(record); return AjaxResult.success(record);
} }
@@ -182,9 +169,7 @@ public class CcdiFileUploadController extends BaseController {
*/ */
@DeleteMapping("/{id}") @DeleteMapping("/{id}")
@Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水") @Operation(summary = "删除上传文件", description = "按上传记录ID删除文件并清理流水")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult deleteFile(@PathVariable Long id) { public AjaxResult deleteFile(@PathVariable Long id) {
projectAccessService.assertCanOperateByFileRecordId(id);
Long userId = SecurityUtils.getUserId(); Long userId = SecurityUtils.getUserId();
String message = fileUploadService.deleteFileUploadRecord(id, userId); String message = fileUploadService.deleteFileUploadRecord(id, userId);
return AjaxResult.success(message); return AjaxResult.success(message);

View File

@@ -1,86 +0,0 @@
package com.ruoyi.ccdi.project.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphEdgeDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphManualEdgeSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiFundGraphService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.core.page.PageDomain;
import com.ruoyi.common.core.page.TableDataInfo;
import com.ruoyi.common.core.page.TableSupport;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.ruoyi.common.utils.SecurityUtils;
import java.util.List;
/**
* 资金流图谱Controller
*/
@RestController
@RequestMapping("/ccdi/project/fund-graph")
@Tag(name = "资金流图谱")
public class CcdiFundGraphController extends BaseController {
@Resource
private ICcdiFundGraphService fundGraphService;
@Resource
private CcdiProjectAccessService projectAccessService;
@GetMapping("/search")
@Operation(summary = "查询资金流图谱主体")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult searchSubjects(CcdiFundGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiFundGraphNodeVO> subjects = fundGraphService.searchSubjects(queryDTO);
return AjaxResult.success(subjects);
}
@GetMapping("/graph")
@Operation(summary = "查询一层资金流图谱")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getGraph(CcdiFundGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiFundGraphVO graph = fundGraphService.getFundGraph(queryDTO);
return AjaxResult.success(graph);
}
@GetMapping("/edge-detail")
@Operation(summary = "查询资金边流水明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public TableDataInfo getEdgeDetail(CcdiFundGraphEdgeDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiFundGraphStatementVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiFundGraphStatementVO> result = fundGraphService.getEdgeDetails(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
@PostMapping("/manual-edge")
@Operation(summary = "新增手工资金流向")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult saveManualEdge(@RequestBody CcdiFundGraphManualEdgeSaveDTO saveDTO) {
try {
projectAccessService.assertCanOperate(saveDTO == null ? null : saveDTO.getProjectId());
CcdiFundGraphEdgeVO edge = fundGraphService.saveManualEdge(saveDTO, SecurityUtils.getUsername());
return AjaxResult.success(edge);
} catch (IllegalArgumentException e) {
return AjaxResult.error(e.getMessage());
}
}
}

View File

@@ -11,11 +11,9 @@ import com.ruoyi.ccdi.project.domain.dto.ModelParamSaveAllDTO;
import com.ruoyi.ccdi.project.domain.vo.ModelListVO; import com.ruoyi.ccdi.project.domain.vo.ModelListVO;
import com.ruoyi.ccdi.project.domain.vo.ModelParamVO; import com.ruoyi.ccdi.project.domain.vo.ModelParamVO;
import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO; import com.ruoyi.ccdi.project.domain.vo.ModelParamAllVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiModelParamService; import com.ruoyi.ccdi.project.service.ICcdiModelParamService;
import io.swagger.v3.oas.annotations.Operation; import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag; import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated; import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
@@ -33,17 +31,12 @@ public class CcdiModelParamController extends BaseController {
@Resource @Resource
private ICcdiModelParamService modelParamService; private ICcdiModelParamService modelParamService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询模型列表 * 查询模型列表
*/ */
@Operation(summary = "查询模型列表") @Operation(summary = "查询模型列表")
@GetMapping("/modelList") @GetMapping("/modelList")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listModels(@RequestParam(required = false) Long projectId) { public AjaxResult listModels(@RequestParam(required = false) Long projectId) {
assertCanReadProjectParam(projectId);
List<ModelListVO> list = modelParamService.selectModelList(projectId); List<ModelListVO> list = modelParamService.selectModelList(projectId);
return success(list); return success(list);
} }
@@ -53,9 +46,7 @@ public class CcdiModelParamController extends BaseController {
*/ */
@Operation(summary = "查询模型参数列表") @Operation(summary = "查询模型参数列表")
@GetMapping("/list") @GetMapping("/list")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) { public AjaxResult list(@Validated ModelParamQueryDTO queryDTO) {
assertCanReadProjectParam(queryDTO.getProjectId());
List<ModelParamVO> list = modelParamService.selectParamList(queryDTO); List<ModelParamVO> list = modelParamService.selectParamList(queryDTO);
return success(list); return success(list);
} }
@@ -66,9 +57,7 @@ public class CcdiModelParamController extends BaseController {
@Operation(summary = "保存模型参数") @Operation(summary = "保存模型参数")
@Log(title = "模型参数配置", businessType = BusinessType.UPDATE) @Log(title = "模型参数配置", businessType = BusinessType.UPDATE)
@PostMapping("/save") @PostMapping("/save")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult save(@Validated @RequestBody ModelParamSaveDTO saveDTO) { public AjaxResult save(@Validated @RequestBody ModelParamSaveDTO saveDTO) {
assertCanOperateProjectParam(saveDTO.getProjectId());
modelParamService.saveParams(saveDTO); modelParamService.saveParams(saveDTO);
return success("保存成功"); return success("保存成功");
} }
@@ -78,9 +67,7 @@ public class CcdiModelParamController extends BaseController {
*/ */
@Operation(summary = "查询所有模型及其参数") @Operation(summary = "查询所有模型及其参数")
@GetMapping("/listAll") @GetMapping("/listAll")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult listAll(@Validated ModelParamAllQueryDTO queryDTO) { public AjaxResult listAll(@Validated ModelParamAllQueryDTO queryDTO) {
assertCanReadProjectParam(queryDTO.getProjectId());
ModelParamAllVO result = modelParamService.selectAllParams(queryDTO.getProjectId()); ModelParamAllVO result = modelParamService.selectAllParams(queryDTO.getProjectId());
return success(result); return success(result);
} }
@@ -91,24 +78,8 @@ public class CcdiModelParamController extends BaseController {
@Operation(summary = "批量保存所有模型参数") @Operation(summary = "批量保存所有模型参数")
@Log(title = "模型参数配置", businessType = BusinessType.UPDATE) @Log(title = "模型参数配置", businessType = BusinessType.UPDATE)
@PostMapping("/saveAll") @PostMapping("/saveAll")
@PreAuthorize("@ss.hasPermi('ccdi:project:edit')")
public AjaxResult saveAll(@Validated @RequestBody ModelParamSaveAllDTO saveAllDTO) { public AjaxResult saveAll(@Validated @RequestBody ModelParamSaveAllDTO saveAllDTO) {
assertCanOperateProjectParam(saveAllDTO.getProjectId());
modelParamService.saveAllParams(saveAllDTO); modelParamService.saveAllParams(saveAllDTO);
return success("保存成功"); return success("保存成功");
} }
private void assertCanReadProjectParam(Long projectId) {
if (projectId == null || projectId <= 0) {
return;
}
projectAccessService.assertCanRead(projectId);
}
private void assertCanOperateProjectParam(Long projectId) {
if (projectId == null || projectId <= 0) {
return;
}
projectAccessService.assertCanOperate(projectId);
}
} }

View File

@@ -85,6 +85,7 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/{projectId}") @GetMapping("/{projectId}")
@Operation(summary = "查询项目详情") @Operation(summary = "查询项目详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getProject(@PathVariable Long projectId) { public AjaxResult getProject(@PathVariable Long projectId) {
CcdiProjectVO vo = projectService.getProjectById(projectId); CcdiProjectVO vo = projectService.getProjectById(projectId);
return AjaxResult.success(vo); return AjaxResult.success(vo);
@@ -95,6 +96,7 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/list") @GetMapping("/list")
@Operation(summary = "查询项目列表") @Operation(summary = "查询项目列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) { public TableDataInfo listProject(CcdiProjectQueryDTO queryDTO) {
PageDomain pageDomain = TableSupport.buildPageRequest(); PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); Page<CcdiProjectVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
@@ -107,6 +109,7 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/history") @GetMapping("/history")
@Operation(summary = "查询历史项目列表") @Operation(summary = "查询历史项目列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) { public AjaxResult listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO); List<CcdiProjectHistoryListItemVO> result = projectService.listHistoryProjects(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
@@ -128,6 +131,7 @@ public class CcdiProjectController extends BaseController {
*/ */
@GetMapping("/statusCounts") @GetMapping("/statusCounts")
@Operation(summary = "查询项目状态统计") @Operation(summary = "查询项目状态统计")
@PreAuthorize("@ss.hasPermi('ccdi:project:list')")
public AjaxResult getStatusCounts() { public AjaxResult getStatusCounts() {
CcdiProjectStatusCountsVO counts = projectService.getStatusCounts(); CcdiProjectStatusCountsVO counts = projectService.getStatusCounts();
return AjaxResult.success(counts); return AjaxResult.success(counts);

View File

@@ -2,20 +2,14 @@ package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
@@ -23,7 +17,6 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService; import com.ruoyi.ccdi.project.service.ICcdiProjectOverviewService;
import com.ruoyi.common.utils.poi.ExcelUtil; import com.ruoyi.common.utils.poi.ExcelUtil;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
@@ -52,9 +45,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Resource @Resource
private ICcdiProjectOverviewService overviewService; private ICcdiProjectOverviewService overviewService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询风险仪表盘 * 查询风险仪表盘
*/ */
@@ -62,7 +52,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险仪表盘") @Operation(summary = "查询风险仪表盘")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getDashboard(Long projectId) { public AjaxResult getDashboard(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectOverviewDashboardVO dashboard = overviewService.getDashboard(projectId); CcdiProjectOverviewDashboardVO dashboard = overviewService.getDashboard(projectId);
return AjaxResult.success(dashboard); return AjaxResult.success(dashboard);
} }
@@ -74,35 +63,10 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险人员总览") @Operation(summary = "查询风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) { public AjaxResult getRiskPeople(CcdiProjectRiskPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO); CcdiProjectRiskPeopleOverviewVO overview = overviewService.getRiskPeopleOverview(queryDTO);
return AjaxResult.success(overview); return AjaxResult.success(overview);
} }
/**
* 查询外部人员预警
*/
@GetMapping("/external-persons")
@Operation(summary = "查询外部人员预警")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalPersons(CcdiProjectExternalPersonQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExternalPersonWarningVO warnings = overviewService.getExternalPersonWarnings(queryDTO);
return AjaxResult.success(warnings);
}
/**
* 查询外部人员风险汇总
*/
@GetMapping("/external-persons/summary")
@Operation(summary = "查询外部人员风险汇总")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskSummary(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectExternalRiskSummaryVO summary = overviewService.getExternalRiskSummary(projectId);
return AjaxResult.success(summary);
}
/** /**
* 查询中高风险人员TOP10 * 查询中高风险人员TOP10
*/ */
@@ -110,7 +74,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询中高风险人员TOP10") @Operation(summary = "查询中高风险人员TOP10")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getTopRiskPeople(Long projectId) { public AjaxResult getTopRiskPeople(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectTopRiskPeopleVO topRiskPeople = overviewService.getTopRiskPeople(projectId); CcdiProjectTopRiskPeopleVO topRiskPeople = overviewService.getTopRiskPeople(projectId);
return AjaxResult.success(topRiskPeople); return AjaxResult.success(topRiskPeople);
} }
@@ -122,7 +85,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险模型卡片") @Operation(summary = "查询风险模型卡片")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskModelCards(Long projectId) { public AjaxResult getRiskModelCards(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectRiskModelCardsVO cards = overviewService.getRiskModelCards(projectId); CcdiProjectRiskModelCardsVO cards = overviewService.getRiskModelCards(projectId);
return AjaxResult.success(cards); return AjaxResult.success(cards);
} }
@@ -134,35 +96,10 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询风险模型命中人员") @Operation(summary = "查询风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) { public AjaxResult getRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO); CcdiProjectRiskModelPeopleVO people = overviewService.getRiskModelPeople(queryDTO);
return AjaxResult.success(people); return AjaxResult.success(people);
} }
/**
* 查询外部人员风险模型卡片
*/
@GetMapping("/external-risk-models/cards")
@Operation(summary = "查询外部人员风险模型卡片")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskModelCards(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProjectRiskModelCardsVO cards = overviewService.getExternalRiskModelCards(projectId);
return AjaxResult.success(cards);
}
/**
* 查询外部人员风险模型命中人员
*/
@GetMapping("/external-risk-models/people")
@Operation(summary = "查询外部人员风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectRiskModelPeopleVO people = overviewService.getExternalRiskModelPeople(queryDTO);
return AjaxResult.success(people);
}
/** /**
* 查询项目分析详情 * 查询项目分析详情
*/ */
@@ -170,7 +107,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询项目分析详情") @Operation(summary = "查询项目分析详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) { public AjaxResult getPersonAnalysisDetail(CcdiProjectPersonAnalysisDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO); CcdiProjectPersonAnalysisDetailVO detail = overviewService.getPersonAnalysisDetail(queryDTO);
return AjaxResult.success(detail); return AjaxResult.success(detail);
} }
@@ -182,7 +118,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询涉疑交易明细") @Operation(summary = "查询涉疑交易明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) { public AjaxResult getSuspiciousTransactions(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO); CcdiProjectSuspiciousTransactionPageVO pageVO = overviewService.getSuspiciousTransactions(queryDTO);
return AjaxResult.success(pageVO); return AjaxResult.success(pageVO);
} }
@@ -194,7 +129,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询项目员工负面征信") @Operation(summary = "查询项目员工负面征信")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) { public AjaxResult getEmployeeCreditNegative(CcdiProjectEmployeeCreditNegativeQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO); CcdiProjectEmployeeCreditNegativePageVO pageVO = overviewService.getEmployeeCreditNegative(queryDTO);
return AjaxResult.success(pageVO); return AjaxResult.success(pageVO);
} }
@@ -206,7 +140,6 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "查询异常账户人员信息") @Operation(summary = "查询异常账户人员信息")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) { public AjaxResult getAbnormalAccountPeople(CcdiProjectAbnormalAccountQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectAbnormalAccountPageVO pageVO = overviewService.getAbnormalAccountPeople(queryDTO); CcdiProjectAbnormalAccountPageVO pageVO = overviewService.getAbnormalAccountPeople(queryDTO);
return AjaxResult.success(pageVO); return AjaxResult.success(pageVO);
} }
@@ -221,7 +154,6 @@ public class CcdiProjectOverviewController extends BaseController {
HttpServletResponse response, HttpServletResponse response,
CcdiProjectSuspiciousTransactionQueryDTO queryDTO CcdiProjectSuspiciousTransactionQueryDTO queryDTO
) { ) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO); List<CcdiProjectSuspiciousTransactionExcel> rows = overviewService.exportSuspiciousTransactions(queryDTO);
ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util = ExcelUtil<CcdiProjectSuspiciousTransactionExcel> util =
new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class); new ExcelUtil<>(CcdiProjectSuspiciousTransactionExcel.class);
@@ -235,58 +167,12 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "导出风险人员总览") @Operation(summary = "导出风险人员总览")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskPeople(HttpServletResponse response, Long projectId) { public void exportRiskPeople(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId); List<CcdiProjectRiskPeopleOverviewExcel> rows = overviewService.exportRiskPeopleOverview(projectId);
ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util = ExcelUtil<CcdiProjectRiskPeopleOverviewExcel> util =
new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class); new ExcelUtil<>(CcdiProjectRiskPeopleOverviewExcel.class);
util.exportExcel(response, rows, "风险人员总览"); util.exportExcel(response, rows, "风险人员总览");
} }
/**
* 导出外部人员预警
*/
@PostMapping("/external-persons/export")
@Operation(summary = "导出外部人员预警")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportExternalPersons(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
List<CcdiProjectExternalPersonWarningExcel> rows = overviewService.exportExternalPersonWarnings(projectId);
ExcelUtil<CcdiProjectExternalPersonWarningExcel> util =
new ExcelUtil<>(CcdiProjectExternalPersonWarningExcel.class);
util.exportExcel(response, rows, "外部人员预警");
}
/**
* 导出风险模型命中人员
*/
@PostMapping("/risk-models/people/export")
@Operation(summary = "导出风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskModelPeople(HttpServletResponse response, CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportRiskModelPeople(queryDTO);
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
util.exportExcel(response, rows, "风险模型命中人员");
}
/**
* 导出外部人员风险模型命中人员
*/
@PostMapping("/external-risk-models/people/export")
@Operation(summary = "导出外部人员风险模型命中人员")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportExternalRiskModelPeople(
HttpServletResponse response,
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiProjectRiskModelPeopleExcel> rows = overviewService.exportExternalRiskModelPeople(queryDTO);
ExcelUtil<CcdiProjectRiskModelPeopleExcel> util =
new ExcelUtil<>(CcdiProjectRiskModelPeopleExcel.class);
util.exportExcel(response, rows, "外部人员风险模型命中人员");
}
/** /**
* 导出风险明细 * 导出风险明细
*/ */
@@ -294,18 +180,16 @@ public class CcdiProjectOverviewController extends BaseController {
@Operation(summary = "导出风险明细") @Operation(summary = "导出风险明细")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportRiskDetails(HttpServletResponse response, Long projectId) { public void exportRiskDetails(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
overviewService.exportRiskDetails(response, projectId); overviewService.exportRiskDetails(response, projectId);
} }
/** /**
* 导出结果总览报告 * 一键导出结果总览报告
*/ */
@RequestMapping(value = "/report/export", method = { RequestMethod.GET, RequestMethod.POST }) @RequestMapping(value = "/report/export", method = { RequestMethod.GET, RequestMethod.POST })
@Operation(summary = "导出结果总览报告") @Operation(summary = "一键导出结果总览报告")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public void exportOverviewReport(HttpServletResponse response, Long projectId) { public void exportOverviewReport(HttpServletResponse response, Long projectId) {
projectAccessService.assertCanRead(projectId);
overviewService.exportOverviewReport(response, projectId); overviewService.exportOverviewReport(response, projectId);
} }
} }

View File

@@ -16,7 +16,6 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExtendedTransferListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetLiabilityListVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService; import com.ruoyi.ccdi.project.service.ICcdiProjectSpecialCheckService;
import com.ruoyi.common.core.controller.BaseController; import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult; import com.ruoyi.common.core.domain.AjaxResult;
@@ -40,9 +39,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Resource @Resource
private ICcdiProjectSpecialCheckService specialCheckService; private ICcdiProjectSpecialCheckService specialCheckService;
@Resource
private CcdiProjectAccessService projectAccessService;
/** /**
* 查询员工家庭资产负债列表 * 查询员工家庭资产负债列表
*/ */
@@ -50,7 +46,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询员工家庭资产负债列表") @Operation(summary = "查询员工家庭资产负债列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityList(@Validated CcdiProjectFamilyAssetLiabilityListQueryDTO queryDTO) { public AjaxResult getFamilyAssetLiabilityList(@Validated CcdiProjectFamilyAssetLiabilityListQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectFamilyAssetLiabilityListVO result = specialCheckService.getFamilyAssetLiabilityList(queryDTO); CcdiProjectFamilyAssetLiabilityListVO result = specialCheckService.getFamilyAssetLiabilityList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -62,7 +57,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询员工家庭资产负债详情") @Operation(summary = "查询员工家庭资产负债详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getFamilyAssetLiabilityDetail(@Validated CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO) { public AjaxResult getFamilyAssetLiabilityDetail(@Validated CcdiProjectFamilyAssetLiabilityDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO); CcdiProjectFamilyAssetLiabilityDetailVO result = specialCheckService.getFamilyAssetLiabilityDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -74,7 +68,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询采购拓展列表") @Operation(summary = "查询采购拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) { public AjaxResult getExtendedPurchaseList(@Validated CcdiProjectExtendedPurchaseQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO); CcdiProjectExtendedPurchaseListVO result = specialCheckService.getExtendedPurchaseList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -86,7 +79,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询采购拓展详情") @Operation(summary = "查询采购拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) { public AjaxResult getExtendedPurchaseDetail(@Validated CcdiProjectExtendedPurchaseDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO); CcdiProjectExtendedPurchaseDetailVO result = specialCheckService.getExtendedPurchaseDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -98,7 +90,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询招聘拓展列表") @Operation(summary = "查询招聘拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) { public AjaxResult getExtendedRecruitmentList(@Validated CcdiProjectExtendedRecruitmentQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO); CcdiProjectExtendedRecruitmentListVO result = specialCheckService.getExtendedRecruitmentList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -110,7 +101,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询招聘拓展详情") @Operation(summary = "查询招聘拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) { public AjaxResult getExtendedRecruitmentDetail(@Validated CcdiProjectExtendedRecruitmentDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO); CcdiProjectExtendedRecruitmentDetailVO result = specialCheckService.getExtendedRecruitmentDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -122,7 +112,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询调动拓展列表") @Operation(summary = "查询调动拓展列表")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) { public AjaxResult getExtendedTransferList(@Validated CcdiProjectExtendedTransferQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO); CcdiProjectExtendedTransferListVO result = specialCheckService.getExtendedTransferList(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }
@@ -134,7 +123,6 @@ public class CcdiProjectSpecialCheckController extends BaseController {
@Operation(summary = "查询调动拓展详情") @Operation(summary = "查询调动拓展详情")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')") @PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) { public AjaxResult getExtendedTransferDetail(@Validated CcdiProjectExtendedTransferDetailQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO); CcdiProjectExtendedTransferDetailVO result = specialCheckService.getExtendedTransferDetail(queryDTO);
return AjaxResult.success(result); return AjaxResult.success(result);
} }

View File

@@ -1,62 +0,0 @@
package com.ruoyi.ccdi.project.controller;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphSuspectedEnterpriseQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphVO;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiRelationGraphService;
import com.ruoyi.common.core.controller.BaseController;
import com.ruoyi.common.core.domain.AjaxResult;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import java.util.List;
/**
* 关系图谱Controller
*/
@RestController
@RequestMapping("/ccdi/project/relation-graph")
@Tag(name = "关系图谱")
public class CcdiRelationGraphController extends BaseController {
@Resource
private ICcdiRelationGraphService relationGraphService;
@Resource
private CcdiProjectAccessService projectAccessService;
@GetMapping("/search")
@Operation(summary = "查询关系图谱主体")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult searchSubjects(CcdiRelationGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
List<CcdiRelationGraphNodeVO> subjects = relationGraphService.searchSubjects(queryDTO);
return AjaxResult.success(subjects);
}
@GetMapping("/graph")
@Operation(summary = "查询一层关系图谱")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getGraph(CcdiRelationGraphQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiRelationGraphVO graph = relationGraphService.getRelationGraph(queryDTO);
return AjaxResult.success(graph);
}
@GetMapping("/suspected-enterprises")
@Operation(summary = "查询关系图谱疑似同名企业")
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
public AjaxResult getSuspectedEnterprises(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO) {
projectAccessService.assertCanRead(queryDTO.getProjectId());
CcdiRelationGraphSuspectedEnterpriseVO result = relationGraphService.getSuspectedEnterprises(queryDTO);
return AjaxResult.success(result);
}
}

View File

@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
/** 配置方式default-全局默认custom-自定义 */ /** 配置方式default-全局默认custom-自定义 */
private String configType; private String configType;
/** 项目状态0-进行中1-已完成2-已归档3-打标中4-打标失败 */ /** 项目状态0-进行中1-已完成2-已归档3-打标中 */
private String status; private String status;
/** 是否归档0-未归档1-已归档 */ /** 是否归档0-未归档1-已归档 */

View File

@@ -1,24 +0,0 @@
package com.ruoyi.ccdi.project.domain;
import lombok.AllArgsConstructor;
import lombok.Data;
/**
* 当前登录用户的项目访问范围。
*/
@Data
@AllArgsConstructor
public class ProjectAccessScope {
/** 当前用户名 */
private String username;
/** 是否可查看全部项目 */
private boolean viewAllProjects;
/** 是否超级管理员 */
private boolean superAdmin;
/** 是否项目管理员 */
private boolean projectManager;
}

View File

@@ -40,9 +40,6 @@ public class CcdiBankStatementQueryDTO {
/** 本方主体 */ /** 本方主体 */
private List<String> ourSubjects; private List<String> ourSubjects;
/** 本方证件号 */
private List<String> ourCertNos;
/** 本方银行 */ /** 本方银行 */
private List<String> ourBanks; private List<String> ourBanks;

View File

@@ -1,42 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资金流图谱边明细查询条件
*/
@Data
public class CcdiFundGraphEdgeDetailQueryDTO {
/** 项目ID历史字段资金流图谱不按项目过滤 */
private Long projectId;
/** 身份证号、员工姓名或本方户名 */
private String keyword;
/** 主体节点object_key复用图谱公共SQL片段时兼容条件判断 */
private String objectKey;
/** 边起点 */
private String fromKey;
/** 边终点 */
private String toKey;
/** 方向1支出2收入 */
private String direction;
/** 交易开始时间 */
private String transactionStartTime;
/** 交易结束时间 */
private String transactionEndTime;
/** 最小金额 */
private BigDecimal amountMin;
/** 最大金额 */
private BigDecimal amountMax;
}

View File

@@ -1,48 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
* 手工资金流向保存参数。
*/
@Data
public class CcdiFundGraphManualEdgeSaveDTO {
/** 当前项目ID仅用于写权限校验不参与手工资金流归属过滤 */
private Long projectId;
/** 起点主体object_key为空时默认使用当前查询中心 */
private String fromObjectKey;
/** 起点主体名称 */
private String fromName;
/** 终点主体object_key已有节点时传入 */
private String toObjectKey;
/** 终点主体名称;新建主体时必填 */
private String toName;
/** 终点主体身份证号/证件号有值时按md5(trim(idNo))复用主体 */
private String toIdNo;
/** 手工录入汇总金额 */
private BigDecimal amount;
/** 手工录入笔数 */
private Integer transactionCount;
/** 方向1支出2收入 */
private String direction;
/** 资金流向关系说明 */
private String relationDesc;
/** 来源说明 */
private String sourceDesc;
/** 分析备注 */
private String remark;
}

View File

@@ -1,45 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资金流图谱查询条件
*/
@Data
public class CcdiFundGraphQueryDTO {
/** 项目ID历史字段资金流图谱不按项目过滤 */
private Long projectId;
/** 身份证号、员工姓名或本方户名 */
private String keyword;
/** 主体节点object_key节点穿透时直接使用 */
private String objectKey;
/** 交易开始时间 */
private String transactionStartTime;
/** 交易结束时间 */
private String transactionEndTime;
/** 最小金额 */
private BigDecimal amountMin;
/** 最大金额 */
private BigDecimal amountMax;
/** 最小汇总金额默认1000 */
private BigDecimal minTotalAmount;
/** 方向1支出2收入 */
private String direction;
/** 返回边数量上限 */
private Integer limit;
/** 预留追溯层级,一期固定按一层处理 */
private Integer depth;
}

View File

@@ -1,19 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 外部人员预警查询DTO
*/
@Data
public class CcdiProjectExternalPersonQueryDTO {
/** 项目ID */
private Long projectId;
/** 页码 */
private Integer pageNum;
/** 每页数量 */
private Integer pageSize;
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import java.util.List;
import java.util.stream.Collectors;
import lombok.Data;
/**
* 外部人员模型命中人员查询DTO
*/
@Data
public class CcdiProjectExternalRiskModelPeopleQueryDTO {
/** 项目ID */
private Long projectId;
/** 模型编码 */
private List<String> modelCodes;
/** 匹配方式 */
private String matchMode;
/** 关键字 */
private String keyword;
/** 页码 */
private Integer pageNum;
/** 每页数量 */
private Integer pageSize;
public String getModelCodesCsv() {
if (modelCodes == null || modelCodes.isEmpty()) {
return null;
}
return modelCodes.stream()
.filter(item -> item != null && !item.isBlank())
.map(String::trim)
.distinct()
.collect(Collectors.joining(","));
}
}

View File

@@ -1,25 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 关系图谱查询条件
*/
@Data
public class CcdiRelationGraphQueryDTO {
/** 项目ID历史字段关系图谱不按项目过滤 */
private Long projectId;
/** 身份证号、姓名、统一社会信用代码或节点object_key */
private String keyword;
/** 节点object_key节点穿透时直接使用 */
private String objectKey;
/** 返回边数量上限 */
private Integer limit;
/** 预留追溯层级,一期固定按一层处理 */
private Integer depth;
}

View File

@@ -1,25 +0,0 @@
package com.ruoyi.ccdi.project.domain.dto;
import lombok.Data;
/**
* 关系图谱疑似企业查询条件
*/
@Data
public class CcdiRelationGraphSuspectedEnterpriseQueryDTO {
/** 项目ID */
private Long projectId;
/** 姓名 */
private String personName;
/** 证件号 */
private String certNo;
/** 出生日期yyyy-MM-dd */
private String birthDate;
/** 返回数量上限 */
private Integer limit;
}

View File

@@ -207,7 +207,6 @@ public class CcdiBankStatement implements Serializable {
entity.setBatchSequence(item.getUploadSequnceNumber()); entity.setBatchSequence(item.getUploadSequnceNumber());
entity.setCustomerCertNo(item.getCustomerCertNo()); entity.setCustomerCertNo(item.getCustomerCertNo());
entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode()); entity.setCustomerSocialCreditCode(item.getCustomerSocialCreditCode());
entity.setCretNo(normalizeCertNo(item.getCretNo()));
// 5. 特殊字段处理 // 5. 特殊字段处理
entity.setMetaJson(null); // 根据文档要求强制设为 null entity.setMetaJson(null); // 根据文档要求强制设为 null
@@ -220,19 +219,4 @@ public class CcdiBankStatement implements Serializable {
throw new RuntimeException("流水数据转换失败", e); throw new RuntimeException("流水数据转换失败", e);
} }
} }
private static String normalizeCertNo(String value) {
if (value == null || value.isBlank()) {
return null;
}
String normalized = value.trim()
.replace('', '-')
.replace('—', '-')
.replace('', '-');
int separatorIndex = normalized.indexOf('-');
if (separatorIndex >= 0) {
normalized = normalized.substring(0, separatorIndex).trim();
}
return normalized.isEmpty() ? null : normalized;
}
} }

View File

@@ -1,35 +0,0 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 外部人员预警导出对象
*/
@Data
public class CcdiProjectExternalPersonWarningExcel {
@Excel(name = "姓名")
private String name;
@Excel(name = "证件号")
private String idNo;
@Excel(name = "主体类型")
private String subjectType;
@Excel(name = "风险等级")
private String riskLevel;
@Excel(name = "命中模型数")
private Integer modelCount;
@Excel(name = "核心异常点")
private String riskPoint;
@Excel(name = "涉及对象")
private String relatedObject;
@Excel(name = "最近交易时间")
private String latestTradeTime;
}

View File

@@ -1,29 +0,0 @@
package com.ruoyi.ccdi.project.domain.excel;
import com.ruoyi.common.annotation.Excel;
import lombok.Data;
/**
* 风险模型命中人员导出对象
*/
@Data
public class CcdiProjectRiskModelPeopleExcel {
@Excel(name = "风险主体")
private String personName;
@Excel(name = "主体类型")
private String subjectType;
@Excel(name = "证件号")
private String idNo;
@Excel(name = "部门/涉及对象")
private String scopeName;
@Excel(name = "命中模型")
private String modelNames;
@Excel(name = "异常标签")
private String hitTags;
}

View File

@@ -14,29 +14,20 @@ public class CcdiProjectSuspiciousTransactionExcel {
@Excel(name = "交易时间") @Excel(name = "交易时间")
private String trxDate; private String trxDate;
@Excel(name = "本方账户") @Excel(name = "可疑人员")
private String leAccountNo; private String suspiciousPersonName;
@Excel(name = "本方主体") @Excel(name = "关联人")
private String leAccountName; private String relatedPersonName;
@Excel(name = "对方名称")
private String customerAccountName;
@Excel(name = "对方账户")
private String customerAccountNo;
@Excel(name = "关联员工") @Excel(name = "关联员工")
private String relatedStaffDisplay; private String relatedStaffDisplay;
@Excel(name = "摘要") @Excel(name = "关系")
private String userMemo; private String relationType;
@Excel(name = "交易类型") @Excel(name = "摘要/交易类型")
private String cashType; private String summaryAndCashType;
@Excel(name = "异常标签")
private String hitTags;
@Excel(name = "交易金额") @Excel(name = "交易金额")
private BigDecimal displayAmount; private BigDecimal displayAmount;

View File

@@ -1,50 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资金流图谱汇总边
*/
@Data
public class CcdiFundGraphEdgeVO {
private String edgeKey;
private String fromKey;
private String toKey;
private String fromObjectKey;
private String toObjectKey;
private String fromName;
private String toName;
private BigDecimal totalAmount;
private Long transactionCount;
private String firstTrxDate;
private String lastTrxDate;
private String direction;
private String familyRelationType;
private String sourceType;
private String relationDesc;
private String sourceDesc;
private String remark;
private Integer depth;
private Boolean canTrace;
}

View File

@@ -1,48 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资金流图谱节点
*/
@Data
public class CcdiFundGraphNodeVO {
private String nodeKey;
private String objectKey;
private String nodeName;
private String idNo;
private String cinocsno;
private String idnoType;
private String staffId;
private String sourceType;
private String nodeType;
private String identityType;
private String relationType;
private Long accountCount;
private String createdTime;
private String updatedTime;
private Boolean canExpand;
private Integer depth;
private BigDecimal totalAmount;
private Long transactionCount;
}

View File

@@ -1,34 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 资金流图谱边对应流水明细
*/
@Data
public class CcdiFundGraphStatementVO {
private Long bankStatementId;
private String trxDate;
private String leAccountNo;
private String leAccountName;
private String customerAccountName;
private String customerAccountNo;
private String cashType;
private String userMemo;
private BigDecimal amount;
private String direction;
private String familyRelationType;
}

View File

@@ -1,28 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
import java.util.ArrayList;
import java.util.List;
/**
* 资金流图谱结果
*/
@Data
public class CcdiFundGraphVO {
private CcdiFundGraphNodeVO centerNode;
private List<CcdiFundGraphNodeVO> nodes = new ArrayList<>();
private List<CcdiFundGraphEdgeVO> edges = new ArrayList<>();
private BigDecimal totalAmount = BigDecimal.ZERO;
private Long transactionCount = 0L;
private Integer maxDepth = 1;
private Boolean traceReserved = true;
}

View File

@@ -1,35 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 外部人员预警项
*/
@Data
public class CcdiProjectExternalPersonWarningItemVO {
private String name;
private String idNo;
private String subjectType;
private String riskLevel;
private String riskLevelType;
private Integer riskCount;
private Integer modelCount;
private String riskPoint;
private String relatedObject;
private String latestTradeTime;
private List<CcdiProjectRiskHitTagVO> riskPointTagList;
private String actionLabel;
}

View File

@@ -1,19 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import java.util.List;
import lombok.Data;
/**
* 外部人员预警分页
*/
@Data
public class CcdiProjectExternalPersonWarningVO {
private List<CcdiProjectExternalPersonWarningItemVO> rows;
private Long total;
private Long pageNum;
private Long pageSize;
}

View File

@@ -1,20 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 外部人员风险等级汇总
*/
@Data
public class CcdiProjectExternalRiskSummaryVO {
private Integer total;
private Integer high;
private Integer medium;
private Integer low;
private Integer noRisk;
}

View File

@@ -3,7 +3,6 @@ package com.ruoyi.ccdi.project.domain.vo;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import lombok.Data; import lombok.Data;
@@ -22,16 +21,10 @@ public class CcdiProjectOverviewReportVO {
private CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO(); private CcdiProjectOverviewDashboardVO dashboard = new CcdiProjectOverviewDashboardVO();
private CcdiProjectExternalRiskSummaryVO externalRiskSummary = new CcdiProjectExternalRiskSummaryVO();
private List<CcdiProjectOverviewReportModelSummaryVO> modelSummaries = new ArrayList<>(); private List<CcdiProjectOverviewReportModelSummaryVO> modelSummaries = new ArrayList<>();
private List<CcdiProjectOverviewReportModelSummaryVO> externalModelSummaries = new ArrayList<>();
private List<CcdiProjectRiskModelPeopleItemVO> riskPeople = new ArrayList<>(); private List<CcdiProjectRiskModelPeopleItemVO> riskPeople = new ArrayList<>();
private List<CcdiProjectExternalPersonWarningExcel> externalPersonWarnings = new ArrayList<>();
private List<CcdiProjectOverviewReportSuspiciousTransactionVO> suspiciousTransactions = new ArrayList<>(); private List<CcdiProjectOverviewReportSuspiciousTransactionVO> suspiciousTransactions = new ArrayList<>();
private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>(); private List<CcdiProjectEmployeeCreditNegativeExcel> illegalPeople = new ArrayList<>();

View File

@@ -13,8 +13,4 @@ public class CcdiProjectOverviewStatVO {
private String label; private String label;
private Integer value; private Integer value;
private Integer employeeValue;
private Integer externalValue;
} }

View File

@@ -17,6 +17,4 @@ public class CcdiProjectRiskHitTagVO {
private String ruleName; private String ruleName;
private String riskLevel; private String riskLevel;
private String reasonDetail;
} }

View File

@@ -23,7 +23,4 @@ public class CcdiProjectStatusCountsVO {
/** 打标中项目数(状态3) */ /** 打标中项目数(状态3) */
private Long status3; private Long status3;
/** 打标失败项目数(状态4) */
private Long status4;
} }

View File

@@ -33,6 +33,4 @@ public class CcdiProjectSuspiciousTransactionItemVO {
private Boolean hasModelRuleHit; private Boolean hasModelRuleHit;
private Boolean hasNameListHit; private Boolean hasNameListHit;
private String nameListHitType;
} }

View File

@@ -50,21 +50,9 @@ public class CcdiProjectVO {
/** 更新时间 */ /** 更新时间 */
private Date updateTime; private Date updateTime;
/** 最近一次打标失败原因 */
private String latestTagTaskErrorMessage;
/** 最近一次打标失败结束时间 */
private Date latestTagTaskEndTime;
/** 创建者(用户名) */ /** 创建者(用户名) */
private String createBy; private String createBy;
/** 创建者姓名(真实姓名) */ /** 创建者姓名(真实姓名) */
private String createByName; private String createByName;
/** 是否当前用户创建 */
private Boolean ownedByCurrentUser;
/** 当前用户是否可操作 */
private Boolean canOperate;
} }

View File

@@ -1,90 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.math.BigDecimal;
/**
* 关系图谱边
*/
@Data
public class CcdiRelationGraphEdgeVO {
private String objectKey;
private String fromKey;
private String toKey;
private String fromObjectKey;
private String toObjectKey;
private String fromName;
private String toName;
private String edgeTable;
private String relationType;
private String companyName;
private String stockName;
private String stockType;
private String stockPercent;
private String shouldCapi;
private String shouldCapiValue;
private String shouldCapiUnit;
private String shoudDate;
private String pKeyNo;
private String operName;
private String operKeyNo;
private String personId;
private String relationName;
private String relationCertNo;
private String gender;
private String birthDate;
private String relationCertType;
private String mobilePhone1;
private String mobilePhone2;
private String wechatNo1;
private String wechatNo2;
private String wechatNo3;
private String contactAddress;
private BigDecimal annualIncome;
private String relationDesc;
private String status;
private String effectiveDate;
private String invalidDate;
private String remark;
private String dataSource;
}

View File

@@ -1,34 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 关系图谱节点
*/
@Data
public class CcdiRelationGraphNodeVO {
private String objectKey;
private String nodeKey;
private String nodeName;
private String idNumber;
private String subjectType;
private String sourceType;
private String detailRefType;
private String detailRefKey;
private String createdTime;
private String updatedTime;
private Boolean canExpand;
private Integer depth;
}

View File

@@ -1,35 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
/**
* 关系图谱疑似企业明细
*/
@Data
public class CcdiRelationGraphSuspectedEnterpriseItemVO {
private String candidateKeyNo;
private String personName;
private String companyId;
private String companyName;
private String creditCode;
private String enterpriseStatus;
private String industryName;
private String relationType;
private String stockPercent;
/** 企业成立日期或当前可用的工商关系日期 */
private String establishDate;
private Integer ageAtEstablish;
private String matchReason;
}

View File

@@ -1,25 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 关系图谱疑似企业结果
*/
@Data
public class CcdiRelationGraphSuspectedEnterpriseVO {
/** 是否因同名候选过多被拦截 */
private Boolean blocked = false;
/** 拦截或空结果说明 */
private String message;
/** 同名工商keyno数量 */
private Integer sameNameKeyNoCount = 0;
/** 表格明细 */
private List<CcdiRelationGraphSuspectedEnterpriseItemVO> rows = new ArrayList<>();
}

View File

@@ -1,23 +0,0 @@
package com.ruoyi.ccdi.project.domain.vo;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* 关系图谱结果
*/
@Data
public class CcdiRelationGraphVO {
private CcdiRelationGraphNodeVO centerNode;
private List<CcdiRelationGraphNodeVO> nodes = new ArrayList<>();
private List<CcdiRelationGraphEdgeVO> edges = new ArrayList<>();
private Long edgeCount = 0L;
private Integer maxDepth = 1;
}

View File

@@ -90,36 +90,6 @@ public interface CcdiBankTagAnalysisMapper {
List<BankTagStatementHitVO> selectLargeTransferStatements(@Param("projectId") Long projectId, List<BankTagStatementHitVO> selectLargeTransferStatements(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold); @Param("threshold") BigDecimal threshold);
/**
* 外部人员单笔大额交易
*
* @param projectId 项目ID
* @param threshold 单笔大额阈值
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalSingleLargeAmountStatements(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/**
* 外部人员累计交易超限
*
* @param projectId 项目ID
* @param threshold 累计交易阈值
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalCumulativeTransactionAmountObjects(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/**
* 外部人员年流水超限
*
* @param projectId 项目ID
* @param threshold 年流水阈值
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalAnnualTurnoverObjects(@Param("projectId") Long projectId,
@Param("threshold") BigDecimal threshold);
/** /**
* 与客户之间非正常资金往来 * 与客户之间非正常资金往来
* *
@@ -156,26 +126,6 @@ public interface CcdiBankTagAnalysisMapper {
*/ */
List<BankTagStatementHitVO> selectGamblingSensitiveKeywordStatements(@Param("projectId") Long projectId); List<BankTagStatementHitVO> selectGamblingSensitiveKeywordStatements(@Param("projectId") Long projectId);
/**
* 外部人员疑似赌博摘要
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalGamblingMemoStatements(@Param("projectId") Long projectId);
/**
* 外部人员同日多对手方疑似赌博交易
*
* @param projectId 项目ID
* @param amountMinThreshold 可疑金额下限
* @param amountMaxThreshold 可疑金额上限
* @return 对象命中结果
*/
List<BankTagObjectHitVO> selectExternalMultiPartyGamblingTransferObjects(@Param("projectId") Long projectId,
@Param("amountMinThreshold") BigDecimal amountMinThreshold,
@Param("amountMaxThreshold") BigDecimal amountMaxThreshold);
/** /**
* 特殊金额交易 * 特殊金额交易
* *
@@ -184,22 +134,6 @@ public interface CcdiBankTagAnalysisMapper {
*/ */
List<BankTagStatementHitVO> selectSpecialAmountTransactionStatements(@Param("projectId") Long projectId); List<BankTagStatementHitVO> selectSpecialAmountTransactionStatements(@Param("projectId") Long projectId);
/**
* 外部人员与员工或员工亲属交易
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalToStaffOrFamilyTransactionStatements(@Param("projectId") Long projectId);
/**
* 外部人员夜间交易
*
* @param projectId 项目ID
* @return 流水命中结果
*/
List<BankTagStatementHitVO> selectExternalNightTransactionStatements(@Param("projectId") Long projectId);
/** /**
* 月度固定收入疑似兼职 * 月度固定收入疑似兼职
* *
@@ -338,11 +272,9 @@ public interface CcdiBankTagAnalysisMapper {
* 微信支付宝提现超额 * 微信支付宝提现超额
* *
* @param projectId 项目ID * @param projectId 项目ID
* @param amountThreshold 提现金额阈值
* @return 对象命中结果 * @return 对象命中结果
*/ */
List<BankTagObjectHitVO> selectWithdrawAmtObjects(@Param("projectId") Long projectId, List<BankTagObjectHitVO> selectWithdrawAmtObjects(@Param("projectId") Long projectId);
@Param("amountThreshold") BigDecimal amountThreshold);
/** /**
* 工资快速转出 * 工资快速转出

View File

@@ -32,12 +32,4 @@ public interface CcdiBankTagTaskMapper extends BaseMapper<CcdiBankTagTask> {
* @return 任务实体 * @return 任务实体
*/ */
CcdiBankTagTask selectRunningTaskByProjectId(@Param("projectId") Long projectId); CcdiBankTagTask selectRunningTaskByProjectId(@Param("projectId") Long projectId);
/**
* 查询项目最近一次失败任务
*
* @param projectId 项目ID
* @return 任务实体
*/
CcdiBankTagTask selectLatestFailedTaskByProjectId(@Param("projectId") Long projectId);
} }

View File

@@ -1,49 +0,0 @@
package com.ruoyi.ccdi.project.mapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphEdgeDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphManualEdgeSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 资金流图谱Mapper
*/
@Mapper
public interface CcdiFundGraphMapper {
List<CcdiFundGraphNodeVO> selectFundGraphSubjects(@Param("query") CcdiFundGraphQueryDTO query);
List<CcdiFundGraphNodeVO> selectFundGraphSubjectsByExactKeyword(@Param("query") CcdiFundGraphQueryDTO query);
List<CcdiFundGraphNodeVO> selectFundGraphSubjectsByName(@Param("query") CcdiFundGraphQueryDTO query);
List<CcdiFundGraphEdgeVO> selectFundGraphEdges(@Param("query") CcdiFundGraphQueryDTO query);
List<CcdiFundGraphEdgeVO> selectFundGraphManualEdges(@Param("query") CcdiFundGraphQueryDTO query);
int countSubjectByObjectKey(@Param("objectKey") String objectKey);
int insertManualSubject(
@Param("objectKey") String objectKey,
@Param("idNo") String idNo,
@Param("name") String name
);
int insertManualEdge(
@Param("objectKey") String objectKey,
@Param("dto") CcdiFundGraphManualEdgeSaveDTO dto,
@Param("operator") String operator
);
Page<CcdiFundGraphStatementVO> selectFundGraphEdgeDetails(
Page<CcdiFundGraphStatementVO> page,
@Param("query") CcdiFundGraphEdgeDetailQueryDTO query
);
}

View File

@@ -3,7 +3,6 @@ package com.ruoyi.ccdi.project.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper; import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO;
@@ -26,9 +25,7 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @param queryDTO 查询条件 * @param queryDTO 查询条件
* @return 分页结果 * @return 分页结果
*/ */
Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, @Param("queryDTO") CcdiProjectQueryDTO queryDTO);
@Param("queryDTO") CcdiProjectQueryDTO queryDTO,
@Param("scope") ProjectAccessScope scope);
/** /**
* 查询历史项目列表 * 查询历史项目列表
@@ -36,14 +33,12 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @param queryDTO 查询条件 * @param queryDTO 查询条件
* @return 历史项目列表 * @return 历史项目列表
*/ */
List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO, List<CcdiProjectHistoryListItemVO> selectHistoryProjects(@Param("queryDTO") CcdiProjectQueryDTO queryDTO);
@Param("scope") ProjectAccessScope scope);
/** /**
* 更新项目总人数与风险人数 * 更新项目风险人数
* *
* @param projectId 项目ID * @param projectId 项目ID
* @param targetCount 总人数
* @param highRiskCount 高风险人数 * @param highRiskCount 高风险人数
* @param mediumRiskCount 中风险人数 * @param mediumRiskCount 中风险人数
* @param lowRiskCount 低风险人数 * @param lowRiskCount 低风险人数
@@ -51,7 +46,6 @@ public interface CcdiProjectMapper extends BaseMapper<CcdiProject> {
* @return 更新行数 * @return 更新行数
*/ */
int updateRiskCountsByProjectId(@Param("projectId") Long projectId, int updateRiskCountsByProjectId(@Param("projectId") Long projectId,
@Param("targetCount") Integer targetCount,
@Param("highRiskCount") Integer highRiskCount, @Param("highRiskCount") Integer highRiskCount,
@Param("mediumRiskCount") Integer mediumRiskCount, @Param("mediumRiskCount") Integer mediumRiskCount,
@Param("lowRiskCount") Integer lowRiskCount, @Param("lowRiskCount") Integer lowRiskCount,

View File

@@ -4,8 +4,6 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
@@ -13,8 +11,6 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO; import com.ruoyi.ccdi.project.domain.vo.CcdiBankStatementListVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativeItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
@@ -125,88 +121,6 @@ public interface CcdiProjectOverviewMapper {
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query @Param("query") CcdiProjectRiskModelPeopleQueryDTO query
); );
/**
* 查询风险模型命中人员导出列表
*
* @param query 查询条件
* @return 命中人员列表
*/
List<CcdiProjectRiskModelPeopleItemVO> selectRiskModelPeopleList(
@Param("query") CcdiProjectRiskModelPeopleQueryDTO query
);
/**
* 分页查询外部人员预警
*
* @param page 分页参数
* @param query 查询条件
* @return 外部人员预警分页
*/
Page<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningPage(
Page<CcdiProjectExternalPersonWarningItemVO> page,
@Param("query") CcdiProjectExternalPersonQueryDTO query
);
/**
* 查询外部人员预警导出列表
*
* @param projectId 项目ID
* @return 外部人员预警列表
*/
List<CcdiProjectExternalPersonWarningItemVO> selectExternalPersonWarningList(@Param("projectId") Long projectId);
/**
* 查询外部人员风险等级汇总
*
* @param projectId 项目ID
* @return 外部人员风险等级汇总
*/
CcdiProjectExternalRiskSummaryVO selectExternalRiskSummaryByProjectId(@Param("projectId") Long projectId);
/**
* 查询外部人员预警模型卡片
*
* @param projectId 项目ID
* @return 模型卡片
*/
List<CcdiProjectRiskModelCardVO> selectExternalRiskModelCardsByProjectId(@Param("projectId") Long projectId);
/**
* 分页查询外部人员模型命中人员
*
* @param page 分页参数
* @param query 查询条件
* @return 命中人员分页
*/
Page<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeoplePage(
Page<CcdiProjectRiskModelPeopleItemVO> page,
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
);
/**
* 查询外部人员模型命中人员导出列表
*
* @param query 查询条件
* @return 命中人员列表
*/
List<CcdiProjectRiskModelPeopleItemVO> selectExternalRiskModelPeopleList(
@Param("query") CcdiProjectExternalRiskModelPeopleQueryDTO query
);
/**
* 查询外部人员命中标签
*
* @param projectId 项目ID
* @param certNo 外部人员证件号
* @param selectedModelCodes 已选模型编码CSV可为空
* @return 命中标签列表
*/
List<CcdiProjectRiskHitTagVO> selectExternalRiskHitTagsByScope(
@Param("projectId") Long projectId,
@Param("certNo") String certNo,
@Param("selectedModelCodes") String selectedModelCodes
);
/** /**
* 分页查询涉疑交易明细 * 分页查询涉疑交易明细
* *
@@ -326,5 +240,4 @@ public interface CcdiProjectOverviewMapper {
* @return 风险人数汇总 * @return 风险人数汇总
*/ */
Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId); Map<String, Object> selectRiskCountSummaryByProjectId(@Param("projectId") Long projectId);
} }

View File

@@ -1,28 +0,0 @@
package com.ruoyi.ccdi.project.mapper;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseItemVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 关系图谱Mapper
*/
@Mapper
public interface CcdiRelationGraphMapper {
List<CcdiRelationGraphNodeVO> selectRelationGraphSubjects(@Param("query") CcdiRelationGraphQueryDTO query);
List<CcdiRelationGraphNodeVO> selectRelationGraphNodesByKeys(@Param("objectKeys") List<String> objectKeys);
List<CcdiRelationGraphEdgeVO> selectRelationGraphEdges(@Param("query") CcdiRelationGraphQueryDTO query);
int countSuspectedEnterpriseKeyNos(@Param("personName") String personName);
List<CcdiRelationGraphSuspectedEnterpriseItemVO> selectSuspectedEnterprises(@Param("personName") String personName,
@Param("limit") Integer limit);
}

View File

@@ -1,173 +0,0 @@
package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
import com.ruoyi.ccdi.project.domain.entity.CcdiBankStatement;
import com.ruoyi.ccdi.project.domain.entity.CcdiEvidence;
import com.ruoyi.ccdi.project.domain.entity.CcdiFileUploadRecord;
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
import com.ruoyi.ccdi.project.mapper.CcdiEvidenceMapper;
import com.ruoyi.ccdi.project.mapper.CcdiFileUploadRecordMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.common.core.domain.entity.SysRole;
import com.ruoyi.common.core.domain.model.LoginUser;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import java.util.List;
import java.util.Objects;
/**
* 项目访问控制。
*/
@Service
public class CcdiProjectAccessService {
private static final String ROLE_ADMIN = "admin";
private static final String ROLE_MANAGER = "manager";
@Resource
private CcdiProjectMapper projectMapper;
@Resource
private CcdiBankStatementMapper bankStatementMapper;
@Resource
private CcdiFileUploadRecordMapper fileUploadRecordMapper;
@Resource
private CcdiEvidenceMapper evidenceMapper;
public ProjectAccessScope buildCurrentScope() {
LoginUser loginUser = SecurityUtils.getLoginUser();
String username = SecurityUtils.getUsername();
boolean superAdmin = isSuperAdmin(loginUser);
boolean projectManager = hasRole(loginUser, ROLE_MANAGER);
return new ProjectAccessScope(username, superAdmin || projectManager, superAdmin, projectManager);
}
public boolean canOperate(CcdiProject project) {
if (project == null) {
return false;
}
ProjectAccessScope scope = buildCurrentScope();
return scope.isSuperAdmin() || Objects.equals(scope.getUsername(), project.getCreateBy());
}
public void assertCanRead(Long projectId) {
CcdiProject project = getRequiredProject(projectId);
ProjectAccessScope scope = buildCurrentScope();
if (scope.isViewAllProjects() || Objects.equals(scope.getUsername(), project.getCreateBy())) {
return;
}
throw new ServiceException("无权查看该项目");
}
public void assertCanOperate(Long projectId) {
CcdiProject project = getRequiredProject(projectId);
if (canOperate(project)) {
return;
}
throw new ServiceException("无权操作该项目");
}
public void assertCanReadByBankStatementId(Long bankStatementId) {
CcdiBankStatement statement = getRequiredBankStatement(bankStatementId);
assertCanRead(statement.getProjectId());
}
public void assertCanReadByFileRecordId(Long fileRecordId) {
CcdiFileUploadRecord record = getRequiredFileRecord(fileRecordId);
assertCanRead(record.getProjectId());
}
public void assertCanOperateByFileRecordId(Long fileRecordId) {
CcdiFileUploadRecord record = getRequiredFileRecord(fileRecordId);
assertCanOperate(record.getProjectId());
}
public void assertCanReadByEvidenceId(Long evidenceId) {
CcdiEvidence evidence = getRequiredEvidence(evidenceId);
assertCanRead(evidence.getProjectId());
}
public void assertSourceProjectsReadable(List<Long> sourceProjectIds) {
if (CollectionUtils.isEmpty(sourceProjectIds)) {
return;
}
for (Long sourceProjectId : sourceProjectIds) {
assertCanRead(sourceProjectId);
}
}
private CcdiProject getRequiredProject(Long projectId) {
if (projectId == null) {
throw new ServiceException("项目ID不能为空");
}
CcdiProject project = projectMapper.selectById(projectId);
if (project == null) {
throw new ServiceException("项目不存在");
}
return project;
}
private CcdiBankStatement getRequiredBankStatement(Long bankStatementId) {
if (bankStatementId == null) {
throw new ServiceException("流水ID不能为空");
}
CcdiBankStatement statement = bankStatementMapper.selectById(bankStatementId);
if (statement == null) {
throw new ServiceException("流水记录不存在");
}
return statement;
}
private CcdiFileUploadRecord getRequiredFileRecord(Long fileRecordId) {
if (fileRecordId == null) {
throw new ServiceException("文件记录ID不能为空");
}
CcdiFileUploadRecord record = fileUploadRecordMapper.selectById(fileRecordId);
if (record == null) {
throw new ServiceException("文件记录不存在");
}
return record;
}
private CcdiEvidence getRequiredEvidence(Long evidenceId) {
if (evidenceId == null) {
throw new ServiceException("证据ID不能为空");
}
CcdiEvidence evidence = evidenceMapper.selectById(evidenceId);
if (evidence == null) {
throw new ServiceException("证据不存在");
}
return evidence;
}
private boolean isSuperAdmin(LoginUser loginUser) {
if (loginUser == null) {
return false;
}
if (SecurityUtils.isAdmin(loginUser.getUserId())) {
return true;
}
return hasRole(loginUser, ROLE_ADMIN);
}
private boolean hasRole(LoginUser loginUser, String roleKey) {
if (loginUser == null || loginUser.getUser() == null
|| CollectionUtils.isEmpty(loginUser.getUser().getRoles())) {
return false;
}
for (SysRole role : loginUser.getUser().getRoles()) {
if (roleKey.equals(role.getRoleKey())) {
return true;
}
}
return false;
}
}

View File

@@ -1,29 +0,0 @@
package com.ruoyi.ccdi.project.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphEdgeDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphManualEdgeSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphVO;
import java.util.List;
/**
* 资金流图谱Service接口
*/
public interface ICcdiFundGraphService {
List<CcdiFundGraphNodeVO> searchSubjects(CcdiFundGraphQueryDTO queryDTO);
CcdiFundGraphVO getFundGraph(CcdiFundGraphQueryDTO queryDTO);
Page<CcdiFundGraphStatementVO> getEdgeDetails(
Page<CcdiFundGraphStatementVO> page,
CcdiFundGraphEdgeDetailQueryDTO queryDTO
);
CcdiFundGraphEdgeVO saveManualEdge(CcdiFundGraphManualEdgeSaveDTO saveDTO, String operator);
}

View File

@@ -2,22 +2,16 @@ package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeCreditNegativePageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisDetailVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
@@ -88,80 +82,6 @@ public interface ICcdiProjectOverviewService {
return new CcdiProjectRiskModelPeopleVO(); return new CcdiProjectRiskModelPeopleVO();
} }
/**
* 导出风险模型命中人员
*
* @param queryDTO 查询条件
* @return 导出列表
*/
default List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
return List.of();
}
/**
* 查询外部人员预警
*
* @param queryDTO 查询条件
* @return 外部人员预警
*/
default CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
return new CcdiProjectExternalPersonWarningVO();
}
/**
* 查询外部人员风险等级汇总
*
* @param projectId 项目ID
* @return 外部人员风险等级汇总
*/
default CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
return new CcdiProjectExternalRiskSummaryVO();
}
/**
* 导出外部人员预警
*
* @param projectId 项目ID
* @return 导出列表
*/
default List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
return List.of();
}
/**
* 查询外部人员风险模型卡片
*
* @param projectId 项目ID
* @return 风险模型卡片
*/
default CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
return new CcdiProjectRiskModelCardsVO();
}
/**
* 查询外部人员风险模型命中人员
*
* @param queryDTO 查询条件
* @return 命中人员
*/
default CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
return new CcdiProjectRiskModelPeopleVO();
}
/**
* 导出外部人员风险模型命中人员
*
* @param queryDTO 查询条件
* @return 导出列表
*/
default List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
return List.of();
}
/** /**
* 查询涉疑交易明细 * 查询涉疑交易明细
* *

View File

@@ -1,21 +0,0 @@
package com.ruoyi.ccdi.project.service;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphSuspectedEnterpriseQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphVO;
import java.util.List;
/**
* 关系图谱Service接口
*/
public interface ICcdiRelationGraphService {
List<CcdiRelationGraphNodeVO> searchSubjects(CcdiRelationGraphQueryDTO queryDTO);
CcdiRelationGraphVO getRelationGraph(CcdiRelationGraphQueryDTO queryDTO);
CcdiRelationGraphSuspectedEnterpriseVO getSuspectedEnterprises(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO);
}

View File

@@ -27,32 +27,20 @@ public class BankTagRuleConfigResolver {
private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries( private static final Map<String, Set<String>> RULE_PARAM_MAPPING = Map.ofEntries(
Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")), Map.entry("SINGLE_LARGE_INCOME", Set.of("SINGLE_TRANSACTION_AMOUNT")),
Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")), Map.entry("CUMULATIVE_INCOME", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
Map.entry("EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", Set.of("CUMULATIVE_TRANSACTION_AMOUNT")),
Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")), Map.entry("ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
Map.entry("EXTERNAL_ANNUAL_TURNOVER", Set.of("ANNUAL_TURNOVER")),
Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")), Map.entry("LARGE_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT")),
Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")), Map.entry("FREQUENT_CASH_DEPOSIT", Set.of("LARGE_CASH_DEPOSIT", "FREQUENT_CASH_DEPOSIT")),
Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")), Map.entry("LARGE_TRANSFER", Set.of("FREQUENT_TRANSFER")),
Map.entry("EXTERNAL_SINGLE_LARGE_AMOUNT", Set.of("FREQUENT_TRANSFER")),
Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")), Map.entry("FOREX_BUY_AMT", Set.of("SINGLE_PURCHASE_AMOUNT")),
Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")), Map.entry("FOREX_SELL_AMT", Set.of("SINGLE_SETTLEMENT_AMOUNT")),
Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")), Map.entry("WITHDRAW_CNT", Set.of("WITHDRAW_CNT")),
Map.entry("WITHDRAW_AMT", Set.of("WITHDRAW_AMT")),
Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")), Map.entry("STOCK_TFR_LARGE", Set.of("STOCK_TFR_LARGE")),
Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")), Map.entry("LARGE_STOCK_TRADING", Set.of("STOCK_TFR_LARGE")),
Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")), Map.entry("MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
Map.entry("EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", Set.of("MULTI_PARTY_AMT_MIN", "MULTI_PARTY_AMT_MAX")),
Map.entry("MONTHLY_FIXED_INCOME", Set.of("MONTHLY_FIXED_INCOME")), Map.entry("MONTHLY_FIXED_INCOME", Set.of("MONTHLY_FIXED_INCOME")),
Map.entry("FIXED_COUNTERPARTY_TRANSFER", Set.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "FIXED_COUNTERPARTY_TRANSFER_MAX")) Map.entry("FIXED_COUNTERPARTY_TRANSFER", Set.of("FIXED_COUNTERPARTY_TRANSFER_MIN", "FIXED_COUNTERPARTY_TRANSFER_MAX"))
); );
private static final Map<String, String> RULE_PARAM_MODEL_MAPPING = Map.of(
"EXTERNAL_SINGLE_LARGE_AMOUNT", "LARGE_TRANSACTION",
"EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", "LARGE_TRANSACTION",
"EXTERNAL_ANNUAL_TURNOVER", "LARGE_TRANSACTION",
"EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", "SUSPICIOUS_GAMBLING"
);
@Resource @Resource
private CcdiProjectMapper projectMapper; private CcdiProjectMapper projectMapper;
@@ -80,13 +68,12 @@ public class BankTagRuleConfigResolver {
} }
Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : projectId; Long effectiveProjectId = "default".equals(project.getConfigType()) ? 0L : projectId;
String paramModelCode = RULE_PARAM_MODEL_MAPPING.getOrDefault(ruleMeta.getRuleCode(), ruleMeta.getModelCode()); List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, ruleMeta.getModelCode());
List<CcdiModelParam> params = modelParamMapper.selectByProjectAndModel(effectiveProjectId, paramModelCode);
Map<String, String> thresholdValues = new LinkedHashMap<>(); Map<String, String> thresholdValues = new LinkedHashMap<>();
Set<String> requiredParamCodes = RULE_PARAM_MAPPING.getOrDefault(ruleMeta.getRuleCode(), Set.of()); Set<String> requiredParamCodes = RULE_PARAM_MAPPING.getOrDefault(ruleMeta.getRuleCode(), Set.of());
log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, paramModelCode={}, requiredParams={}", log.info("【流水标签】解析规则参数: projectId={}, effectiveProjectId={}, ruleCode={}, requiredParams={}",
projectId, effectiveProjectId, ruleMeta.getRuleCode(), paramModelCode, requiredParamCodes); projectId, effectiveProjectId, ruleMeta.getRuleCode(), requiredParamCodes);
for (CcdiModelParam param : params) { for (CcdiModelParam param : params) {
if (requiredParamCodes.contains(param.getParamCode())) { if (requiredParamCodes.contains(param.getParamCode())) {
thresholdValues.put(param.getParamCode(), param.getParamValue()); thresholdValues.put(param.getParamCode(), param.getParamValue());

View File

@@ -153,7 +153,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
task.setUpdateBy(operator); task.setUpdateBy(operator);
task.setUpdateTime(new Date()); task.setUpdateTime(new Date());
updateFailedTaskSafely(task, ex); updateFailedTaskSafely(task, ex);
projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.TAG_FAILED, operator); projectService.updateProjectStatus(projectId, CcdiProjectStatusConstants.PROCESSING, operator);
log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}", log.error("【流水标签】任务执行失败: taskId={}, projectId={}, modelCode={}, triggerType={}, error={}",
task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex); task.getId(), projectId, modelCode, triggerType, ex.getMessage(), ex);
throw ex; throw ex;
@@ -225,15 +225,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements( case "LARGE_TRANSFER" -> analysisMapper.selectLargeTransferStatements(
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER")) projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
); );
case "EXTERNAL_SINGLE_LARGE_AMOUNT" -> analysisMapper.selectExternalSingleLargeAmountStatements(
projectId, toBigDecimal(config.getThresholdValue("FREQUENT_TRANSFER"))
);
case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId); case "ABNORMAL_CUSTOMER_TRANSACTION" -> analysisMapper.selectAbnormalCustomerTransactionStatements(projectId);
case "EXTERNAL_NIGHT_TRANSACTION" -> analysisMapper.selectExternalNightTransactionStatements(projectId);
case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId); case "GAMBLING_SENSITIVE_KEYWORD" -> analysisMapper.selectGamblingSensitiveKeywordStatements(projectId);
case "EXTERNAL_GAMBLING_MEMO" -> analysisMapper.selectExternalGamblingMemoStatements(projectId);
case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId); case "SPECIAL_AMOUNT_TRANSACTION" -> analysisMapper.selectSpecialAmountTransactionStatements(projectId);
case "EXTERNAL_TO_STAFF_FAMILY_TRANSACTION" -> analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(projectId);
case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId); case "SUSPICIOUS_INCOME_KEYWORD" -> analysisMapper.selectSuspiciousIncomeKeywordStatements(projectId);
case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId); case "HOUSE_REGISTRATION_MISMATCH" -> analysisMapper.selectHouseRegistrationMismatchStatements(projectId);
case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId); case "PROPERTY_FEE_REGISTRATION_MISMATCH" -> analysisMapper.selectPropertyFeeRegistrationMismatchStatements(projectId);
@@ -264,15 +258,9 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects( case "CUMULATIVE_INCOME" -> analysisMapper.selectCumulativeIncomeObjects(
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT")) projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
); );
case "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT" -> analysisMapper.selectExternalCumulativeTransactionAmountObjects(
projectId, toBigDecimal(config.getThresholdValue("CUMULATIVE_TRANSACTION_AMOUNT"))
);
case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects( case "ANNUAL_TURNOVER" -> analysisMapper.selectAnnualTurnoverObjects(
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER")) projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
); );
case "EXTERNAL_ANNUAL_TURNOVER" -> analysisMapper.selectExternalAnnualTurnoverObjects(
projectId, toBigDecimal(config.getThresholdValue("ANNUAL_TURNOVER"))
);
case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects( case "FREQUENT_CASH_DEPOSIT" -> analysisMapper.selectFrequentCashDepositObjects(
projectId, projectId,
toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")), toBigDecimal(config.getThresholdValue("LARGE_CASH_DEPOSIT")),
@@ -284,11 +272,6 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")), toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX")) toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
); );
case "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER" -> analysisMapper.selectExternalMultiPartyGamblingTransferObjects(
projectId,
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MIN")),
toBigDecimal(config.getThresholdValue("MULTI_PARTY_AMT_MAX"))
);
case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects( case "MONTHLY_FIXED_INCOME" -> analysisMapper.selectMonthlyFixedIncomeObjects(
projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME")) projectId, toBigDecimal(config.getThresholdValue("MONTHLY_FIXED_INCOME"))
); );
@@ -302,9 +285,7 @@ public class CcdiBankTagServiceImpl implements ICcdiBankTagService {
case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects( case "WITHDRAW_CNT" -> analysisMapper.selectWithdrawCntObjects(
projectId, toInteger(config.getThresholdValue("WITHDRAW_CNT")) projectId, toInteger(config.getThresholdValue("WITHDRAW_CNT"))
); );
case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects( case "WITHDRAW_AMT" -> analysisMapper.selectWithdrawAmtObjects(projectId);
projectId, toBigDecimal(config.getThresholdValue("WITHDRAW_AMT"))
);
case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId); case "SALARY_QUICK_TRANSFER" -> analysisMapper.selectSalaryQuickTransferObjects(projectId);
case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId); case "SALARY_UNUSED" -> analysisMapper.selectSalaryUnusedObjects(projectId);
case "SUDDEN_ACCOUNT_CLOSURE" -> analysisMapper.selectSuddenAccountClosureObjects(projectId); case "SUDDEN_ACCOUNT_CLOSURE" -> analysisMapper.selectSuddenAccountClosureObjects(projectId);

View File

@@ -226,18 +226,15 @@ public class CcdiFileUploadServiceImpl implements ICcdiFileUploadService {
CcdiFileUploadRecord record = recordMapper.selectById(id); CcdiFileUploadRecord record = recordMapper.selectById(id);
validateDeleteRecord(record); validateDeleteRecord(record);
/* DeleteFilesRequest request = new DeleteFilesRequest();
* 按当前要求,项目管理-上传数据页面删除时先不调用流水分析平台删除接口。 request.setGroupId(record.getLsfxProjectId());
* DeleteFilesRequest request = new DeleteFilesRequest(); request.setLogIds(new Integer[]{record.getLogId()});
* request.setGroupId(record.getLsfxProjectId()); request.setUserId(toUploadUserId(operatorUserId));
* request.setLogIds(new Integer[]{record.getLogId()});
* request.setUserId(toUploadUserId(operatorUserId)); DeleteFilesResponse response = lsfxClient.deleteFiles(request);
* if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) {
* DeleteFilesResponse response = lsfxClient.deleteFiles(request); throw new RuntimeException("流水分析平台删除文件失败");
* if (response == null || Boolean.FALSE.equals(response.getSuccessResponse())) { }
* throw new RuntimeException("流水分析平台删除文件失败");
* }
*/
bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId()); bankStatementMapper.deleteByProjectIdAndBatchId(record.getProjectId(), record.getLogId());
refreshProjectTargetCount(record.getProjectId()); refreshProjectTargetCount(record.getProjectId());

View File

@@ -1,383 +0,0 @@
package com.ruoyi.ccdi.project.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphEdgeDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphManualEdgeSaveDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiFundGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphVO;
import com.ruoyi.ccdi.project.mapper.CcdiFundGraphMapper;
import com.ruoyi.ccdi.project.service.ICcdiFundGraphService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import org.springframework.util.DigestUtils;
import java.math.BigDecimal;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
/**
* 资金流图谱Service实现
*/
@Service
public class CcdiFundGraphServiceImpl implements ICcdiFundGraphService {
private static final int DEFAULT_LIMIT = 20;
private static final int MAX_LIMIT = 100;
private static final BigDecimal DEFAULT_MIN_TOTAL_AMOUNT = new BigDecimal("1000");
private static final Comparator<CcdiFundGraphEdgeVO> EDGE_COMPARATOR = Comparator
.comparing(CcdiFundGraphServiceImpl::safeAmount, Comparator.reverseOrder())
.thenComparing(CcdiFundGraphServiceImpl::safeTransactionCount, Comparator.reverseOrder())
.thenComparing(CcdiFundGraphServiceImpl::safeDateText, Comparator.reverseOrder())
.thenComparing(edge -> normalizeSortText(edge == null ? null : edge.getEdgeKey()));
@Resource
private CcdiFundGraphMapper fundGraphMapper;
@Override
public List<CcdiFundGraphNodeVO> searchSubjects(CcdiFundGraphQueryDTO queryDTO) {
CcdiFundGraphQueryDTO query = normalizeGraphQuery(queryDTO);
if (isBlank(query.getKeyword()) && isBlank(query.getObjectKey())) {
return Collections.emptyList();
}
return selectSubjects(query);
}
@Override
public CcdiFundGraphVO getFundGraph(CcdiFundGraphQueryDTO queryDTO) {
CcdiFundGraphQueryDTO query = normalizeGraphQuery(queryDTO);
CcdiFundGraphNodeVO centerNode = resolveCenterNode(query);
if (centerNode == null || isBlank(centerNode.getObjectKey())) {
return new CcdiFundGraphVO();
}
query.setObjectKey(centerNode.getObjectKey());
List<CcdiFundGraphEdgeVO> edges = new ArrayList<>();
List<CcdiFundGraphEdgeVO> realEdges = fundGraphMapper.selectFundGraphEdges(query);
if (realEdges != null) {
edges.addAll(realEdges);
}
List<CcdiFundGraphEdgeVO> manualEdges = fundGraphMapper.selectFundGraphManualEdges(query);
if (manualEdges != null) {
edges.addAll(manualEdges);
}
edges = sortAndLimitEdges(edges, query.getLimit());
CcdiFundGraphVO graph = new CcdiFundGraphVO();
graph.setCenterNode(centerNode);
graph.setEdges(edges);
graph.setNodes(buildNodes(centerNode, edges));
graph.setTransactionCount(edges.stream()
.map(CcdiFundGraphEdgeVO::getTransactionCount)
.filter(item -> item != null)
.reduce(0L, Long::sum));
graph.setTotalAmount(edges.stream()
.map(CcdiFundGraphEdgeVO::getTotalAmount)
.filter(item -> item != null)
.reduce(BigDecimal.ZERO, BigDecimal::add));
graph.setMaxDepth(1);
graph.setTraceReserved(true);
return graph;
}
@Override
public Page<CcdiFundGraphStatementVO> getEdgeDetails(
Page<CcdiFundGraphStatementVO> page,
CcdiFundGraphEdgeDetailQueryDTO queryDTO
) {
CcdiFundGraphEdgeDetailQueryDTO query = normalizeDetailQuery(queryDTO);
if (isBlank(query.getFromKey()) || isBlank(query.getToKey())) {
return page;
}
return fundGraphMapper.selectFundGraphEdgeDetails(page, query);
}
@Override
public CcdiFundGraphEdgeVO saveManualEdge(CcdiFundGraphManualEdgeSaveDTO saveDTO, String operator) {
CcdiFundGraphManualEdgeSaveDTO dto = normalizeManualEdge(saveDTO);
String fromObjectKey = dto.getFromObjectKey();
String toObjectKey = resolveManualToObjectKey(dto);
dto.setToObjectKey(toObjectKey);
String edgeObjectKey = md5("MANUAL_EDGE|" + fromObjectKey + "|" + toObjectKey + "|" + dto.getDirection()
+ "|" + UUID.randomUUID());
fundGraphMapper.insertManualEdge(edgeObjectKey, dto, normalizeText(operator));
CcdiFundGraphEdgeVO edge = new CcdiFundGraphEdgeVO();
edge.setEdgeKey(edgeObjectKey);
edge.setFromKey(toSubjectKey(fromObjectKey));
edge.setToKey(toSubjectKey(toObjectKey));
edge.setFromObjectKey(fromObjectKey);
edge.setToObjectKey(toObjectKey);
edge.setFromName(dto.getFromName());
edge.setToName(dto.getToName());
edge.setTotalAmount(dto.getAmount());
edge.setTransactionCount(dto.getTransactionCount() == null ? 1L : dto.getTransactionCount().longValue());
edge.setDirection(dto.getDirection());
edge.setRelationDesc(dto.getRelationDesc());
edge.setSourceDesc(dto.getSourceDesc());
edge.setRemark(dto.getRemark());
edge.setSourceType("MANUAL");
edge.setDepth(1);
edge.setCanTrace(false);
return edge;
}
private CcdiFundGraphNodeVO resolveCenterNode(CcdiFundGraphQueryDTO query) {
if (isBlank(query.getObjectKey()) && isBlank(query.getKeyword())) {
return null;
}
List<CcdiFundGraphNodeVO> subjects = selectSubjects(query);
if (subjects == null || subjects.isEmpty()) {
return null;
}
return subjects.get(0);
}
private List<CcdiFundGraphNodeVO> selectSubjects(CcdiFundGraphQueryDTO query) {
if (query == null) {
return Collections.emptyList();
}
if (!isBlank(query.getObjectKey())) {
return fundGraphMapper.selectFundGraphSubjects(query);
}
if (isBlank(query.getKeyword())) {
return Collections.emptyList();
}
List<CcdiFundGraphNodeVO> exactSubjects = fundGraphMapper.selectFundGraphSubjectsByExactKeyword(query);
if (exactSubjects != null && !exactSubjects.isEmpty()) {
return exactSubjects;
}
return fundGraphMapper.selectFundGraphSubjectsByName(query);
}
private List<CcdiFundGraphNodeVO> buildNodes(CcdiFundGraphNodeVO centerNode, List<CcdiFundGraphEdgeVO> edges) {
Map<String, CcdiFundGraphNodeVO> nodeMap = new LinkedHashMap<>();
Map<String, CcdiFundGraphNodeVO> subjectCache = new LinkedHashMap<>();
subjectCache.put(centerNode.getObjectKey(), centerNode);
addNode(nodeMap, centerNode, centerNode.getNodeKey(), centerNode.getObjectKey(), centerNode.getNodeName(),
centerNode.getRelationType(), centerNode.getCanExpand(), BigDecimal.ZERO, 0L);
for (CcdiFundGraphEdgeVO edge : edges) {
String centerObjectKey = centerNode.getObjectKey();
String fromRelationType = centerObjectKey != null && centerObjectKey.equals(edge.getFromObjectKey())
? null
: edge.getFamilyRelationType();
String toRelationType = centerObjectKey != null && centerObjectKey.equals(edge.getToObjectKey())
? null
: edge.getFamilyRelationType();
addNode(nodeMap, lookupSubject(edge.getFromObjectKey(), subjectCache), edge.getFromKey(),
edge.getFromObjectKey(), edge.getFromName(), fromRelationType, true, edge.getTotalAmount(),
edge.getTransactionCount());
addNode(nodeMap, lookupSubject(edge.getToObjectKey(), subjectCache), edge.getToKey(),
edge.getToObjectKey(), edge.getToName(), toRelationType, edge.getCanTrace(),
edge.getTotalAmount(), edge.getTransactionCount());
}
return List.copyOf(nodeMap.values());
}
private CcdiFundGraphNodeVO lookupSubject(String objectKey, Map<String, CcdiFundGraphNodeVO> subjectCache) {
if (isBlank(objectKey)) {
return null;
}
if (subjectCache.containsKey(objectKey)) {
return subjectCache.get(objectKey);
}
CcdiFundGraphQueryDTO query = new CcdiFundGraphQueryDTO();
query.setObjectKey(objectKey);
query.setLimit(DEFAULT_LIMIT);
List<CcdiFundGraphNodeVO> subjects = fundGraphMapper.selectFundGraphSubjects(query);
CcdiFundGraphNodeVO subject = subjects == null || subjects.isEmpty() ? null : subjects.get(0);
subjectCache.put(objectKey, subject);
return subject;
}
private String resolveManualToObjectKey(CcdiFundGraphManualEdgeSaveDTO dto) {
if (!isBlank(dto.getToObjectKey())) {
ensureManualSubject(dto.getToObjectKey(), dto.getToIdNo(), dto.getToName());
return dto.getToObjectKey();
}
String objectKey = !isBlank(dto.getToIdNo())
? md5(dto.getToIdNo())
: md5("MANUAL_NODE|" + dto.getToName() + "|" + UUID.randomUUID());
dto.setToObjectKey(objectKey);
ensureManualSubject(objectKey, dto.getToIdNo(), dto.getToName());
return objectKey;
}
private void ensureManualSubject(String objectKey, String idNo, String name) {
if (fundGraphMapper.countSubjectByObjectKey(objectKey) > 0) {
return;
}
fundGraphMapper.insertManualSubject(objectKey, normalizeText(idNo), normalizeText(name));
}
private CcdiFundGraphManualEdgeSaveDTO normalizeManualEdge(CcdiFundGraphManualEdgeSaveDTO saveDTO) {
CcdiFundGraphManualEdgeSaveDTO dto = saveDTO == null ? new CcdiFundGraphManualEdgeSaveDTO() : saveDTO;
dto.setFromObjectKey(normalizeText(dto.getFromObjectKey()));
dto.setFromName(normalizeText(dto.getFromName()));
dto.setToObjectKey(normalizeText(dto.getToObjectKey()));
dto.setToName(normalizeText(dto.getToName()));
dto.setToIdNo(normalizeText(dto.getToIdNo()));
dto.setDirection(normalizeText(dto.getDirection()));
dto.setRelationDesc(normalizeText(dto.getRelationDesc()));
dto.setSourceDesc(normalizeText(dto.getSourceDesc()));
dto.setRemark(normalizeText(dto.getRemark()));
if (isBlank(dto.getFromObjectKey())) {
throw new IllegalArgumentException("起点主体不能为空");
}
if (isBlank(dto.getToObjectKey()) && isBlank(dto.getToName())) {
throw new IllegalArgumentException("终点主体不能为空");
}
if (isBlank(dto.getDirection())) {
dto.setDirection("1");
}
if (dto.getAmount() == null) {
dto.setAmount(BigDecimal.ZERO);
}
if (dto.getTransactionCount() == null || dto.getTransactionCount() <= 0) {
dto.setTransactionCount(1);
}
return dto;
}
private void addNode(
Map<String, CcdiFundGraphNodeVO> nodeMap,
CcdiFundGraphNodeVO subject,
String nodeKey,
String objectKey,
String nodeName,
String relationType,
Boolean canExpand,
BigDecimal edgeAmount,
Long edgeCount
) {
if (isBlank(nodeKey)) {
return;
}
CcdiFundGraphNodeVO node = nodeMap.computeIfAbsent(nodeKey, key -> {
CcdiFundGraphNodeVO item = new CcdiFundGraphNodeVO();
item.setNodeKey(key);
item.setObjectKey(objectKey);
item.setNodeName(subject != null && !isBlank(subject.getNodeName())
? subject.getNodeName()
: (isBlank(nodeName) ? "未知主体" : nodeName));
item.setIdNo(subject == null ? null : subject.getIdNo());
item.setCinocsno(subject == null ? null : subject.getCinocsno());
item.setIdnoType(subject == null ? null : subject.getIdnoType());
item.setStaffId(subject == null ? null : subject.getStaffId());
item.setSourceType(subject == null ? null : subject.getSourceType());
item.setNodeType(subject != null && !isBlank(subject.getNodeType()) ? subject.getNodeType() : "PERSON");
item.setIdentityType(subject != null && !isBlank(subject.getIdentityType()) ? subject.getIdentityType() : "IDNO");
item.setRelationType(relationType);
item.setAccountCount(subject == null ? 0L : subject.getAccountCount());
item.setCreatedTime(subject == null ? null : subject.getCreatedTime());
item.setUpdatedTime(subject == null ? null : subject.getUpdatedTime());
item.setCanExpand(Boolean.TRUE.equals(canExpand));
item.setDepth(1);
item.setTotalAmount(BigDecimal.ZERO);
item.setTransactionCount(0L);
return item;
});
if (isBlank(node.getObjectKey())) {
node.setObjectKey(objectKey);
}
if (isBlank(node.getRelationType())) {
node.setRelationType(relationType);
}
if (Boolean.TRUE.equals(canExpand)) {
node.setCanExpand(true);
}
node.setTotalAmount(node.getTotalAmount().add(edgeAmount == null ? BigDecimal.ZERO : edgeAmount));
node.setTransactionCount(node.getTransactionCount() + (edgeCount == null ? 0L : edgeCount));
}
private CcdiFundGraphQueryDTO normalizeGraphQuery(CcdiFundGraphQueryDTO queryDTO) {
CcdiFundGraphQueryDTO query = queryDTO == null ? new CcdiFundGraphQueryDTO() : queryDTO;
query.setKeyword(normalizeText(query.getKeyword()));
query.setObjectKey(normalizeText(query.getObjectKey()));
query.setTransactionStartTime(normalizeText(query.getTransactionStartTime()));
query.setTransactionEndTime(normalizeText(query.getTransactionEndTime()));
query.setDirection(normalizeText(query.getDirection()));
if (query.getMinTotalAmount() == null) {
query.setMinTotalAmount(DEFAULT_MIN_TOTAL_AMOUNT);
}
query.setLimit(normalizeLimit(query.getLimit()));
query.setDepth(1);
return query;
}
private CcdiFundGraphEdgeDetailQueryDTO normalizeDetailQuery(CcdiFundGraphEdgeDetailQueryDTO queryDTO) {
CcdiFundGraphEdgeDetailQueryDTO query = queryDTO == null ? new CcdiFundGraphEdgeDetailQueryDTO() : queryDTO;
query.setKeyword(normalizeText(query.getKeyword()));
query.setFromKey(normalizeText(query.getFromKey()));
query.setToKey(normalizeText(query.getToKey()));
query.setDirection(normalizeText(query.getDirection()));
query.setTransactionStartTime(normalizeText(query.getTransactionStartTime()));
query.setTransactionEndTime(normalizeText(query.getTransactionEndTime()));
return query;
}
private Integer normalizeLimit(Integer limit) {
if (limit == null || limit <= 0) {
return DEFAULT_LIMIT;
}
return Math.min(limit, MAX_LIMIT);
}
private List<CcdiFundGraphEdgeVO> sortAndLimitEdges(List<CcdiFundGraphEdgeVO> edges, Integer limit) {
if (edges == null || edges.isEmpty()) {
return Collections.emptyList();
}
List<CcdiFundGraphEdgeVO> sorted = new ArrayList<>(edges);
sorted.sort(EDGE_COMPARATOR);
int finalLimit = normalizeLimit(limit);
if (sorted.size() > finalLimit) {
return List.copyOf(sorted.subList(0, finalLimit));
}
return List.copyOf(sorted);
}
private String normalizeText(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
private String toSubjectKey(String objectKey) {
return "idno_node/" + objectKey;
}
private String md5(String value) {
return DigestUtils.md5DigestAsHex(value.trim().getBytes(StandardCharsets.UTF_8));
}
private static BigDecimal safeAmount(CcdiFundGraphEdgeVO edge) {
return edge == null || edge.getTotalAmount() == null ? BigDecimal.ZERO : edge.getTotalAmount();
}
private static Long safeTransactionCount(CcdiFundGraphEdgeVO edge) {
return edge == null || edge.getTransactionCount() == null ? 0L : edge.getTransactionCount();
}
private static String safeDateText(CcdiFundGraphEdgeVO edge) {
return normalizeSortText(edge == null ? null : edge.getLastTrxDate());
}
private static String normalizeSortText(String value) {
return value == null ? "" : value;
}
}

View File

@@ -23,7 +23,6 @@ import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import org.apache.commons.lang3.StringUtils; import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.springframework.context.annotation.Lazy;
import org.springframework.beans.BeanUtils; import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
@@ -54,7 +53,6 @@ public class CcdiModelParamServiceImpl implements ICcdiModelParamService {
private ICcdiProjectService projectService; private ICcdiProjectService projectService;
@Resource @Resource
@Lazy
private ICcdiBankTagService bankTagService; private ICcdiBankTagService bankTagService;
@Override @Override

View File

@@ -2,7 +2,6 @@ package com.ruoyi.ccdi.project.service.impl;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
@@ -66,7 +65,7 @@ public class CcdiProjectOverviewReportPdfExporter {
writeCover(writer, report); writeCover(writer, report);
writeUploadSubjects(writer, report.getUploadSubjects()); writeUploadSubjects(writer, report.getUploadSubjects());
writeParams(writer, report.getParams()); writeParams(writer, report.getParams());
writeRiskOverview(writer, report); writeRiskModels(writer, report);
writeRiskDetails(writer, report); writeRiskDetails(writer, report);
writer.close(); writer.close();
document.save(response.getOutputStream()); document.save(response.getOutputStream());
@@ -119,9 +118,9 @@ public class CcdiProjectOverviewReportPdfExporter {
); );
} }
private void writeRiskOverview(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException { private void writeRiskModels(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
writer.section("三、风险总览"); writer.section("三、风险模型");
writer.metrics(buildOverallRiskMetrics(report)); writer.metrics(report.getDashboard().getStats());
writer.subsection("风险模型汇总"); writer.subsection("风险模型汇总");
writer.table( writer.table(
List.of("模型名称", "预警数量", "涉及人员"), List.of("模型名称", "预警数量", "涉及人员"),
@@ -152,40 +151,6 @@ public class CcdiProjectOverviewReportPdfExporter {
new float[] { 0.1F, 0.11F, 0.16F, 0.14F, 0.24F, 0.25F }, new float[] { 0.1F, 0.11F, 0.16F, 0.14F, 0.24F, 0.25F },
"暂无风险人员与异常点数据" "暂无风险人员与异常点数据"
); );
if (hasExternalRisk(report)) {
writer.subsection("外部人员预警");
writer.metrics(buildExternalMetrics(report));
writer.table(
List.of("外部模型", "预警数量", "涉及人数"),
report.getExternalModelSummaries().stream()
.map(item -> List.of(
safeText(item.getModelName()),
String.valueOf(defaultZero(item.getWarningCount())),
formatCount(item.getPeopleCount(), "")
))
.collect(Collectors.toList()),
new float[] { 0.5F, 0.2F, 0.3F },
"暂无外部人员模型汇总数据"
);
writer.table(
List.of("姓名", "证件号", "主体类型", "风险等级", "命中模型数", "核心异常点", "涉及对象", "最近交易时间"),
report.getExternalPersonWarnings().stream()
.map(item -> List.of(
safeText(item.getName()),
maskIdCard(item.getIdNo()),
safeText(item.getSubjectType()),
safeText(item.getRiskLevel()),
String.valueOf(defaultZero(item.getModelCount())),
safeText(item.getRiskPoint()),
safeText(item.getRelatedObject()),
safeText(item.getLatestTradeTime())
))
.collect(Collectors.toList()),
new float[] { 0.09F, 0.15F, 0.1F, 0.09F, 0.1F, 0.25F, 0.12F, 0.1F },
"暂无外部人员预警数据"
);
}
} }
private void writeRiskDetails(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException { private void writeRiskDetails(PdfPageWriter writer, CcdiProjectOverviewReportVO report) throws IOException {
@@ -363,69 +328,6 @@ public class CcdiProjectOverviewReportPdfExporter {
return result; return result;
} }
private List<CcdiProjectOverviewStatVO> buildOverallRiskMetrics(CcdiProjectOverviewReportVO report) {
List<CcdiProjectOverviewStatVO> employeeStats = report.getDashboard().getStats();
if (employeeStats == null || employeeStats.isEmpty()) {
return List.of(
buildMetric("总人数", report.getExternalRiskSummary().getTotal()),
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
buildMetric("无风险", report.getExternalRiskSummary().getNoRisk())
);
}
int employeeTotal = metricValue(employeeStats, "people");
int high = metricValue(employeeStats, "riskPeople");
int medium = metricValue(employeeStats, "medium");
int low = metricValue(employeeStats, "low");
int noRisk = metricValue(employeeStats, "count");
int externalTotal = defaultZero(report.getExternalRiskSummary().getTotal());
int externalHigh = defaultZero(report.getExternalRiskSummary().getHigh());
int externalMedium = defaultZero(report.getExternalRiskSummary().getMedium());
int externalLow = defaultZero(report.getExternalRiskSummary().getLow());
int externalNoRisk = defaultZero(report.getExternalRiskSummary().getNoRisk());
return List.of(
buildMetric("总人数", employeeTotal + externalTotal),
buildMetric("高风险", high + externalHigh),
buildMetric("中风险", medium + externalMedium),
buildMetric("低风险", low + externalLow),
buildMetric("无风险", noRisk + externalNoRisk)
);
}
private Integer metricValue(List<CcdiProjectOverviewStatVO> stats, String key) {
return defaultZero(stats.stream()
.filter(stat -> key.equals(stat.getKey()))
.findFirst()
.map(CcdiProjectOverviewStatVO::getValue)
.orElse(0));
}
private boolean hasExternalRisk(CcdiProjectOverviewReportVO report) {
return defaultZero(report.getExternalRiskSummary().getHigh()) > 0
|| defaultZero(report.getExternalRiskSummary().getMedium()) > 0
|| defaultZero(report.getExternalRiskSummary().getLow()) > 0
|| !report.getExternalModelSummaries().isEmpty()
|| !report.getExternalPersonWarnings().isEmpty();
}
private List<CcdiProjectOverviewStatVO> buildExternalMetrics(CcdiProjectOverviewReportVO report) {
return List.of(
buildMetric("外部人员", report.getExternalRiskSummary().getTotal()),
buildMetric("高风险", report.getExternalRiskSummary().getHigh()),
buildMetric("中风险", report.getExternalRiskSummary().getMedium()),
buildMetric("低风险", report.getExternalRiskSummary().getLow()),
buildMetric("无风险人员", report.getExternalRiskSummary().getNoRisk())
);
}
private CcdiProjectOverviewStatVO buildMetric(String label, Integer value) {
CcdiProjectOverviewStatVO stat = new CcdiProjectOverviewStatVO();
stat.setLabel(label);
stat.setValue(defaultZero(value));
return stat;
}
private String formatPeopleSummary(CcdiProjectOverviewReportModelSummaryVO item) { private String formatPeopleSummary(CcdiProjectOverviewReportModelSummaryVO item) {
String names = safeText(item.getPeopleNames()); String names = safeText(item.getPeopleNames());
if ("-".equals(names)) { if ("-".equals(names)) {
@@ -567,7 +469,6 @@ public class CcdiProjectOverviewReportPdfExporter {
private static final float SUBSECTION_FONT_SIZE = 12F; private static final float SUBSECTION_FONT_SIZE = 12F;
private static final float LINE_HEIGHT = 12F; private static final float LINE_HEIGHT = 12F;
private static final float CELL_PADDING = 5F; private static final float CELL_PADDING = 5F;
private static final float TABLE_AFTER_GAP = 32F;
private final PDDocument document; private final PDDocument document;
private final PDType0Font font; private final PDType0Font font;
@@ -595,7 +496,7 @@ public class CcdiProjectOverviewReportPdfExporter {
} }
void title(String text) throws IOException { void title(String text) throws IOException {
writeLine(text, TITLE_FONT_SIZE, new Color(18, 56, 93), 0F, 28F, true); writeLine(text, TITLE_FONT_SIZE, new Color(18, 56, 93), 0F, 28F);
} }
void text(String text, float fontSize, Color color) throws IOException { void text(String text, float fontSize, Color color) throws IOException {
@@ -604,12 +505,12 @@ public class CcdiProjectOverviewReportPdfExporter {
void section(String text) throws IOException { void section(String text) throws IOException {
ensureSpace(32F); ensureSpace(32F);
writeLine(text, SECTION_FONT_SIZE, new Color(18, 56, 93), 0F, 26F, true); writeLine(text, SECTION_FONT_SIZE, new Color(18, 56, 93), 0F, 26F);
} }
void subsection(String text) throws IOException { void subsection(String text) throws IOException {
ensureSpace(26F); ensureSpace(26F);
writeLine(text, SUBSECTION_FONT_SIZE, new Color(51, 65, 85), 0F, 22F, true); writeLine(text, SUBSECTION_FONT_SIZE, new Color(51, 65, 85), 0F, 22F);
} }
void separator() throws IOException { void separator() throws IOException {
@@ -656,7 +557,7 @@ public class CcdiProjectOverviewReportPdfExporter {
for (List<String> row : safeRows) { for (List<String> row : safeRows) {
drawRow(row, widths, false); drawRow(row, widths, false);
} }
y -= TABLE_AFTER_GAP; y -= 8F;
} }
private float[] calculateWidths(float[] ratios) { private float[] calculateWidths(float[] ratios) {
@@ -731,17 +632,6 @@ public class CcdiProjectOverviewReportPdfExporter {
} }
private void writeLine(String text, float fontSize, Color color, float indent, float advance) throws IOException { private void writeLine(String text, float fontSize, Color color, float indent, float advance) throws IOException {
writeLine(text, fontSize, color, indent, advance, false);
}
private void writeLine(
String text,
float fontSize,
Color color,
float indent,
float advance,
boolean bold
) throws IOException {
ensureSpace(advance); ensureSpace(advance);
content.beginText(); content.beginText();
content.setNonStrokingColor(color); content.setNonStrokingColor(color);
@@ -749,14 +639,6 @@ public class CcdiProjectOverviewReportPdfExporter {
content.newLineAtOffset(MARGIN + indent, y); content.newLineAtOffset(MARGIN + indent, y);
content.showText(text); content.showText(text);
content.endText(); content.endText();
if (bold) {
content.beginText();
content.setNonStrokingColor(color);
content.setFont(font, fontSize);
content.newLineAtOffset(MARGIN + indent + 0.25F, y);
content.showText(text);
content.endText();
}
y -= advance; y -= advance;
} }

View File

@@ -5,16 +5,12 @@ import com.ruoyi.ccdi.project.domain.CcdiModelParam;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectAbnormalAccountQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectEmployeeCreditNegativeQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalPersonQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectExternalRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectPersonAnalysisDetailQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskModelPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectRiskPeopleQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSuspiciousTransactionQueryDTO;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectAbnormalAccountExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectEmployeeCreditNegativeExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectExternalPersonWarningExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskModelPeopleExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectRiskPeopleOverviewExcel;
import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel; import com.ruoyi.ccdi.project.domain.excel.CcdiProjectSuspiciousTransactionExcel;
import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult; import com.ruoyi.ccdi.project.domain.entity.CcdiProjectOverviewEmployeeResult;
@@ -30,21 +26,14 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisAbnormalGroupVO
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisBasicInfoVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectPersonAnalysisObjectRecordVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectEmployeeRiskAggregateVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewDashboardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportParamVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportSuspiciousTransactionVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewEmployeeHitRowVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewReportModelSummaryVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectOverviewStatVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskPeopleOverviewVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO;
@@ -52,7 +41,6 @@ import com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionPageVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectTopRiskPeopleVO;
import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper; import com.ruoyi.ccdi.project.mapper.CcdiModelParamMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankStatementMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper; import com.ruoyi.ccdi.project.mapper.CcdiBankTagResultMapper;
import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper; import com.ruoyi.ccdi.project.mapper.CcdiProjectOverviewEmployeeResultMapper;
@@ -86,9 +74,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
@Resource @Resource
private CcdiProjectMapper projectMapper; private CcdiProjectMapper projectMapper;
@Resource
private CcdiBankStatementMapper bankStatementMapper;
@Resource @Resource
private CcdiModelParamMapper modelParamMapper; private CcdiModelParamMapper modelParamMapper;
@@ -227,108 +212,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return people; return people;
} }
@Override
public List<CcdiProjectRiskModelPeopleExcel> exportRiskModelPeople(CcdiProjectRiskModelPeopleQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
normalizeRiskModelPeopleQuery(queryDTO);
return defaultList(overviewMapper.selectRiskModelPeopleList(queryDTO)).stream()
.map(item -> buildRiskModelPeopleExcelRow(item, "员工"))
.toList();
}
@Override
public CcdiProjectExternalPersonWarningVO getExternalPersonWarnings(CcdiProjectExternalPersonQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
Page<CcdiProjectExternalPersonWarningItemVO> page = new Page<>(
defaultRiskPeoplePageNum(queryDTO.getPageNum()),
defaultRiskPeoplePageSize(queryDTO.getPageSize())
);
Page<CcdiProjectExternalPersonWarningItemVO> resultPage =
overviewMapper.selectExternalPersonWarningPage(page, queryDTO);
List<CcdiProjectExternalPersonWarningItemVO> rows =
defaultList(resultPage == null ? null : resultPage.getRecords()).stream()
.peek(item -> item.setActionLabel(ACTION_LABEL))
.toList();
CcdiProjectExternalPersonWarningVO warnings = new CcdiProjectExternalPersonWarningVO();
warnings.setRows(rows);
warnings.setTotal(resultPage == null ? 0L : resultPage.getTotal());
warnings.setPageNum(page.getCurrent());
warnings.setPageSize(page.getSize());
return warnings;
}
@Override
public CcdiProjectExternalRiskSummaryVO getExternalRiskSummary(Long projectId) {
ensureProjectExists(projectId);
CcdiProjectExternalRiskSummaryVO summary = overviewMapper.selectExternalRiskSummaryByProjectId(projectId);
if (summary == null) {
return new CcdiProjectExternalRiskSummaryVO();
}
summary.setTotal(defaultZero(summary.getTotal()));
summary.setHigh(defaultZero(summary.getHigh()));
summary.setMedium(defaultZero(summary.getMedium()));
summary.setLow(defaultZero(summary.getLow()));
summary.setNoRisk(defaultZero(summary.getNoRisk()));
return summary;
}
@Override
public List<CcdiProjectExternalPersonWarningExcel> exportExternalPersonWarnings(Long projectId) {
ensureProjectExists(projectId);
return defaultList(overviewMapper.selectExternalPersonWarningList(projectId)).stream()
.map(this::buildExternalPersonWarningExcelRow)
.toList();
}
@Override
public CcdiProjectRiskModelCardsVO getExternalRiskModelCards(Long projectId) {
ensureProjectExists(projectId);
CcdiProjectRiskModelCardsVO cards = new CcdiProjectRiskModelCardsVO();
cards.setCardList(defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)));
return cards;
}
@Override
public CcdiProjectRiskModelPeopleVO getExternalRiskModelPeople(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
ensureProjectExists(queryDTO.getProjectId());
normalizeExternalRiskModelPeopleQuery(queryDTO);
Page<CcdiProjectRiskModelPeopleItemVO> page = new Page<>(
defaultPageNum(queryDTO.getPageNum()),
defaultPageSize(queryDTO.getPageSize())
);
Page<CcdiProjectRiskModelPeopleItemVO> resultPage =
overviewMapper.selectExternalRiskModelPeoplePage(page, queryDTO);
List<CcdiProjectRiskModelPeopleItemVO> rows = defaultList(resultPage == null ? null : resultPage.getRecords())
.stream()
.peek(item -> item.setActionLabel(ACTION_LABEL))
.toList();
CcdiProjectRiskModelPeopleVO people = new CcdiProjectRiskModelPeopleVO();
people.setRows(rows);
people.setTotal(resultPage == null ? 0L : resultPage.getTotal());
return people;
}
@Override
public List<CcdiProjectRiskModelPeopleExcel> exportExternalRiskModelPeople(
CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO
) {
ensureProjectExists(queryDTO.getProjectId());
normalizeExternalRiskModelPeopleQuery(queryDTO);
return defaultList(overviewMapper.selectExternalRiskModelPeopleList(queryDTO)).stream()
.map(item -> buildRiskModelPeopleExcelRow(item, item.getStaffCode()))
.toList();
}
@Override @Override
public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions( public CcdiProjectSuspiciousTransactionPageVO getSuspiciousTransactions(
CcdiProjectSuspiciousTransactionQueryDTO queryDTO CcdiProjectSuspiciousTransactionQueryDTO queryDTO
@@ -356,7 +239,7 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
ensureProjectExists(queryDTO.getProjectId()); ensureProjectExists(queryDTO.getProjectId());
normalizeSuspiciousTransactionQuery(queryDTO); normalizeSuspiciousTransactionQuery(queryDTO);
return defaultList(overviewMapper.selectReportSuspiciousTransactionList(queryDTO)).stream() return defaultList(overviewMapper.selectSuspiciousTransactionList(queryDTO)).stream()
.map(this::buildSuspiciousTransactionExcelRow) .map(this::buildSuspiciousTransactionExcelRow)
.toList(); .toList();
} }
@@ -447,12 +330,9 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
.toList()); .toList());
report.setParams(buildReportParams(project)); report.setParams(buildReportParams(project));
report.setModelSummaries(defaultList(overviewMapper.selectReportRiskModelSummaries(projectId))); report.setModelSummaries(defaultList(overviewMapper.selectReportRiskModelSummaries(projectId)));
report.setExternalRiskSummary(getExternalRiskSummary(projectId));
report.setExternalModelSummaries(buildExternalReportModelSummaries(projectId));
report.setRiskPeople(defaultList(overviewMapper.selectReportRiskPeople(projectId)).stream() report.setRiskPeople(defaultList(overviewMapper.selectReportRiskPeople(projectId)).stream()
.peek(item -> item.setActionLabel(ACTION_LABEL)) .peek(item -> item.setActionLabel(ACTION_LABEL))
.toList()); .toList());
report.setExternalPersonWarnings(exportExternalPersonWarnings(projectId));
report.setSuspiciousTransactions(defaultList( report.setSuspiciousTransactions(defaultList(
overviewMapper.selectReportSuspiciousTransactionList(suspiciousQuery) overviewMapper.selectReportSuspiciousTransactionList(suspiciousQuery)
)); ));
@@ -491,7 +371,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
projectMapper.updateRiskCountsByProjectId( projectMapper.updateRiskCountsByProjectId(
projectId, projectId,
countProjectScopeStaff(projectId),
countRiskLevel(results, "HIGH"), countRiskLevel(results, "HIGH"),
countRiskLevel(results, "MEDIUM"), countRiskLevel(results, "MEDIUM"),
countRiskLevel(results, "LOW"), countRiskLevel(results, "LOW"),
@@ -506,7 +385,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
Map<String, Object> summary = overviewMapper.selectRiskCountSummaryByProjectId(projectId); Map<String, Object> summary = overviewMapper.selectRiskCountSummaryByProjectId(projectId);
projectMapper.updateRiskCountsByProjectId( projectMapper.updateRiskCountsByProjectId(
projectId, projectId,
countProjectScopeStaff(projectId),
readCount(summary, "highRiskCount"), readCount(summary, "highRiskCount"),
readCount(summary, "mediumRiskCount"), readCount(summary, "mediumRiskCount"),
readCount(summary, "lowRiskCount"), readCount(summary, "lowRiskCount"),
@@ -514,10 +392,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
); );
} }
private int countProjectScopeStaff(Long projectId) {
return defaultZero(bankStatementMapper.countMatchedStaffCountByProjectId(projectId));
}
private CcdiProjectRiskPeopleOverviewItemVO buildRiskPeopleItem(Long projectId, CcdiProjectEmployeeRiskAggregateVO aggregate) { private CcdiProjectRiskPeopleOverviewItemVO buildRiskPeopleItem(Long projectId, CcdiProjectEmployeeRiskAggregateVO aggregate) {
CcdiProjectRiskPeopleOverviewItemVO item = new CcdiProjectRiskPeopleOverviewItemVO(); CcdiProjectRiskPeopleOverviewItemVO item = new CcdiProjectRiskPeopleOverviewItemVO();
item.setName(aggregate.getStaffName()); item.setName(aggregate.getStaffName());
@@ -559,51 +433,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return row; return row;
} }
private CcdiProjectExternalPersonWarningExcel buildExternalPersonWarningExcelRow(
CcdiProjectExternalPersonWarningItemVO item
) {
CcdiProjectExternalPersonWarningExcel row = new CcdiProjectExternalPersonWarningExcel();
row.setName(item.getName());
row.setIdNo(item.getIdNo());
row.setSubjectType(item.getSubjectType());
row.setRiskLevel(item.getRiskLevel());
row.setModelCount(item.getModelCount());
row.setRiskPoint(item.getRiskPoint());
row.setRelatedObject(item.getRelatedObject());
row.setLatestTradeTime(item.getLatestTradeTime());
return row;
}
private List<CcdiProjectOverviewReportModelSummaryVO> buildExternalReportModelSummaries(Long projectId) {
return defaultList(overviewMapper.selectExternalRiskModelCardsByProjectId(projectId)).stream()
.map(this::buildExternalReportModelSummary)
.toList();
}
private CcdiProjectOverviewReportModelSummaryVO buildExternalReportModelSummary(CcdiProjectRiskModelCardVO card) {
CcdiProjectOverviewReportModelSummaryVO row = new CcdiProjectOverviewReportModelSummaryVO();
row.setModelCode(card.getModelCode());
row.setModelName(card.getModelName());
row.setWarningCount(card.getWarningCount());
row.setPeopleCount(card.getPeopleCount());
row.setPeopleNames("-");
return row;
}
private CcdiProjectRiskModelPeopleExcel buildRiskModelPeopleExcelRow(
CcdiProjectRiskModelPeopleItemVO item,
String subjectType
) {
CcdiProjectRiskModelPeopleExcel row = new CcdiProjectRiskModelPeopleExcel();
row.setPersonName(item.getStaffName());
row.setSubjectType(subjectType);
row.setIdNo(item.getIdNo());
row.setScopeName(item.getDepartment());
row.setModelNames(joinModelNames(item.getModelNames()));
row.setHitTags(joinHitTagNames(item.getHitTagList()));
return row;
}
private void ensureProjectExists(Long projectId) { private void ensureProjectExists(Long projectId) {
getRequiredProject(projectId); getRequiredProject(projectId);
} }
@@ -616,14 +445,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase()); queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
} }
private void normalizeExternalRiskModelPeopleQuery(CcdiProjectExternalRiskModelPeopleQueryDTO queryDTO) {
if (queryDTO.getMatchMode() == null || queryDTO.getMatchMode().isBlank()) {
queryDTO.setMatchMode("ANY");
return;
}
queryDTO.setMatchMode(queryDTO.getMatchMode().trim().toUpperCase());
}
private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) { private void normalizeSuspiciousTransactionQuery(CcdiProjectSuspiciousTransactionQueryDTO queryDTO) {
if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) { if (queryDTO.getSuspiciousType() == null || queryDTO.getSuspiciousType().isBlank()) {
queryDTO.setSuspiciousType("ALL"); queryDTO.setSuspiciousType("ALL");
@@ -693,18 +514,15 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
} }
private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow( private CcdiProjectSuspiciousTransactionExcel buildSuspiciousTransactionExcelRow(
CcdiProjectOverviewReportSuspiciousTransactionVO item CcdiProjectSuspiciousTransactionItemVO item
) { ) {
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel(); CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
row.setTrxDate(item.getTrxDate()); row.setTrxDate(item.getTrxDate());
row.setLeAccountNo(item.getLeAccountNo()); row.setSuspiciousPersonName(item.getSuspiciousPersonName());
row.setLeAccountName(item.getLeAccountName()); row.setRelatedPersonName(item.getRelatedPersonName());
row.setCustomerAccountName(item.getCustomerAccountName());
row.setCustomerAccountNo(item.getCustomerAccountNo());
row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode())); row.setRelatedStaffDisplay(formatRelatedStaff(item.getRelatedStaffName(), item.getRelatedStaffCode()));
row.setUserMemo(item.getUserMemo()); row.setRelationType(item.getRelationType());
row.setCashType(item.getCashType()); row.setSummaryAndCashType(formatSummaryAndCashType(item.getUserMemo(), item.getCashType()));
row.setHitTags(item.getHitTags());
row.setDisplayAmount(item.getDisplayAmount()); row.setDisplayAmount(item.getDisplayAmount());
return row; return row;
} }
@@ -779,21 +597,6 @@ public class CcdiProjectOverviewServiceImpl implements ICcdiProjectOverviewServi
return safeMemo + "/" + safeCashType; return safeMemo + "/" + safeCashType;
} }
private String joinModelNames(List<String> modelNames) {
return defaultList(modelNames).stream()
.filter(item -> item != null && !item.isBlank())
.distinct()
.collect(Collectors.joining(""));
}
private String joinHitTagNames(List<CcdiProjectRiskHitTagVO> hitTags) {
return defaultList(hitTags).stream()
.map(CcdiProjectRiskHitTagVO::getRuleName)
.filter(item -> item != null && !item.isBlank())
.distinct()
.collect(Collectors.joining(""));
}
private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail( private CcdiProjectPersonAnalysisAbnormalDetailVO buildAbnormalDetail(
List<CcdiBankStatementListVO> statementRows, List<CcdiBankStatementListVO> statementRows,
List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows List<CcdiProjectPersonAnalysisObjectRecordVO> objectRows

View File

@@ -44,33 +44,19 @@ public class CcdiProjectRiskDetailWorkbookExporter {
private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) { private void writeSuspiciousSheet(Sheet sheet, List<CcdiProjectSuspiciousTransactionExcel> rows) {
Row header = sheet.createRow(0); Row header = sheet.createRow(0);
String[] headers = { String[] headers = { "交易时间", "可疑人员", "关联人", "关联员工", "关系", "摘要/交易类型", "交易金额" };
"交易时间",
"本方账户",
"本方主体",
"对方名称",
"对方账户",
"关联员工",
"摘要",
"交易类型",
"异常标签",
"交易金额"
};
writeHeader(header, headers); writeHeader(header, headers);
for (int i = 0; i < rows.size(); i++) { for (int i = 0; i < rows.size(); i++) {
CcdiProjectSuspiciousTransactionExcel item = rows.get(i); CcdiProjectSuspiciousTransactionExcel item = rows.get(i);
Row row = sheet.createRow(i + 1); Row row = sheet.createRow(i + 1);
row.createCell(0).setCellValue(safeText(item.getTrxDate())); row.createCell(0).setCellValue(safeText(item.getTrxDate()));
row.createCell(1).setCellValue(safeText(item.getLeAccountNo())); row.createCell(1).setCellValue(safeText(item.getSuspiciousPersonName()));
row.createCell(2).setCellValue(safeText(item.getLeAccountName())); row.createCell(2).setCellValue(safeText(item.getRelatedPersonName()));
row.createCell(3).setCellValue(safeText(item.getCustomerAccountName())); row.createCell(3).setCellValue(safeText(item.getRelatedStaffDisplay()));
row.createCell(4).setCellValue(safeText(item.getCustomerAccountNo())); row.createCell(4).setCellValue(safeText(item.getRelationType()));
row.createCell(5).setCellValue(safeText(item.getRelatedStaffDisplay())); row.createCell(5).setCellValue(safeText(item.getSummaryAndCashType()));
row.createCell(6).setCellValue(safeText(item.getUserMemo())); row.createCell(6).setCellValue(safeNumber(item.getDisplayAmount()));
row.createCell(7).setCellValue(safeText(item.getCashType()));
row.createCell(8).setCellValue(safeText(item.getHitTags()));
row.createCell(9).setCellValue(safeNumber(item.getDisplayAmount()));
} }
} }

View File

@@ -4,18 +4,14 @@ import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants; import com.ruoyi.ccdi.project.constants.CcdiProjectStatusConstants;
import com.ruoyi.ccdi.project.domain.CcdiProject; import com.ruoyi.ccdi.project.domain.CcdiProject;
import com.ruoyi.ccdi.project.domain.ProjectAccessScope;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectImportHistoryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO; import com.ruoyi.ccdi.project.domain.dto.CcdiProjectQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiProjectSaveDTO; 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.event.CcdiProjectHistoryImportSubmittedEvent;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectHistoryListItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO; import com.ruoyi.ccdi.project.domain.vo.CcdiProjectStatusCountsVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiProjectVO; 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.mapper.CcdiProjectMapper;
import com.ruoyi.ccdi.project.service.CcdiProjectAccessService;
import com.ruoyi.ccdi.project.service.ICcdiProjectService; import com.ruoyi.ccdi.project.service.ICcdiProjectService;
import com.ruoyi.common.exception.ServiceException; import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.lsfx.client.LsfxAnalysisClient; import com.ruoyi.lsfx.client.LsfxAnalysisClient;
@@ -47,18 +43,12 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Resource @Resource
private CcdiProjectMapper projectMapper; private CcdiProjectMapper projectMapper;
@Resource
private CcdiBankTagTaskMapper bankTagTaskMapper;
@Resource @Resource
private LsfxAnalysisClient lsfxAnalysisClient; private LsfxAnalysisClient lsfxAnalysisClient;
@Resource @Resource
private ApplicationEventPublisher applicationEventPublisher; private ApplicationEventPublisher applicationEventPublisher;
@Resource
private CcdiProjectAccessService projectAccessService;
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) { public CcdiProjectVO createProject(CcdiProjectSaveDTO dto) {
@@ -87,7 +77,6 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
// 5. 返回VO // 5. 返回VO
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo); BeanUtils.copyProperties(project, vo);
fillProjectExtraFields(project, vo);
return vo; return vo;
} }
@@ -101,7 +90,6 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
if (existingProject == null) { if (existingProject == null) {
throw new ServiceException("项目不存在"); throw new ServiceException("项目不存在");
} }
projectAccessService.assertCanOperate(dto.getProjectId());
// 只更新允许修改的字段 // 只更新允许修改的字段
existingProject.setProjectName(dto.getProjectName()); existingProject.setProjectName(dto.getProjectName());
@@ -112,46 +100,38 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(existingProject, vo); BeanUtils.copyProperties(existingProject, vo);
fillProjectExtraFields(existingProject, vo);
return vo; return vo;
} }
@Override @Override
public boolean deleteProject(Long projectId) { public boolean deleteProject(Long projectId) {
projectAccessService.assertCanOperate(projectId);
return projectMapper.deleteById(projectId) > 0; return projectMapper.deleteById(projectId) > 0;
} }
@Override @Override
public CcdiProjectVO getProjectById(Long projectId) { public CcdiProjectVO getProjectById(Long projectId) {
projectAccessService.assertCanRead(projectId);
CcdiProject project = projectMapper.selectById(projectId); CcdiProject project = projectMapper.selectById(projectId);
if (project == null) { if (project == null) {
return null; return null;
} }
CcdiProjectVO vo = new CcdiProjectVO(); CcdiProjectVO vo = new CcdiProjectVO();
BeanUtils.copyProperties(project, vo); BeanUtils.copyProperties(project, vo);
fillProjectExtraFields(project, vo);
return vo; return vo;
} }
@Override @Override
public Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO) { public Page<CcdiProjectVO> selectProjectPage(Page<CcdiProjectVO> page, CcdiProjectQueryDTO queryDTO) {
ProjectAccessScope scope = projectAccessService.buildCurrentScope(); return projectMapper.selectProjectPage(page, queryDTO);
Page<CcdiProjectVO> result = projectMapper.selectProjectPage(page, queryDTO, scope);
fillProjectExtraFields(result.getRecords());
return result;
} }
@Override @Override
public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) { public List<CcdiProjectHistoryListItemVO> listHistoryProjects(CcdiProjectQueryDTO queryDTO) {
return projectMapper.selectHistoryProjects(queryDTO, projectAccessService.buildCurrentScope()); return projectMapper.selectHistoryProjects(queryDTO);
} }
@Override @Override
@Transactional(rollbackFor = Exception.class) @Transactional(rollbackFor = Exception.class)
public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) { public CcdiProjectVO importFromHistory(CcdiProjectImportHistoryDTO dto, String operator) {
projectAccessService.assertSourceProjectsReadable(dto.getSourceProjectIds());
CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO(); CcdiProjectSaveDTO saveDTO = new CcdiProjectSaveDTO();
saveDTO.setProjectName(dto.getProjectName()); saveDTO.setProjectName(dto.getProjectName());
saveDTO.setDescription(dto.getDescription()); saveDTO.setDescription(dto.getDescription());
@@ -171,43 +151,44 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
@Override @Override
public CcdiProjectStatusCountsVO getStatusCounts() { public CcdiProjectStatusCountsVO getStatusCounts() {
CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO(); CcdiProjectStatusCountsVO vo = new CcdiProjectStatusCountsVO();
ProjectAccessScope scope = projectAccessService.buildCurrentScope();
// 统计全部项目 // 统计全部项目
LambdaQueryWrapper<CcdiProject> baseWrapper = buildScopeWrapper(scope); Long totalCount = projectMapper.selectCount(null);
Long totalCount = projectMapper.selectCount(baseWrapper);
vo.setAll(totalCount); vo.setAll(totalCount);
// 统计进行中项目状态0 // 统计进行中项目状态0
Long status0Count = projectMapper.selectCount(buildScopeWrapper(scope) Long status0Count = projectMapper.selectCount(
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.PROCESSING)); new LambdaQueryWrapper<CcdiProject>()
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.PROCESSING)
);
vo.setStatus0(status0Count); vo.setStatus0(status0Count);
// 统计已完成项目状态1 // 统计已完成项目状态1
Long status1Count = projectMapper.selectCount(buildScopeWrapper(scope) Long status1Count = projectMapper.selectCount(
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.COMPLETED)); new LambdaQueryWrapper<CcdiProject>()
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.COMPLETED)
);
vo.setStatus1(status1Count); vo.setStatus1(status1Count);
// 统计已归档项目状态2 // 统计已归档项目状态2
Long status2Count = projectMapper.selectCount(buildScopeWrapper(scope) Long status2Count = projectMapper.selectCount(
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.ARCHIVED)); new LambdaQueryWrapper<CcdiProject>()
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.ARCHIVED)
);
vo.setStatus2(status2Count); vo.setStatus2(status2Count);
Long status3Count = projectMapper.selectCount(buildScopeWrapper(scope) Long status3Count = projectMapper.selectCount(
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAGGING)); new LambdaQueryWrapper<CcdiProject>()
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAGGING)
);
vo.setStatus3(status3Count); vo.setStatus3(status3Count);
Long status4Count = projectMapper.selectCount(buildScopeWrapper(scope)
.eq(CcdiProject::getStatus, CcdiProjectStatusConstants.TAG_FAILED));
vo.setStatus4(status4Count);
return vo; return vo;
} }
@Override @Override
public void archiveProject(Long projectId, String operator) { public void archiveProject(Long projectId, String operator) {
CcdiProject project = getRequiredProject(projectId); CcdiProject project = getRequiredProject(projectId);
projectAccessService.assertCanOperate(projectId);
if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) { if (CcdiProjectStatusConstants.ARCHIVED.equals(project.getStatus())) {
throw new ServiceException("项目已归档,无需重复操作"); throw new ServiceException("项目已归档,无需重复操作");
} }
@@ -282,55 +263,10 @@ public class CcdiProjectServiceImpl implements ICcdiProjectService {
case CcdiProjectStatusConstants.COMPLETED -> "已完成"; case CcdiProjectStatusConstants.COMPLETED -> "已完成";
case CcdiProjectStatusConstants.ARCHIVED -> "已归档"; case CcdiProjectStatusConstants.ARCHIVED -> "已归档";
case CcdiProjectStatusConstants.TAGGING -> "打标中"; case CcdiProjectStatusConstants.TAGGING -> "打标中";
case CcdiProjectStatusConstants.TAG_FAILED -> "打标失败";
default -> "未知"; 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 LambdaQueryWrapper<CcdiProject> buildScopeWrapper(ProjectAccessScope scope) {
LambdaQueryWrapper<CcdiProject> wrapper = new LambdaQueryWrapper<>();
if (scope != null && !scope.isViewAllProjects()) {
wrapper.eq(CcdiProject::getCreateBy, scope.getUsername());
}
return wrapper;
}
private void fillProjectExtraFields(List<CcdiProjectVO> records) {
if (records == null || records.isEmpty()) {
return;
}
ProjectAccessScope scope = projectAccessService.buildCurrentScope();
for (CcdiProjectVO vo : records) {
fillProjectAccessFields(vo, scope);
}
}
private void fillProjectExtraFields(CcdiProject project, CcdiProjectVO vo) {
fillLatestTagFailure(project, vo);
fillProjectAccessFields(vo, projectAccessService.buildCurrentScope());
}
private void fillProjectAccessFields(CcdiProjectVO vo, ProjectAccessScope scope) {
if (vo == null || scope == null) {
return;
}
boolean ownedByCurrentUser = Objects.equals(scope.getUsername(), vo.getCreateBy());
vo.setOwnedByCurrentUser(ownedByCurrentUser);
vo.setCanOperate(scope.isSuperAdmin() || ownedByCurrentUser);
}
private String resolveOperator(String operator) { private String resolveOperator(String operator) {
return StringUtils.hasText(operator) ? operator : "system"; return StringUtils.hasText(operator) ? operator : "system";
} }

View File

@@ -1,284 +0,0 @@
package com.ruoyi.ccdi.project.service.impl;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphQueryDTO;
import com.ruoyi.ccdi.project.domain.dto.CcdiRelationGraphSuspectedEnterpriseQueryDTO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphEdgeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseItemVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseVO;
import com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphVO;
import com.ruoyi.ccdi.project.mapper.CcdiRelationGraphMapper;
import com.ruoyi.ccdi.project.service.ICcdiRelationGraphService;
import jakarta.annotation.Resource;
import org.springframework.stereotype.Service;
import java.time.LocalDate;
import java.time.Period;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 关系图谱Service实现
*/
@Service
public class CcdiRelationGraphServiceImpl implements ICcdiRelationGraphService {
private static final int DEFAULT_LIMIT = 80;
private static final int MAX_LIMIT = 200;
private static final int DEFAULT_SUSPECTED_LIMIT = 10;
private static final int MAX_SUSPECTED_LIMIT = 20;
private static final int SAME_NAME_BLOCK_THRESHOLD = 20;
private static final String NODE_PREFIX = "rel_node/";
private static final DateTimeFormatter DATE_FORMATTER = DateTimeFormatter.ofPattern("yyyy-MM-dd");
@Resource
private CcdiRelationGraphMapper relationGraphMapper;
@Override
public List<CcdiRelationGraphNodeVO> searchSubjects(CcdiRelationGraphQueryDTO queryDTO) {
CcdiRelationGraphQueryDTO query = normalizeGraphQuery(queryDTO);
if (isBlank(query.getKeyword()) && isBlank(query.getObjectKey())) {
return Collections.emptyList();
}
return relationGraphMapper.selectRelationGraphSubjects(query);
}
@Override
public CcdiRelationGraphVO getRelationGraph(CcdiRelationGraphQueryDTO queryDTO) {
CcdiRelationGraphQueryDTO query = normalizeGraphQuery(queryDTO);
CcdiRelationGraphNodeVO centerNode = resolveCenterNode(query);
if (centerNode == null || isBlank(centerNode.getObjectKey())) {
return new CcdiRelationGraphVO();
}
query.setObjectKey(centerNode.getObjectKey());
List<CcdiRelationGraphEdgeVO> edges = relationGraphMapper.selectRelationGraphEdges(query);
if (edges == null) {
edges = Collections.emptyList();
}
List<CcdiRelationGraphNodeVO> nodes = buildNodes(centerNode, edges);
CcdiRelationGraphVO graph = new CcdiRelationGraphVO();
graph.setCenterNode(centerNode);
graph.setNodes(nodes);
graph.setEdges(edges);
graph.setEdgeCount((long) edges.size());
graph.setMaxDepth(1);
return graph;
}
@Override
public CcdiRelationGraphSuspectedEnterpriseVO getSuspectedEnterprises(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO) {
CcdiRelationGraphSuspectedEnterpriseVO result = new CcdiRelationGraphSuspectedEnterpriseVO();
CcdiRelationGraphSuspectedEnterpriseQueryDTO query = normalizeSuspectedEnterpriseQuery(queryDTO);
if (isBlank(query.getPersonName())) {
result.setMessage("缺少人员姓名,无法按工商同名主体召回");
return result;
}
int sameNameKeyNoCount = relationGraphMapper.countSuspectedEnterpriseKeyNos(query.getPersonName());
result.setSameNameKeyNoCount(sameNameKeyNoCount);
if (sameNameKeyNoCount > SAME_NAME_BLOCK_THRESHOLD) {
result.setBlocked(true);
result.setMessage("同名工商主体过多,请结合交易对手企业名称或其他线索进一步筛选");
return result;
}
LocalDate birthDate = resolveBirthDate(query);
List<CcdiRelationGraphSuspectedEnterpriseItemVO> rows =
relationGraphMapper.selectSuspectedEnterprises(query.getPersonName(), MAX_SUSPECTED_LIMIT);
List<CcdiRelationGraphSuspectedEnterpriseItemVO> filteredRows = new ArrayList<>();
if (rows != null) {
for (CcdiRelationGraphSuspectedEnterpriseItemVO row : rows) {
if (row == null) {
continue;
}
row.setPersonName(query.getPersonName());
if (applyAgeRule(row, birthDate)) {
filteredRows.add(row);
if (filteredRows.size() >= query.getLimit()) {
break;
}
}
}
}
result.setRows(filteredRows);
if (filteredRows.isEmpty()) {
result.setMessage("未发现可展示的疑似同名企业");
}
return result;
}
private CcdiRelationGraphNodeVO resolveCenterNode(CcdiRelationGraphQueryDTO query) {
List<CcdiRelationGraphNodeVO> subjects = relationGraphMapper.selectRelationGraphSubjects(query);
if (subjects == null || subjects.isEmpty()) {
return null;
}
return subjects.get(0);
}
private List<CcdiRelationGraphNodeVO> buildNodes(CcdiRelationGraphNodeVO centerNode, List<CcdiRelationGraphEdgeVO> edges) {
Set<String> objectKeys = new LinkedHashSet<>();
objectKeys.add(centerNode.getObjectKey());
for (CcdiRelationGraphEdgeVO edge : edges) {
edge.setFromObjectKey(toObjectKey(edge.getFromKey()));
edge.setToObjectKey(toObjectKey(edge.getToKey()));
if (!isBlank(edge.getFromObjectKey())) {
objectKeys.add(edge.getFromObjectKey());
}
if (!isBlank(edge.getToObjectKey())) {
objectKeys.add(edge.getToObjectKey());
}
}
List<CcdiRelationGraphNodeVO> rawNodes = relationGraphMapper.selectRelationGraphNodesByKeys(new ArrayList<>(objectKeys));
Map<String, CcdiRelationGraphNodeVO> nodeMap = new LinkedHashMap<>();
if (rawNodes != null) {
for (CcdiRelationGraphNodeVO node : rawNodes) {
enrichNode(node, centerNode);
nodeMap.put(node.getObjectKey(), node);
}
}
if (!nodeMap.containsKey(centerNode.getObjectKey())) {
enrichNode(centerNode, centerNode);
nodeMap.put(centerNode.getObjectKey(), centerNode);
}
for (CcdiRelationGraphEdgeVO edge : edges) {
CcdiRelationGraphNodeVO fromNode = nodeMap.get(edge.getFromObjectKey());
CcdiRelationGraphNodeVO toNode = nodeMap.get(edge.getToObjectKey());
if (fromNode != null) {
edge.setFromName(fromNode.getNodeName());
}
if (toNode != null) {
edge.setToName(toNode.getNodeName());
}
}
return List.copyOf(nodeMap.values());
}
private void enrichNode(CcdiRelationGraphNodeVO node, CcdiRelationGraphNodeVO centerNode) {
if (node == null) {
return;
}
node.setNodeKey(NODE_PREFIX + node.getObjectKey());
node.setCanExpand(true);
node.setDepth(node.getObjectKey() != null && node.getObjectKey().equals(centerNode.getObjectKey()) ? 0 : 1);
}
private CcdiRelationGraphQueryDTO normalizeGraphQuery(CcdiRelationGraphQueryDTO queryDTO) {
CcdiRelationGraphQueryDTO query = queryDTO == null ? new CcdiRelationGraphQueryDTO() : queryDTO;
query.setKeyword(normalizeText(query.getKeyword()));
query.setObjectKey(normalizeText(query.getObjectKey()));
query.setLimit(normalizeLimit(query.getLimit()));
query.setDepth(1);
return query;
}
private CcdiRelationGraphSuspectedEnterpriseQueryDTO normalizeSuspectedEnterpriseQuery(CcdiRelationGraphSuspectedEnterpriseQueryDTO queryDTO) {
CcdiRelationGraphSuspectedEnterpriseQueryDTO query =
queryDTO == null ? new CcdiRelationGraphSuspectedEnterpriseQueryDTO() : queryDTO;
query.setPersonName(normalizeText(query.getPersonName()));
query.setCertNo(normalizeText(query.getCertNo()));
query.setBirthDate(normalizeText(query.getBirthDate()));
query.setLimit(normalizeSuspectedLimit(query.getLimit()));
return query;
}
private Integer normalizeSuspectedLimit(Integer limit) {
if (limit == null || limit <= 0) {
return DEFAULT_SUSPECTED_LIMIT;
}
return Math.min(limit, MAX_SUSPECTED_LIMIT);
}
private LocalDate resolveBirthDate(CcdiRelationGraphSuspectedEnterpriseQueryDTO query) {
LocalDate explicitBirthDate = parseDate(query.getBirthDate());
if (explicitBirthDate != null) {
return explicitBirthDate;
}
return parseBirthDateFromCertNo(query.getCertNo());
}
private boolean applyAgeRule(CcdiRelationGraphSuspectedEnterpriseItemVO row, LocalDate birthDate) {
LocalDate establishDate = parseDate(row.getEstablishDate());
if (birthDate == null || establishDate == null) {
row.setMatchReason("姓名一致;企业成立日期或出生日期缺失,年龄无法判断");
return true;
}
int age = Period.between(birthDate, establishDate).getYears();
row.setAgeAtEstablish(age);
if (age < 18) {
return false;
}
row.setMatchReason("姓名一致;成立时年龄" + age + "");
return true;
}
private LocalDate parseBirthDateFromCertNo(String certNo) {
if (isBlank(certNo)) {
return null;
}
String value = certNo.trim();
if (value.matches("^\\d{17}[0-9Xx]$")) {
return parseCompactDate(value.substring(6, 14));
}
if (value.matches("^\\d{15}$")) {
return parseCompactDate("19" + value.substring(6, 12));
}
return null;
}
private LocalDate parseCompactDate(String value) {
try {
return LocalDate.parse(value, DateTimeFormatter.BASIC_ISO_DATE);
} catch (DateTimeParseException ignored) {
return null;
}
}
private LocalDate parseDate(String value) {
if (isBlank(value)) {
return null;
}
try {
return LocalDate.parse(value.trim(), DATE_FORMATTER);
} catch (DateTimeParseException ignored) {
return null;
}
}
private Integer normalizeLimit(Integer limit) {
if (limit == null || limit <= 0) {
return DEFAULT_LIMIT;
}
return Math.min(limit, MAX_LIMIT);
}
private String toObjectKey(String nodeKey) {
if (isBlank(nodeKey)) {
return null;
}
return nodeKey.startsWith(NODE_PREFIX) ? nodeKey.substring(NODE_PREFIX.length()) : nodeKey;
}
private String normalizeText(String value) {
if (value == null) {
return null;
}
String trimmed = value.trim();
return trimmed.isEmpty() ? null : trimmed;
}
private boolean isBlank(String value) {
return value == null || value.trim().isEmpty();
}
}

View File

@@ -119,7 +119,7 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="countMatchedStaffCountByProjectId" resultType="java.lang.Integer"> <select id="countMatchedStaffCountByProjectId" resultType="java.lang.Integer">
select count(distinct trim(bs.cret_no)) select count(distinct trim(bs.cret_no))
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ccdi_base_staff staff on staff.id_card = trim(bs.cret_no) inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and bs.cret_no is not null and bs.cret_no is not null
and trim(bs.cret_no) != '' and trim(bs.cret_no) != ''
@@ -215,12 +215,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
#{item} #{item}
</foreach> </foreach>
</if> </if>
<if test="query.ourCertNos != null and query.ourCertNos.size() > 0">
AND bs.cret_no IN
<foreach collection="query.ourCertNos" item="item" open="(" separator="," close=")">
#{item}
</foreach>
</if>
<if test="query.ourBanks != null and query.ourBanks.size() > 0"> <if test="query.ourBanks != null and query.ourBanks.size() > 0">
AND bs.BANK IN AND bs.BANK IN
<foreach collection="query.ourBanks" item="item" open="(" separator="," close=")"> <foreach collection="query.ourBanks" item="item" open="(" separator="," close=")">

View File

@@ -41,23 +41,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and trim(bs.cret_no) != '' and trim(bs.cret_no) != ''
</sql> </sql>
<sql id="externalPersonPredicateSql">
bs.cret_no is not null
and trim(bs.cret_no) != ''
and not exists (
select 1
from ccdi_base_staff staff
where staff.id_card = bs.cret_no
)
and not exists (
select 1
from ccdi_staff_fmy_relation relation
where relation.status = 1
and relation.relation_cert_no = bs.cret_no
)
and trim(IFNULL(bs.LE_ACCOUNT_NAME, '')) &lt;&gt; trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))
</sql>
<sql id="cashDepositPredicate"> <sql id="cashDepositPredicate">
( (
( (
@@ -122,7 +105,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<sql id="salaryExclusionPredicate"> <sql id="salaryExclusionPredicate">
not ( not (
(
bs.CUSTOMER_ACCOUNT_NAME = '浙江兰溪农村商业银行股份有限公司' bs.CUSTOMER_ACCOUNT_NAME = '浙江兰溪农村商业银行股份有限公司'
and ( and (
IFNULL(bs.USER_MEMO, '') LIKE '%代发%' IFNULL(bs.USER_MEMO, '') LIKE '%代发%'
@@ -151,92 +133,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
or IFNULL(bs.CASH_TYPE, '') LIKE '%劳务费%' or IFNULL(bs.CASH_TYPE, '') LIKE '%劳务费%'
) )
) )
or (
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%公积金中心%'
and (
IFNULL(bs.USER_MEMO, '') LIKE '%公积金%'
or IFNULL(bs.USER_MEMO, '') LIKE '%批量代付%'
or IFNULL(bs.CASH_TYPE, '') LIKE '%公积金%'
or IFNULL(bs.CASH_TYPE, '') LIKE '%批量代付%'
)
)
)
</sql>
<sql id="financialProductExclusionPredicate">
not (
(
(
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|理财.*托管|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝'
or IFNULL(bs.USER_MEMO, '') REGEXP '理财|理财产品|结构.*存款|结构性存款|本金划出|本金返还|余额宝|朝朝宝|朝朝盈|现金宝|金添利|定存宝|整存整取|智能存款|通知存款'
or IFNULL(bs.CASH_TYPE, '') REGEXP '受托理财|表内理财|购买理财|理财购买|理财扣款|理财申购|理财认购|结构性存款|存款产品|朝朝宝'
or (
IFNULL(bs.USER_MEMO, '') REGEXP '申购|认购|赎回'
and IFNULL(bs.USER_MEMO, '') REGEXP '理财|产品|存款|本金|余额宝|朝朝宝|朝朝盈'
)
)
and IFNULL(bs.USER_MEMO, '') NOT REGEXP '财务|经理|代理财税'
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') NOT LIKE '%代理财政%'
)
)
</sql>
<sql id="thirdPartyWithdrawIncomePredicate">
(
(
bs.BANK in ('ALIPAY', 'WECHAT')
and (
IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
)
)
or (
(
bs.BANK is null
or bs.BANK = ''
or bs.BANK not in ('ALIPAY', 'WECHAT')
)
and (
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
or IFNULL(bs.USER_MEMO, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
or IFNULL(bs.CASH_TYPE, '') REGEXP '支付宝|Alipay|财付通|Tenpay|微信|wechat|WeChat|微信零钱'
)
and (
IFNULL(bs.CASH_TYPE, '') LIKE '%提现%'
or IFNULL(bs.USER_MEMO, '') LIKE '%提现%'
or IFNULL(bs.USER_MEMO, '') LIKE '%转出到%银行%'
or IFNULL(bs.USER_MEMO, '') LIKE '%提现到账%'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现%'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%转出到%银行%'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%提现到账%'
)
)
)
</sql>
<sql id="abnormalCustomerTransactionSubjectSql">
select
staff.id_card as subjectCertNo,
staff.name as subjectName,
'本人' as subjectType
from ccdi_base_staff staff
union all
select
relation.relation_cert_no as subjectCertNo,
relation.relation_name as subjectName,
case
when relation.relation_type is not null and trim(relation.relation_type) != '' then relation.relation_type
else '关系人'
end as subjectType
from ccdi_staff_fmy_relation relation
where relation.status = 1
and relation.relation_cert_no is not null
and trim(relation.relation_cert_no) != ''
</sql> </sql>
<sql id="salaryIncomePredicate"> <sql id="salaryIncomePredicate">
@@ -271,7 +167,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|(购|买).*车|车款|房款|首付|(房|车).*贷' IFNULL(bs.USER_MEMO, '') REGEXP '(购|买).*房|(购|买).*车|车款|房款|首付|(房|车).*贷'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '汽车销售|汽车金融|4S店|汽贸|车行|房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局' or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '汽车销售|汽车金融|4S店|汽贸|车行|房地产|置业|置地|地产|房产|不动产|链家|贝壳|我爱我家|房管局'
) )
and <include refid="financialProductExclusionPredicate"/>
and ( and (
exists ( exists (
select 1 select 1
@@ -303,7 +198,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款' IFNULL(bs.USER_MEMO, '') REGEXP '税务|缴税|税款'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政' or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '税务|税务局|国库|国家金库|财政'
) )
and <include refid="financialProductExclusionPredicate"/>
and ( and (
exists ( exists (
select 1 select 1
@@ -341,7 +235,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
and relation.person_id is null and relation.person_id is null
and <include refid="salaryExclusionPredicate"/> and <include refid="salaryExclusionPredicate"/>
and <include refid="financialProductExclusionPredicate"/>
</select> </select>
<select id="selectCumulativeIncomeObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectCumulativeIncomeObjects" resultMap="BankTagObjectHitResultMap">
@@ -369,7 +262,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
and relation.person_id is null and relation.person_id is null
and <include refid="salaryExclusionPredicate"/> and <include refid="salaryExclusionPredicate"/>
and <include refid="financialProductExclusionPredicate"/>
group by staff.id_card, bs.CUSTOMER_ACCOUNT_NAME group by staff.id_card, bs.CUSTOMER_ACCOUNT_NAME
having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{threshold} having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
) t ) t
@@ -391,7 +283,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
and <include refid="financialProductExclusionPredicate"/>
and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH) and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
group by staff.id_card group by staff.id_card
having SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) > #{threshold} having SUM(IFNULL(bs.AMOUNT_DR, 0) + IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
@@ -411,12 +302,19 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_CR, 0) > #{threshold} and IFNULL(bs.AMOUNT_CR, 0) > #{threshold}
and <include refid="cashDepositPredicate"/> and <include refid="cashDepositPredicate"/>
and <include refid="financialProductExclusionPredicate"/> and (
and exists ( exists (
select 1 select 1
from ccdi_base_staff staff from ccdi_base_staff staff
where staff.id_card = bs.cret_no where staff.id_card = bs.cret_no
) )
or exists (
select 1
from ccdi_staff_fmy_relation relation
where relation.relation_cert_no = bs.cret_no
and relation.status = 1
)
)
</select> </select>
<select id="selectFrequentCashDepositObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectFrequentCashDepositObjects" resultMap="BankTagObjectHitResultMap">
@@ -442,7 +340,16 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_CR, 0) > #{amountThreshold} and IFNULL(bs.AMOUNT_CR, 0) > #{amountThreshold}
and <include refid="cashDepositPredicate"/> and <include refid="cashDepositPredicate"/>
and <include refid="financialProductExclusionPredicate"/> union all
select
relation.person_id AS object_key,
LEFT(TRIM(bs.TRX_DATE), 10) AS cash_date
from ccdi_bank_statement bs
inner join ccdi_staff_fmy_relation relation on relation.relation_cert_no = bs.cret_no
where bs.project_id = #{projectId}
and relation.status = 1
and IFNULL(bs.AMOUNT_CR, 0) > #{amountThreshold}
and <include refid="cashDepositPredicate"/>
) source ) source
group by source.object_key, source.cash_date group by source.object_key, source.cash_date
having COUNT(1) > #{frequencyThreshold} having COUNT(1) > #{frequencyThreshold}
@@ -466,8 +373,8 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
or IFNULL(bs.USER_MEMO, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入' or IFNULL(bs.USER_MEMO, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
or IFNULL(bs.CASH_TYPE, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入' or IFNULL(bs.CASH_TYPE, '') REGEXP '转帐|转账|汇入|转存|红包|汇款|网转|转入'
) )
and IFNULL(bs.USER_MEMO, '') NOT LIKE '%款%'
and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') and IFNULL(bs.LE_ACCOUNT_NAME, '') &lt;&gt; IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')
and <include refid="financialProductExclusionPredicate"/>
and ( and (
exists ( exists (
select 1 select 1
@@ -483,200 +390,14 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) )
</select> </select>
<select id="selectExternalSingleLargeAmountStatements" resultMap="BankTagStatementHitResultMap">
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
CONCAT(
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
'”单笔交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,超过阈值 ', CAST(#{threshold} AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > #{threshold}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and <include refid="financialProductExclusionPredicate"/>
</select>
<select id="selectExternalCumulativeTransactionAmountObjects" resultMap="BankTagObjectHitResultMap">
select
'EXTERNAL_CERT_NO' AS objectType,
t.certNo AS objectKey,
CONCAT(
'外部人员“', IFNULL(t.personName, ''),
'”累计交易金额 ', CAST(t.totalAmount AS CHAR),
' 元,超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
) AS reasonDetail
from (
select
bs.cret_no AS certNo,
max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS totalAmount
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and <include refid="financialProductExclusionPredicate"/>
group by bs.cret_no
having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
) t
</select>
<select id="selectExternalAnnualTurnoverObjects" resultMap="BankTagObjectHitResultMap">
select
'EXTERNAL_CERT_NO' AS objectType,
t.certNo AS objectKey,
CONCAT(
'外部人员“', IFNULL(t.personName, ''),
'”近一年流水交易额 ', CAST(t.annualAmount AS CHAR),
' 元,超过阈值 ', CAST(#{threshold} AS CHAR), ' 元'
) AS reasonDetail
from (
select
bs.cret_no AS certNo,
max(IFNULL(bs.LE_ACCOUNT_NAME, '')) AS personName,
ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) AS annualAmount
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and <include refid="financialProductExclusionPredicate"/>
and STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 10), '%Y-%m-%d') >= DATE_SUB(CURDATE(), INTERVAL 12 MONTH)
group by bs.cret_no
having ROUND(SUM(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0))), 2) > #{threshold}
) t
</select>
<select id="selectAbnormalCustomerTransactionStatements" resultMap="BankTagStatementHitResultMap"> <select id="selectAbnormalCustomerTransactionStatements" resultMap="BankTagStatementHitResultMap">
select
hit.bankStatementId AS bankStatementId,
max(hit.groupId) AS groupId,
max(hit.logId) AS logId,
substring_index(
min(concat(lpad(hit.matchPriority, 2, '0'), '|', hit.reasonDetail)),
'|',
-1
) AS reasonDetail
from (
select select
bs.bank_statement_id AS bankStatementId, bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId, bs.group_id AS groupId,
bs.batch_id AS logId, bs.batch_id AS logId,
1 AS matchPriority, '占位SQL待补充真实规则' AS reasonDetail
CONCAT(
subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与信贷客户账号发生交易,',
'金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ( where 1 = 0
<include refid="abnormalCustomerTransactionSubjectSql"/>
) subject on subject.subjectCertNo = bs.cret_no
inner join ccdi_account_info account
on trim(IFNULL(bs.customer_account_no, '')) != ''
and account.owner_type = 'CREDIT_CUSTOMER'
and account.account_no = bs.customer_account_no
where bs.project_id = #{projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
2 AS matchPriority,
CONCAT(
subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介账号发生交易,',
'金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs
inner join (
<include refid="abnormalCustomerTransactionSubjectSql"/>
) subject on subject.subjectCertNo = bs.cret_no
inner join ccdi_account_info account
on trim(IFNULL(bs.customer_account_no, '')) != ''
and account.owner_type = 'INTERMEDIARY'
and account.account_no = bs.customer_account_no
where bs.project_id = #{projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
3 AS matchPriority,
CONCAT(
subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介关联企业发生交易,',
'金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs
inner join (
<include refid="abnormalCustomerTransactionSubjectSql"/>
) subject on subject.subjectCertNo = bs.cret_no
inner join ccdi_enterprise_base_info enterprise
on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
and enterprise.enterprise_name = bs.CUSTOMER_ACCOUNT_NAME
and enterprise.ent_source = 'INTERMEDIARY'
where bs.project_id = #{projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
4 AS matchPriority,
CONCAT(
subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介库人员发生微信/支付宝交易,',
'金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs
inner join (
<include refid="abnormalCustomerTransactionSubjectSql"/>
) subject on subject.subjectCertNo = bs.cret_no
inner join ccdi_biz_intermediary intermediary
on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
and trim(IFNULL(intermediary.name, '')) != ''
and bs.CUSTOMER_ACCOUNT_NAME like concat('%', intermediary.name, '%')
where bs.project_id = #{projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
and bs.bank in ('ALIPAY', 'WECHAT')
union all
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
5 AS matchPriority,
CONCAT(
subject.subjectType, '“', IFNULL(subject.subjectName, ''), '”与中介库人员发生名称精确匹配交易,',
'金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR),
' 元,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''), '”'
) AS reasonDetail
from ccdi_bank_statement bs
inner join (
<include refid="abnormalCustomerTransactionSubjectSql"/>
) subject on subject.subjectCertNo = bs.cret_no
inner join ccdi_biz_intermediary intermediary
on trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '')) != ''
and intermediary.name = bs.CUSTOMER_ACCOUNT_NAME
where bs.project_id = #{projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
) hit
group by hit.bankStatementId
</select> </select>
<select id="selectLowIncomeRelativeLargeTransactionObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectLowIncomeRelativeLargeTransactionObjects" resultMap="BankTagObjectHitResultMap">
@@ -695,9 +416,9 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from ccdi_staff_fmy_relation relation from ccdi_staff_fmy_relation relation
inner join ccdi_bank_statement bs on relation.relation_cert_no = bs.cret_no inner join ccdi_bank_statement bs on relation.relation_cert_no = bs.cret_no
where relation.status = 1 where relation.status = 1
and relation.annual_income is not null
and ( and (
relation.annual_income = 0 relation.annual_income is null
or relation.annual_income = 0
or relation.annual_income / 12 &lt; 3000 or relation.annual_income / 12 &lt; 3000
) )
and bs.project_id = #{projectId} and bs.project_id = #{projectId}
@@ -782,77 +503,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_DR, 0) > 0 and IFNULL(bs.AMOUNT_DR, 0) > 0
and ( and (
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注' IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|注'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|VIP666|USDT下注' or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌|球|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|捕鱼|电子游艺|注'
) )
</select> </select>
<select id="selectExternalGamblingMemoStatements" resultMap="BankTagStatementHitResultMap">
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
CONCAT(
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
'”摘要/对手方命中疑似赌博关键词,摘要“', IFNULL(bs.USER_MEMO, ''),
'”,对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
'”,交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
) AS reasonDetail
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and (
IFNULL(bs.USER_MEMO, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
or IFNULL(bs.CASH_TYPE, '') REGEXP '游戏|抖币|体彩|福彩|彩票|赌博|赌球|下注|投注|球赛投注|外围|博彩|六合|时时彩|赛车|赌场|筹码|盘口|返水|洗码|庄家|闲家|百家乐|斗牛|炸金花|牌九|麻将|牌局|捕鱼|电子游艺|VIP666|USDT下注'
)
</select>
<select id="selectExternalMultiPartyGamblingTransferObjects" resultMap="BankTagObjectHitResultMap">
select
'EXTERNAL_CERT_NO' AS objectType,
t.certNo AS objectKey,
CONCAT(
'外部人员“', IFNULL(MAX(t.personName), ''),
'”交易日 ', MAX(t.tradeDate),
' 发生 ', CAST(MAX(t.hitCount) AS CHAR),
' 笔疑似赌博交易,涉及 ', CAST(MAX(t.partyCount) AS CHAR),
' 个对手方,金额合计 ', CAST(MAX(t.totalAmount) AS CHAR), ' 元'
) AS reasonDetail
from (
select
source.certNo AS certNo,
max(source.personName) AS personName,
source.tradeDate AS tradeDate,
COUNT(1) AS hitCount,
COUNT(DISTINCT source.customerAccountName) AS partyCount,
ROUND(SUM(source.tradeAmount), 2) AS totalAmount
from (
select
bs.cret_no AS certNo,
IFNULL(bs.LE_ACCOUNT_NAME, '') AS personName,
LEFT(TRIM(bs.TRX_DATE), 10) AS tradeDate,
bs.CUSTOMER_ACCOUNT_NAME AS customerAccountName,
GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS tradeAmount
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) between #{amountMinThreshold} and #{amountMaxThreshold}
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; ''
and (
IFNULL(bs.USER_MEMO, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
or IFNULL(bs.CASH_TYPE, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay|转账|红包|牌局|赌'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '微信|wechat|WeChat|财付通|Tenpay|支付宝|Alipay'
)
) source
group by source.certNo, source.tradeDate
having COUNT(1) > 2
and COUNT(DISTINCT source.customerAccountName) >= 2
) t
group by t.certNo
</select>
<select id="selectSpecialAmountTransactionStatements" resultMap="BankTagStatementHitResultMap"> <select id="selectSpecialAmountTransactionStatements" resultMap="BankTagStatementHitResultMap">
select select
bs.bank_statement_id AS bankStatementId, bs.bank_statement_id AS bankStatementId,
@@ -879,74 +534,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
) )
</select> </select>
<select id="selectExternalToStaffOrFamilyTransactionStatements" resultMap="BankTagStatementHitResultMap">
select distinct
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
CONCAT(
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
'”与', CASE
WHEN counter_account.owner_type = 'EMPLOYEE' THEN '员工'
WHEN counter_account.owner_type = 'RELATION' THEN '员工亲属'
WHEN counter_staff.id_card is not null THEN '员工'
WHEN counter_relation.relation_cert_no is not null THEN '员工亲属'
ELSE '员工/员工亲属'
END,
'“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
'”发生资金往来,交易金额 ',
CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
) AS reasonDetail
from ccdi_bank_statement bs
left join ccdi_account_info counter_account
on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
left join ccdi_base_staff counter_staff
on counter_account.account_no is null
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
left join ccdi_staff_fmy_relation counter_relation
on counter_account.account_no is null
and counter_relation.status = 1
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and (
counter_account.owner_type in ('EMPLOYEE', 'RELATION')
or (
counter_account.account_no is null
and (
counter_staff.id_card is not null
or counter_relation.relation_cert_no is not null
)
)
)
</select>
<select id="selectExternalNightTransactionStatements" resultMap="BankTagStatementHitResultMap">
select
bs.bank_statement_id AS bankStatementId,
bs.group_id AS groupId,
bs.batch_id AS logId,
CONCAT(
'外部人员“', IFNULL(bs.LE_ACCOUNT_NAME, ''),
'”夜间交易,交易时间 ', IFNULL(bs.TRX_DATE, ''),
',对手方“', IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''),
'”,交易金额 ', CAST(GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) AS CHAR), ' 元'
) AS reasonDetail
from ccdi_bank_statement bs
where bs.project_id = #{projectId}
and <include refid="externalPersonPredicateSql"/>
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 0
and (
HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) >= 22
or HOUR(STR_TO_DATE(LEFT(TRIM(bs.TRX_DATE), 19), '%Y-%m-%d %H:%i:%s')) &lt; 6
)
</select>
<select id="selectMonthlyFixedIncomeObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectMonthlyFixedIncomeObjects" resultMap="BankTagObjectHitResultMap">
select select
'STAFF_ID_CARD' AS objectType, 'STAFF_ID_CARD' AS objectType,
@@ -1071,15 +658,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_CR, 0) > 0 and IFNULL(bs.AMOUNT_CR, 0) > 0
and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; '浙江兰溪农村商业银行股份有限公司' and IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') &lt;&gt; '浙江兰溪农村商业银行股份有限公司'
and not (
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') LIKE '%公积金中心%'
and (
IFNULL(bs.USER_MEMO, '') LIKE '%公积金%'
or IFNULL(bs.USER_MEMO, '') LIKE '%批量代付%'
or IFNULL(bs.CASH_TYPE, '') LIKE '%公积金%'
or IFNULL(bs.CASH_TYPE, '') LIKE '%批量代付%'
)
)
and ( and (
IFNULL(bs.USER_MEMO, '') REGEXP '代发|工资|分红|红利|奖金|薪酬|薪金|补贴|薪|年终奖|年金|加班费|劳务费|劳务外包|提成|劳务派遣|绩效|酬劳|批量代付|PAYROLL|SALA|CPF|directors.*fees' IFNULL(bs.USER_MEMO, '') REGEXP '代发|工资|分红|红利|奖金|薪酬|薪金|补贴|薪|年终奖|年金|加班费|劳务费|劳务外包|提成|劳务派遣|绩效|酬劳|批量代付|PAYROLL|SALA|CPF|directors.*fees'
or IFNULL(bs.CASH_TYPE, '') REGEXP '代发|工资|劳务费' or IFNULL(bs.CASH_TYPE, '') REGEXP '代发|工资|劳务费'
@@ -1477,7 +1055,6 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证' or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证' or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证'
) )
and <include refid="financialProductExclusionPredicate"/>
</select> </select>
<select id="selectWithdrawCntObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectWithdrawCntObjects" resultMap="BankTagObjectHitResultMap">
@@ -1497,8 +1074,11 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no inner join ccdi_base_staff staff on staff.id_card = bs.cret_no
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_CR, 0) > 0 and IFNULL(bs.AMOUNT_CR, 0) >= 0
and <include refid="thirdPartyWithdrawIncomePredicate"/> and (
IFNULL(bs.USER_MEMO, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
or IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '财付通|微信零钱|微信|wechat|WeChat|Tenpay|支付宝|Alipay|提现'
)
group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10) group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
having COUNT(1) > #{frequencyThreshold} having COUNT(1) > #{frequencyThreshold}
) t ) t
@@ -1507,25 +1087,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
<select id="selectWithdrawAmtObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectWithdrawAmtObjects" resultMap="BankTagObjectHitResultMap">
select select
'STAFF_ID_CARD' AS objectType, 'STAFF_ID_CARD' AS objectType,
t.objectKey AS objectKey, '' AS objectKey,
CONCAT( '占位SQL待补充真实规则' AS reasonDetail
'单日微信/支付宝提现到账金额 ', CAST(t.withdrawAmount AS CHAR),
' 元,超过阈值 ', CAST(#{amountThreshold} AS CHAR),
' 元,交易日:', t.transDate
) AS reasonDetail
from (
select
staff.id_card AS objectKey,
LEFT(TRIM(bs.TRX_DATE), 10) AS transDate,
ROUND(SUM(IFNULL(bs.AMOUNT_CR, 0)), 2) AS withdrawAmount
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ccdi_base_staff staff on staff.id_card = bs.cret_no where 1 = 0
where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_CR, 0) > 0
and <include refid="thirdPartyWithdrawIncomePredicate"/>
group by staff.id_card, LEFT(TRIM(bs.TRX_DATE), 10)
having SUM(IFNULL(bs.AMOUNT_CR, 0)) > #{amountThreshold}
) t
</select> </select>
<select id="selectSalaryQuickTransferObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectSalaryQuickTransferObjects" resultMap="BankTagObjectHitResultMap">
@@ -1757,11 +1322,10 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
where bs.project_id = #{projectId} where bs.project_id = #{projectId}
and IFNULL(bs.AMOUNT_DR, 0) > #{threshold} and IFNULL(bs.AMOUNT_DR, 0) > #{threshold}
and ( and (
IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管' IFNULL(bs.CUSTOMER_ACCOUNT_NAME, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证' or IFNULL(bs.USER_MEMO, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|资金存管|第三方存管|银证转账|银证|证转银|银转证' or IFNULL(bs.CASH_TYPE, '') REGEXP '证券|国泰君安|中信建投|中金|基金|期货|信托|同花顺|理财|资金存管|第三方存管'
) )
and <include refid="financialProductExclusionPredicate"/>
</select> </select>
<select id="selectProxyAccountOperationObjects" resultMap="BankTagObjectHitResultMap"> <select id="selectProxyAccountOperationObjects" resultMap="BankTagObjectHitResultMap">

View File

@@ -65,15 +65,4 @@ PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
limit 1 limit 1
</select> </select>
<select id="selectLatestFailedTaskByProjectId" resultMap="CcdiBankTagTaskResultMap">
select id, project_id, trigger_type, model_code, status, need_rerun, success_rule_count,
failed_rule_count, hit_count, error_message, start_time, end_time,
create_by, create_time, update_by, update_time
from ccdi_bank_tag_task
where project_id = #{projectId}
and status = 'FAILED'
order by id desc
limit 1
</select>
</mapper> </mapper>

View File

@@ -1,475 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiFundGraphMapper">
<resultMap id="FundGraphNodeResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphNodeVO">
<id property="objectKey" column="objectKey"/>
<result property="nodeKey" column="nodeKey"/>
<result property="nodeName" column="nodeName"/>
<result property="idNo" column="idNo"/>
<result property="cinocsno" column="cinocsno"/>
<result property="idnoType" column="idnoType"/>
<result property="staffId" column="staffId"/>
<result property="sourceType" column="sourceType"/>
<result property="nodeType" column="nodeType"/>
<result property="identityType" column="identityType"/>
<result property="relationType" column="relationType"/>
<result property="accountCount" column="accountCount"/>
<result property="createdTime" column="createdTime"/>
<result property="updatedTime" column="updatedTime"/>
<result property="canExpand" column="canExpand"/>
<result property="depth" column="depth"/>
<result property="totalAmount" column="totalAmount"/>
<result property="transactionCount" column="transactionCount"/>
</resultMap>
<resultMap id="FundGraphEdgeResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphEdgeVO">
<id property="edgeKey" column="edgeKey"/>
<result property="fromKey" column="fromKey"/>
<result property="toKey" column="toKey"/>
<result property="fromObjectKey" column="fromObjectKey"/>
<result property="toObjectKey" column="toObjectKey"/>
<result property="fromName" column="fromName"/>
<result property="toName" column="toName"/>
<result property="totalAmount" column="totalAmount"/>
<result property="transactionCount" column="transactionCount"/>
<result property="firstTrxDate" column="firstTrxDate"/>
<result property="lastTrxDate" column="lastTrxDate"/>
<result property="direction" column="direction"/>
<result property="familyRelationType" column="familyRelationType"/>
<result property="sourceType" column="sourceType"/>
<result property="relationDesc" column="relationDesc"/>
<result property="sourceDesc" column="sourceDesc"/>
<result property="remark" column="remark"/>
<result property="depth" column="depth"/>
<result property="canTrace" column="canTrace"/>
</resultMap>
<resultMap id="FundGraphStatementResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiFundGraphStatementVO">
<id property="bankStatementId" column="bankStatementId"/>
<result property="trxDate" column="trxDate"/>
<result property="leAccountNo" column="leAccountNo"/>
<result property="leAccountName" column="leAccountName"/>
<result property="customerAccountName" column="customerAccountName"/>
<result property="customerAccountNo" column="customerAccountNo"/>
<result property="cashType" column="cashType"/>
<result property="userMemo" column="userMemo"/>
<result property="amount" column="amount"/>
<result property="direction" column="direction"/>
<result property="familyRelationType" column="familyRelationType"/>
</resultMap>
<sql id="detailFilter">
<if test="query.transactionStartTime != null and query.transactionStartTime != ''">
AND d.trx_date <![CDATA[ >= ]]> (#{query.transactionStartTime} COLLATE utf8mb4_general_ci)
</if>
<if test="query.transactionEndTime != null and query.transactionEndTime != ''">
AND d.trx_date <![CDATA[ <= ]]>
(CASE
WHEN LENGTH(TRIM(#{query.transactionEndTime})) = 10
THEN CONCAT(TRIM(#{query.transactionEndTime}), ' 23:59:59')
ELSE TRIM(#{query.transactionEndTime})
END COLLATE utf8mb4_general_ci)
</if>
<if test="query.amountMin != null">
AND d.amount <![CDATA[ >= ]]> #{query.amountMin}
</if>
<if test="query.amountMax != null">
AND d.amount <![CDATA[ <= ]]> #{query.amountMax}
</if>
<if test="query.direction != null and query.direction != ''">
AND d.flag = (#{query.direction} COLLATE utf8mb4_general_ci)
</if>
</sql>
<sql id="subjectJoinRows">
SELECT
d.object_key AS detailObjectKey,
d.bank_statement_id AS bankStatementId,
d.trx_date AS trxDate,
d.le_account_no AS leAccountNo,
d.le_account_name AS leAccountName,
d.customer_account_name AS customerAccountName,
d.customer_account_no AS customerAccountNo,
d.cash_type AS cashType,
d.user_memo AS userMemo,
d.amount,
d.flag AS direction,
d.family_relation_type AS familyRelationType,
from_subject.object_key AS fromObjectKey,
to_subject.object_key AS toObjectKey,
CONCAT('idno_node/', from_subject.object_key) AS fromKey,
CONCAT('idno_node/', to_subject.object_key) AS toKey,
from_subject.name AS fromName,
to_subject.name AS toName,
from_subject.idnocfno AS fromIdNo,
to_subject.idnocfno AS toIdNo
FROM lx_fund_flow_detail_edge d
INNER JOIN lx_fund_flow_own_account_edge from_own
ON from_own.to_key = d.from_key
INNER JOIN lx_fund_flow_subject_node from_subject
ON CONCAT('idno_node/', from_subject.object_key) = from_own.from_key
INNER JOIN lx_fund_flow_own_account_edge to_own
ON to_own.to_key = d.to_key
INNER JOIN lx_fund_flow_subject_node to_subject
ON CONCAT('idno_node/', to_subject.object_key) = to_own.from_key
WHERE 1 = 1
<include refid="detailFilter"/>
</sql>
<sql id="subjectJoinRowsByCenter">
SELECT
d.object_key AS detailObjectKey,
d.bank_statement_id AS bankStatementId,
d.trx_date AS trxDate,
d.le_account_no AS leAccountNo,
d.le_account_name AS leAccountName,
d.customer_account_name AS customerAccountName,
d.customer_account_no AS customerAccountNo,
d.cash_type AS cashType,
d.user_memo AS userMemo,
d.amount,
d.flag AS direction,
d.family_relation_type AS familyRelationType,
center_subject.object_key AS fromObjectKey,
to_subject.object_key AS toObjectKey,
CONCAT('idno_node/', center_subject.object_key) AS fromKey,
CONCAT('idno_node/', to_subject.object_key) AS toKey,
center_subject.name AS fromName,
to_subject.name AS toName,
center_subject.idnocfno AS fromIdNo,
to_subject.idnocfno AS toIdNo
FROM lx_fund_flow_subject_node center_subject
INNER JOIN lx_fund_flow_own_account_edge from_own
ON from_own.from_key = CONCAT('idno_node/', center_subject.object_key)
INNER JOIN lx_fund_flow_detail_edge d
ON d.from_key = from_own.to_key
INNER JOIN lx_fund_flow_own_account_edge to_own
ON to_own.to_key = d.to_key
INNER JOIN lx_fund_flow_subject_node to_subject
ON to_subject.object_key = SUBSTRING(to_own.from_key, 11)
WHERE center_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
<include refid="detailFilter"/>
UNION ALL
SELECT
d.object_key AS detailObjectKey,
d.bank_statement_id AS bankStatementId,
d.trx_date AS trxDate,
d.le_account_no AS leAccountNo,
d.le_account_name AS leAccountName,
d.customer_account_name AS customerAccountName,
d.customer_account_no AS customerAccountNo,
d.cash_type AS cashType,
d.user_memo AS userMemo,
d.amount,
d.flag AS direction,
d.family_relation_type AS familyRelationType,
from_subject.object_key AS fromObjectKey,
center_subject.object_key AS toObjectKey,
CONCAT('idno_node/', from_subject.object_key) AS fromKey,
CONCAT('idno_node/', center_subject.object_key) AS toKey,
from_subject.name AS fromName,
center_subject.name AS toName,
from_subject.idnocfno AS fromIdNo,
center_subject.idnocfno AS toIdNo
FROM lx_fund_flow_subject_node center_subject
INNER JOIN lx_fund_flow_own_account_edge to_own
ON to_own.from_key = CONCAT('idno_node/', center_subject.object_key)
INNER JOIN lx_fund_flow_detail_edge d
ON d.to_key = to_own.to_key
INNER JOIN lx_fund_flow_own_account_edge from_own
ON from_own.to_key = d.from_key
INNER JOIN lx_fund_flow_subject_node from_subject
ON from_subject.object_key = SUBSTRING(from_own.from_key, 11)
WHERE center_subject.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
AND from_subject.object_key != center_subject.object_key
<include refid="detailFilter"/>
</sql>
<sql id="fundGraphSubjectColumns">
n.object_key AS objectKey,
CONCAT('idno_node/', n.object_key) AS nodeKey,
n.name AS nodeName,
n.idnocfno AS idNo,
n.cinocsno AS cinocsno,
n.idno_type AS idnoType,
n.staff_id AS staffId,
n.source_type AS sourceType,
CASE
WHEN n.idno_type = 'NAME_PROXY' OR n.source_type LIKE '%COUNTERPARTY%' THEN 'PROXY'
ELSE 'PERSON'
END AS nodeType,
CASE
WHEN n.idnocfno IS NOT NULL AND TRIM(n.idnocfno) != '' THEN 'IDNO'
ELSE 'NAME'
END AS identityType,
CASE
WHEN n.source_type LIKE 'GRAPH_TEST_FAMILY_%' THEN REPLACE(n.source_type, 'GRAPH_TEST_FAMILY_', '')
ELSE NULL
END AS relationType,
CASE
WHEN EXISTS (
SELECT 1
FROM lx_fund_flow_own_account_edge own
WHERE own.from_key = CONCAT('idno_node/', n.object_key)
) THEN 1
ELSE 0
END AS canExpand,
(
SELECT COUNT(1)
FROM lx_fund_flow_own_account_edge own_count
WHERE own_count.from_key = CONCAT('idno_node/', n.object_key)
) AS accountCount,
DATE_FORMAT(n.created_time, '%Y-%m-%d %H:%i:%s') AS createdTime,
DATE_FORMAT(n.updated_time, '%Y-%m-%d %H:%i:%s') AS updatedTime,
0 AS depth,
0 AS totalAmount,
0 AS transactionCount
</sql>
<select id="selectFundGraphSubjects" resultMap="FundGraphNodeResultMap">
SELECT
<include refid="fundGraphSubjectColumns"/>
FROM lx_fund_flow_subject_node n
WHERE n.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
</select>
<select id="selectFundGraphSubjectsByExactKeyword" resultMap="FundGraphNodeResultMap">
SELECT exact_rows.*
FROM (
SELECT
<include refid="fundGraphSubjectColumns"/>,
0 AS matchOrder
FROM lx_fund_flow_subject_node n
WHERE n.idnocfno = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
UNION ALL
SELECT
<include refid="fundGraphSubjectColumns"/>,
1 AS matchOrder
FROM lx_fund_flow_subject_node n
WHERE n.object_key = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
AND (
n.idnocfno IS NULL
OR n.idnocfno != (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
)
) exact_rows
ORDER BY exact_rows.matchOrder, exact_rows.nodeName
LIMIT
<choose>
<when test="query.limit != null and query.limit > 0">
#{query.limit}
</when>
<otherwise>
20
</otherwise>
</choose>
</select>
<select id="selectFundGraphSubjectsByName" resultMap="FundGraphNodeResultMap">
SELECT
<include refid="fundGraphSubjectColumns"/>
FROM lx_fund_flow_subject_node n
WHERE n.name LIKE (CONCAT('%', TRIM(#{query.keyword}), '%') COLLATE utf8mb4_general_ci)
ORDER BY
CASE
WHEN n.staff_id IS NOT NULL AND TRIM(n.staff_id) != '' THEN 0
WHEN UPPER(IFNULL(n.source_type, '')) LIKE '%EMPLOYEE%' THEN 0
WHEN n.source_type LIKE '%员工%' THEN 0
ELSE 1
END,
n.name
LIMIT
<choose>
<when test="query.limit != null and query.limit > 0">
#{query.limit}
</when>
<otherwise>
20
</otherwise>
</choose>
</select>
<select id="selectFundGraphEdges" resultMap="FundGraphEdgeResultMap">
SELECT
MD5(CONCAT(graph_rows.fromKey, '|', graph_rows.toKey, '|', graph_rows.direction, '|', IFNULL(graph_rows.familyRelationType, ''))) AS edgeKey,
graph_rows.fromKey,
graph_rows.toKey,
graph_rows.fromObjectKey,
graph_rows.toObjectKey,
MAX(graph_rows.fromName) AS fromName,
MAX(graph_rows.toName) AS toName,
SUM(graph_rows.amount) AS totalAmount,
COUNT(1) AS transactionCount,
MIN(graph_rows.trxDate) AS firstTrxDate,
MAX(graph_rows.trxDate) AS lastTrxDate,
graph_rows.direction,
graph_rows.familyRelationType,
'BANK' AS sourceType,
NULL AS relationDesc,
NULL AS sourceDesc,
NULL AS remark,
1 AS depth,
CASE
WHEN MAX(
CASE
WHEN graph_rows.fromObjectKey = #{query.objectKey} THEN
CASE
WHEN graph_rows.toIdNo IS NOT NULL AND TRIM(graph_rows.toIdNo) != '' THEN 1
ELSE 0
END
ELSE
CASE
WHEN graph_rows.fromIdNo IS NOT NULL AND TRIM(graph_rows.fromIdNo) != '' THEN 1
ELSE 0
END
END
) = 1 THEN 1
WHEN EXISTS (
SELECT 1
FROM lx_fund_flow_own_account_edge own
WHERE own.from_key = CASE
WHEN graph_rows.fromObjectKey = #{query.objectKey}
THEN graph_rows.toKey
ELSE graph_rows.fromKey
END
) THEN 1
ELSE 0
END AS canTrace
FROM (
<include refid="subjectJoinRowsByCenter"/>
) graph_rows
WHERE 1 = 1
GROUP BY
graph_rows.fromKey,
graph_rows.toKey,
graph_rows.fromObjectKey,
graph_rows.toObjectKey,
graph_rows.direction,
graph_rows.familyRelationType
<if test="query.minTotalAmount != null">
HAVING SUM(graph_rows.amount) <![CDATA[ >= ]]> #{query.minTotalAmount}
</if>
ORDER BY totalAmount DESC, transactionCount DESC
LIMIT #{query.limit}
</select>
<select id="selectFundGraphManualEdges" resultMap="FundGraphEdgeResultMap">
SELECT
m.object_key AS edgeKey,
CONCAT('idno_node/', m.from_object_key) AS fromKey,
CONCAT('idno_node/', m.to_object_key) AS toKey,
m.from_object_key AS fromObjectKey,
m.to_object_key AS toObjectKey,
COALESCE(from_subject.name, m.from_name) AS fromName,
COALESCE(to_subject.name, m.to_name) AS toName,
m.amount AS totalAmount,
m.transaction_count AS transactionCount,
DATE_FORMAT(m.created_time, '%Y-%m-%d %H:%i:%s') AS firstTrxDate,
DATE_FORMAT(m.created_time, '%Y-%m-%d %H:%i:%s') AS lastTrxDate,
m.direction,
NULL AS familyRelationType,
m.source_type AS sourceType,
m.relation_desc AS relationDesc,
m.source_desc AS sourceDesc,
m.remark,
1 AS depth,
0 AS canTrace
FROM lx_fund_flow_manual_edge m
LEFT JOIN lx_fund_flow_subject_node from_subject
ON from_subject.object_key = m.from_object_key
LEFT JOIN lx_fund_flow_subject_node to_subject
ON to_subject.object_key = m.to_object_key
WHERE m.source_type = 'MANUAL'
AND (
m.from_object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
OR m.to_object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
)
ORDER BY m.updated_time DESC
LIMIT #{query.limit}
</select>
<select id="selectFundGraphEdgeDetails" resultMap="FundGraphStatementResultMap">
SELECT
graph_rows.bankStatementId,
graph_rows.trxDate,
graph_rows.leAccountNo,
graph_rows.leAccountName,
graph_rows.customerAccountName,
graph_rows.customerAccountNo,
graph_rows.cashType,
graph_rows.userMemo,
graph_rows.amount,
graph_rows.direction,
graph_rows.familyRelationType
FROM (
<include refid="subjectJoinRows"/>
) graph_rows
WHERE graph_rows.fromKey = (#{query.fromKey} COLLATE utf8mb4_general_ci)
AND graph_rows.toKey = (#{query.toKey} COLLATE utf8mb4_general_ci)
<if test="query.direction != null and query.direction != ''">
AND graph_rows.direction = (#{query.direction} COLLATE utf8mb4_general_ci)
</if>
ORDER BY graph_rows.trxDate DESC, graph_rows.bankStatementId DESC
</select>
<select id="countSubjectByObjectKey" resultType="int">
SELECT COUNT(1)
FROM lx_fund_flow_subject_node
WHERE object_key = (#{objectKey} COLLATE utf8mb4_general_ci)
</select>
<insert id="insertManualSubject">
INSERT IGNORE INTO lx_fund_flow_subject_node (
object_key,
idnocfno,
name,
idno_type,
source_type
) VALUES (
#{objectKey},
#{idNo},
#{name},
CASE
WHEN #{idNo} IS NULL OR TRIM(#{idNo}) = '' THEN 'NAME_PROXY'
ELSE 'PERSON'
END,
'MANUAL'
)
</insert>
<insert id="insertManualEdge">
INSERT INTO lx_fund_flow_manual_edge (
object_key,
from_object_key,
to_object_key,
from_name,
to_name,
amount,
transaction_count,
direction,
relation_desc,
source_desc,
remark,
source_type,
created_by,
updated_by
) VALUES (
#{objectKey},
#{dto.fromObjectKey},
#{dto.toObjectKey},
#{dto.fromName},
#{dto.toName},
#{dto.amount},
#{dto.transactionCount},
#{dto.direction},
#{dto.relationDesc},
#{dto.sourceDesc},
#{dto.remark},
'MANUAL',
#{operator},
#{operator}
)
</insert>
</mapper>

View File

@@ -40,9 +40,6 @@
FROM ccdi_project p FROM ccdi_project p
LEFT JOIN sys_user u ON p.create_by = u.user_name AND u.del_flag = '0' LEFT JOIN sys_user u ON p.create_by = u.user_name AND u.del_flag = '0'
<where> <where>
<if test="scope != null and !scope.viewAllProjects">
AND p.create_by = #{scope.username}
</if>
<if test="queryDTO.projectName != null and queryDTO.projectName != ''"> <if test="queryDTO.projectName != null and queryDTO.projectName != ''">
AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%') AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
</if> </if>
@@ -64,9 +61,6 @@
FROM ccdi_project p FROM ccdi_project p
<where> <where>
p.status in ('1', '2') p.status in ('1', '2')
<if test="scope != null and !scope.viewAllProjects">
AND p.create_by = #{scope.username}
</if>
<if test="queryDTO.projectName != null and queryDTO.projectName != ''"> <if test="queryDTO.projectName != null and queryDTO.projectName != ''">
AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%') AND p.project_name LIKE CONCAT('%', #{queryDTO.projectName}, '%')
</if> </if>
@@ -76,8 +70,7 @@
<update id="updateRiskCountsByProjectId"> <update id="updateRiskCountsByProjectId">
update ccdi_project update ccdi_project
set target_count = #{targetCount}, set high_risk_count = #{highRiskCount},
high_risk_count = #{highRiskCount},
medium_risk_count = #{mediumRiskCount}, medium_risk_count = #{mediumRiskCount},
low_risk_count = #{lowRiskCount}, low_risk_count = #{lowRiskCount},
update_by = #{updateBy}, update_by = #{updateBy},

View File

@@ -33,38 +33,6 @@
select="selectRiskHitTagsByScope"/> select="selectRiskHitTagsByScope"/>
</resultMap> </resultMap>
<resultMap id="ExternalRiskModelPeopleItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelPeopleItemVO">
<id property="idNo" column="cert_no"/>
<result property="staffName" column="person_name"/>
<result property="staffCode" column="subject_type"/>
<result property="department" column="related_object"/>
<collection property="modelNames"
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
ofType="java.lang.String"
select="selectExternalRiskModelNamesByScope"/>
<collection property="hitTagList"
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
select="selectExternalRiskHitTagsByScope"/>
</resultMap>
<resultMap id="ExternalPersonWarningItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalPersonWarningItemVO">
<result property="name" column="person_name"/>
<result property="idNo" column="cert_no"/>
<result property="subjectType" column="subject_type"/>
<result property="riskLevel" column="risk_level_name"/>
<result property="riskLevelType" column="risk_level_type"/>
<result property="riskCount" column="risk_count"/>
<result property="modelCount" column="model_count"/>
<result property="riskPoint" column="risk_point"/>
<result property="relatedObject" column="related_object"/>
<result property="latestTradeTime" column="latest_trade_time"/>
<collection property="riskPointTagList"
column="{projectId=project_id,certNo=cert_no,selectedModelCodes=selected_model_codes}"
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO"
select="selectExternalRiskHitTagsByScope"/>
</resultMap>
<resultMap id="SuspiciousTransactionItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO"> <resultMap id="SuspiciousTransactionItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectSuspiciousTransactionItemVO">
<id property="bankStatementId" column="bankStatementId"/> <id property="bankStatementId" column="bankStatementId"/>
<result property="trxDate" column="trxDate"/> <result property="trxDate" column="trxDate"/>
@@ -78,7 +46,6 @@
<result property="displayAmount" column="displayAmount"/> <result property="displayAmount" column="displayAmount"/>
<result property="hasModelRuleHit" column="hasModelRuleHit"/> <result property="hasModelRuleHit" column="hasModelRuleHit"/>
<result property="hasNameListHit" column="hasNameListHit"/> <result property="hasNameListHit" column="hasNameListHit"/>
<result property="nameListHitType" column="nameListHitType"/>
</resultMap> </resultMap>
<resultMap id="AbnormalAccountItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO"> <resultMap id="AbnormalAccountItemResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiProjectAbnormalAccountItemVO">
@@ -147,13 +114,6 @@
) tens ) tens
</sql> </sql>
<sql id="externalModelCodeFilterSql">
('EXTERNAL_LARGE_TRANSACTION',
'EXTERNAL_ABNORMAL_TRANSACTION',
'EXTERNAL_SUSPICIOUS_GAMBLING',
'EXTERNAL_SUSPICIOUS_RELATION')
</sql>
<sql id="resolvedEmployeeRiskBaseSql"> <sql id="resolvedEmployeeRiskBaseSql">
select distinct select distinct
tr.id, tr.id,
@@ -533,408 +493,6 @@
order by result.staff_name asc, result.staff_id_card asc order by result.staff_name asc, result.staff_id_card asc
</select> </select>
<select id="selectRiskModelPeopleList" resultMap="RiskModelPeopleItemResultMap">
<bind name="projectId" value="query.projectId"/>
select
result.project_id,
result.staff_id_card,
result.staff_name,
result.staff_code,
result.dept_name as department,
#{query.modelCodesCsv} as selected_model_codes
from ccdi_project_overview_employee_result result
where 1 = 1
and result.project_id = #{query.projectId}
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
<choose>
<when test="query.matchMode == 'ALL'">
<foreach collection="query.modelCodes" item="modelCode">
and find_in_set(#{modelCode}, result.model_codes_csv)
</foreach>
</when>
<otherwise>
and (
<foreach collection="query.modelCodes" item="modelCode" separator=" or ">
find_in_set(#{modelCode}, result.model_codes_csv)
</foreach>
)
</otherwise>
</choose>
</if>
<if test="query.keyword != null and query.keyword != ''">
and (
result.staff_name like concat('%', trim(#{query.keyword}), '%')
or result.staff_code like concat('%', trim(#{query.keyword}), '%')
)
</if>
<if test="query.deptId != null">
and result.dept_id = #{query.deptId}
</if>
order by result.staff_name asc, result.staff_id_card asc
</select>
<sql id="externalPersonSubjectSql">
select
bs.project_id,
bs.cret_no as cert_no,
coalesce(
max(intermediary.name),
max(customer.name),
max(nullif(trim(bs.LE_ACCOUNT_NAME), '')),
'外部人员'
) as person_name,
case
when max(case when intermediary.person_id is not null then 1 else 0 end) > 0 then '中介'
when max(case when customer.person_id is not null then 1 else 0 end) > 0 then '客户'
else '外部人员'
end as subject_type
from ccdi_bank_statement bs
left join ccdi_base_staff staff
on staff.id_card = bs.cret_no
left join ccdi_staff_fmy_relation relation
on relation.status = 1
and relation.relation_cert_no = bs.cret_no
left join ccdi_biz_intermediary intermediary
on intermediary.person_sub_type = '本人'
and intermediary.person_id = bs.cret_no
left join ccdi_credit_customer_base customer
on customer.person_id = bs.cret_no
where bs.project_id = #{externalProjectId}
and bs.cret_no is not null
and trim(bs.cret_no) != ''
and staff.id_card is null
and relation.relation_cert_no is null
group by bs.project_id, bs.cret_no
</sql>
<sql id="externalPersonSourceSql">
select
subject.project_id,
subject.cert_no,
subject.person_name,
subject.subject_type,
bs.bank_statement_id,
bs.TRX_DATE as trx_date,
bs.CUSTOMER_ACCOUNT_NAME as customer_account_name,
bs.customer_cert_no,
case
when counter_account.owner_type = 'EMPLOYEE' then '员工'
when counter_account.owner_type = 'RELATION' then '员工亲属'
when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'
when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'
when counter_staff.id_card is not null then '员工'
when counter_relation.relation_cert_no is not null then '员工亲属'
when counter_intermediary.person_id is not null then '中介库人员'
else null
end as related_object,
tr.model_code,
tr.model_name,
tr.rule_code,
tr.rule_name,
tr.risk_level,
tr.reason_detail
from (
<include refid="externalPersonSubjectSql"/>
) subject
inner join ccdi_bank_statement bs
on bs.project_id = subject.project_id
and bs.cret_no = subject.cert_no
inner join ccdi_bank_statement_tag_result tr
on tr.project_id = bs.project_id
and tr.bank_statement_id = bs.bank_statement_id
and tr.model_code in <include refid="externalModelCodeFilterSql"/>
left join ccdi_account_info counter_account
on trim(bs.CUSTOMER_ACCOUNT_NO) != ''
and counter_account.account_no = trim(bs.CUSTOMER_ACCOUNT_NO)
and counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')
left join ccdi_base_staff counter_staff
on counter_account.account_no is null
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
left join ccdi_staff_fmy_relation counter_relation
on counter_account.account_no is null
and counter_relation.status = 1
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)
left join ccdi_biz_intermediary counter_intermediary
on counter_account.account_no is null
and counter_intermediary.person_sub_type = '本人'
and trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and counter_intermediary.name = trim(bs.CUSTOMER_ACCOUNT_NAME)
where trim(ifnull(bs.LE_ACCOUNT_NAME, '')) != trim(ifnull(bs.CUSTOMER_ACCOUNT_NAME, ''))
union all
select
subject.project_id,
subject.cert_no,
subject.person_name,
subject.subject_type,
null as bank_statement_id,
null as trx_date,
null as customer_account_name,
null as customer_cert_no,
'资金' as related_object,
tr.model_code,
tr.model_name,
tr.rule_code,
tr.rule_name,
tr.risk_level,
tr.reason_detail
from (
<include refid="externalPersonSubjectSql"/>
) subject
inner join ccdi_bank_statement_tag_result tr
on tr.project_id = subject.project_id
and tr.object_type = 'EXTERNAL_CERT_NO'
and tr.object_key = subject.cert_no
and tr.model_code in <include refid="externalModelCodeFilterSql"/>
</sql>
<sql id="externalPersonAggregateSql">
select
source.project_id,
source.cert_no,
max(source.person_name) as person_name,
max(source.subject_type) as subject_type,
count(*) as risk_count,
count(distinct source.model_code) as model_count,
group_concat(distinct source.rule_name order by source.rule_name separator '、') as risk_point,
group_concat(distinct source.related_object order by source.related_object separator '、') as related_object,
max(source.trx_date) as latest_trade_time,
case
when sum(case when source.risk_level = 'HIGH' then 1 else 0 end) > 0 then 'HIGH'
when sum(case when source.risk_level = 'MEDIUM' then 1 else 0 end) > 0 then 'MEDIUM'
else 'LOW'
end as risk_level_code,
null as selected_model_codes
from (
<include refid="externalPersonSourceSql"/>
) source
group by source.project_id, source.cert_no
</sql>
<sql id="externalPersonWarningSelectSql">
select
agg.project_id,
agg.cert_no,
agg.person_name,
agg.subject_type,
agg.risk_count,
agg.model_count,
agg.risk_point,
coalesce(agg.related_object, '-') as related_object,
agg.latest_trade_time,
agg.risk_level_code,
case
when agg.risk_level_code = 'HIGH' then '高风险'
when agg.risk_level_code = 'MEDIUM' then '中风险'
else '低风险'
end as risk_level_name,
case
when agg.risk_level_code = 'HIGH' then 'danger'
when agg.risk_level_code = 'MEDIUM' then 'warning'
else 'info'
end as risk_level_type,
case
when agg.risk_level_code = 'HIGH' then 1
when agg.risk_level_code = 'MEDIUM' then 2
else 3
end as risk_level_sort,
agg.selected_model_codes
from (
<include refid="externalPersonAggregateSql"/>
) agg
</sql>
<select id="selectExternalPersonWarningPage" resultMap="ExternalPersonWarningItemResultMap">
<bind name="externalProjectId" value="query.projectId"/>
select *
from (
<include refid="externalPersonWarningSelectSql"/>
) warning
order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
</select>
<select id="selectExternalPersonWarningList" resultMap="ExternalPersonWarningItemResultMap">
<bind name="externalProjectId" value="projectId"/>
select *
from (
<include refid="externalPersonWarningSelectSql"/>
) warning
order by warning.risk_level_sort asc, warning.model_count desc, warning.risk_count desc, warning.latest_trade_time desc
</select>
<select id="selectExternalRiskSummaryByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectExternalRiskSummaryVO">
<bind name="externalProjectId" value="projectId"/>
select
count(*) as total,
coalesce(sum(case when risk.risk_level_code = 'HIGH' then 1 else 0 end), 0) as high,
coalesce(sum(case when risk.risk_level_code = 'MEDIUM' then 1 else 0 end), 0) as medium,
coalesce(sum(case when risk.risk_level_code = 'LOW' then 1 else 0 end), 0) as low,
coalesce(sum(case when risk.risk_level_code is null then 1 else 0 end), 0) as noRisk
from (
<include refid="externalPersonSubjectSql"/>
) subject
left join (
<include refid="externalPersonAggregateSql"/>
) risk
on risk.project_id = subject.project_id
and risk.cert_no = subject.cert_no
</select>
<select id="selectExternalRiskModelCardsByProjectId" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskModelCardVO">
<bind name="externalProjectId" value="projectId"/>
select
model_scope.model_code,
max(model_scope.model_name) as model_name,
count(*) as warning_count,
count(distinct model_scope.cert_no) as people_count
from (
<include refid="externalPersonSourceSql"/>
) model_scope
group by model_scope.model_code
order by warning_count desc, model_scope.model_code asc
</select>
<select id="selectExternalRiskModelPeoplePage" resultMap="ExternalRiskModelPeopleItemResultMap">
<bind name="externalProjectId" value="query.projectId"/>
select
warning.project_id,
warning.cert_no,
warning.person_name,
warning.subject_type,
warning.related_object,
#{query.modelCodesCsv} as selected_model_codes
from (
<include refid="externalPersonWarningSelectSql"/>
) warning
where 1 = 1
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
<choose>
<when test="query.matchMode == 'ALL'">
<foreach collection="query.modelCodes" item="modelCode">
and exists (
select 1
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = warning.cert_no
and source.model_code = #{modelCode}
)
</foreach>
</when>
<otherwise>
and exists (
select 1
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = warning.cert_no
and source.model_code in
<foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
#{modelCode}
</foreach>
)
</otherwise>
</choose>
</if>
<if test="query.keyword != null and query.keyword != ''">
and (
warning.person_name like concat('%', trim(#{query.keyword}), '%')
or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
)
</if>
order by warning.person_name asc, warning.cert_no asc
</select>
<select id="selectExternalRiskModelPeopleList" resultMap="ExternalRiskModelPeopleItemResultMap">
<bind name="externalProjectId" value="query.projectId"/>
select
warning.project_id,
warning.cert_no,
warning.person_name,
warning.subject_type,
warning.related_object,
#{query.modelCodesCsv} as selected_model_codes
from (
<include refid="externalPersonWarningSelectSql"/>
) warning
where 1 = 1
<if test="query.modelCodes != null and query.modelCodes.size() > 0">
<choose>
<when test="query.matchMode == 'ALL'">
<foreach collection="query.modelCodes" item="modelCode">
and exists (
select 1
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = warning.cert_no
and source.model_code = #{modelCode}
)
</foreach>
</when>
<otherwise>
and exists (
select 1
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = warning.cert_no
and source.model_code in
<foreach collection="query.modelCodes" item="modelCode" open="(" separator="," close=")">
#{modelCode}
</foreach>
)
</otherwise>
</choose>
</if>
<if test="query.keyword != null and query.keyword != ''">
and (
warning.person_name like concat('%', trim(#{query.keyword}), '%')
or warning.cert_no like concat('%', trim(#{query.keyword}), '%')
)
</if>
order by warning.person_name asc, warning.cert_no asc
</select>
<select id="selectExternalRiskModelNamesByScope" resultType="java.lang.String">
<bind name="externalProjectId" value="projectId"/>
select distinct source.model_name
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = #{certNo}
<if test="selectedModelCodes != null and selectedModelCodes != ''">
and find_in_set(source.model_code, #{selectedModelCodes})
</if>
order by source.model_name asc
</select>
<select id="selectExternalRiskHitTagsByScope" resultType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectRiskHitTagVO">
<bind name="externalProjectId" value="projectId"/>
select
source.model_code as modelCode,
max(source.model_name) as modelName,
source.rule_code as ruleCode,
max(source.rule_name) as ruleName,
max(source.risk_level) as riskLevel,
coalesce(
max(case when source.bank_statement_id is null then nullif(source.reason_detail, '') end),
max(nullif(source.reason_detail, ''))
) as reasonDetail
from (
<include refid="externalPersonSourceSql"/>
) source
where source.cert_no = #{certNo}
<if test="selectedModelCodes != null and selectedModelCodes != ''">
and find_in_set(source.model_code, #{selectedModelCodes})
</if>
group by source.model_code, source.rule_code
order by source.model_code asc, source.rule_code asc
</select>
<sql id="suspiciousTransactionBaseSql"> <sql id="suspiciousTransactionBaseSql">
select select
bs.bank_statement_id as bankStatementId, bs.bank_statement_id as bankStatementId,
@@ -972,70 +530,57 @@
from ccdi_bank_statement_tag_result tr from ccdi_bank_statement_tag_result tr
where tr.project_id = #{query.projectId} where tr.project_id = #{query.projectId}
and tr.bank_statement_id is not null and tr.bank_statement_id is not null
and tr.rule_name like '%可疑%'
</sql> </sql>
<sql id="suspiciousTransactionNameHitSql"> <sql id="suspiciousTransactionNameHitSql">
select select
hits.bankStatementId, hits.bankStatementId,
hits.suspiciousPersonName, hits.suspiciousPersonName,
hits.matchPriority, hits.matchPriority
hits.nameListHitType
from ( from (
select
bs.bank_statement_id as bankStatementId,
coalesce(credit_customer.name, account.account_name, '信贷客户账号') as suspiciousPersonName,
1 as matchPriority,
'信贷客户' as nameListHitType
from ccdi_bank_statement bs
inner join ccdi_account_info account
on trim(bs.customer_account_no) != ''
and account.owner_type = 'CREDIT_CUSTOMER'
and account.account_no = bs.customer_account_no
left join ccdi_credit_customer_base credit_customer
on credit_customer.person_id = account.owner_id
where bs.project_id = #{query.projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all
select
bs.bank_statement_id as bankStatementId,
coalesce(intermediary.name, enterprise.enterprise_name, account.account_name, '中介账号') as suspiciousPersonName,
2 as matchPriority,
'中介' as nameListHitType
from ccdi_bank_statement bs
inner join ccdi_account_info account
on trim(bs.customer_account_no) != ''
and account.owner_type = 'INTERMEDIARY'
and account.account_no = bs.customer_account_no
left join ccdi_biz_intermediary intermediary
on intermediary.person_id = account.owner_id
left join ccdi_enterprise_base_info enterprise
on enterprise.social_credit_code = account.owner_id
where bs.project_id = #{query.projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all
select select
bs.bank_statement_id as bankStatementId, bs.bank_statement_id as bankStatementId,
intermediary.name as suspiciousPersonName, intermediary.name as suspiciousPersonName,
3 as matchPriority, 1 as matchPriority
'中介' as nameListHitType
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ccdi_biz_intermediary intermediary inner join ccdi_biz_intermediary intermediary
on trim(bs.CUSTOMER_ACCOUNT_NAME) != '' on trim(bs.customer_cert_no) != ''
and intermediary.name = bs.CUSTOMER_ACCOUNT_NAME and intermediary.person_id = bs.customer_cert_no
where bs.project_id = #{query.projectId} where bs.project_id = #{query.projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
union all union all
select select
bs.bank_statement_id as bankStatementId, bs.bank_statement_id as bankStatementId,
enterprise.enterprise_name as suspiciousPersonName, enterprise.enterprise_name as suspiciousPersonName,
4 as matchPriority, 2 as matchPriority
'中介' as nameListHitType from ccdi_bank_statement bs
inner join ccdi_enterprise_base_info enterprise
on trim(bs.customer_social_credit_code) != ''
and enterprise.social_credit_code = bs.customer_social_credit_code
and enterprise.risk_level = '1'
and enterprise.ent_source = 'INTERMEDIARY'
where bs.project_id = #{query.projectId}
union all
select
bs.bank_statement_id as bankStatementId,
intermediary.name as suspiciousPersonName,
3 as matchPriority
from ccdi_bank_statement bs
inner join ccdi_biz_intermediary intermediary
on trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
and intermediary.name = bs.CUSTOMER_ACCOUNT_NAME
where bs.project_id = #{query.projectId}
union all
select
bs.bank_statement_id as bankStatementId,
enterprise.enterprise_name as suspiciousPersonName,
3 as matchPriority
from ccdi_bank_statement bs from ccdi_bank_statement bs
inner join ccdi_enterprise_base_info enterprise inner join ccdi_enterprise_base_info enterprise
on trim(bs.CUSTOMER_ACCOUNT_NAME) != '' on trim(bs.CUSTOMER_ACCOUNT_NAME) != ''
@@ -1043,38 +588,9 @@
and enterprise.risk_level = '1' and enterprise.risk_level = '1'
and enterprise.ent_source = 'INTERMEDIARY' and enterprise.ent_source = 'INTERMEDIARY'
where bs.project_id = #{query.projectId} where bs.project_id = #{query.projectId}
and GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000
) hits ) hits
</sql> </sql>
<sql id="externalSuspiciousTransactionSql">
select
bs.bank_statement_id as bankStatementId,
bs.TRX_DATE as trxDate,
source.person_name as relatedPersonName,
null as relatedStaffName,
null as relatedStaffCode,
source.subject_type as relationType,
bs.USER_MEMO as userMemo,
bs.CASH_TYPE as cashType,
case
when ifnull(bs.AMOUNT_CR, 0) > 0 then bs.AMOUNT_CR
when ifnull(bs.AMOUNT_DR, 0) > 0 then -bs.AMOUNT_DR
else 0
end as displayAmount,
1 as hasModelRuleHit,
0 as hasNameListHit,
source.person_name as suspiciousPersonName,
9 as matchPriority,
'外部人员预警' as nameListHitType
from (
<bind name="externalProjectId" value="query.projectId"/>
<include refid="externalPersonSourceSql"/>
) source
inner join ccdi_bank_statement bs
on bs.bank_statement_id = source.bank_statement_id
</sql>
<sql id="suspiciousTransactionMergedSql"> <sql id="suspiciousTransactionMergedSql">
select select
base.bankStatementId, base.bankStatementId,
@@ -1089,8 +605,7 @@
1 as hasModelRuleHit, 1 as hasModelRuleHit,
0 as hasNameListHit, 0 as hasNameListHit,
null as suspiciousPersonName, null as suspiciousPersonName,
null as matchPriority, null as matchPriority
null as nameListHitType
from ( from (
<include refid="suspiciousTransactionBaseSql"/> <include refid="suspiciousTransactionBaseSql"/>
) base ) base
@@ -1113,35 +628,13 @@
0 as hasModelRuleHit, 0 as hasModelRuleHit,
1 as hasNameListHit, 1 as hasNameListHit,
name_hits.suspiciousPersonName, name_hits.suspiciousPersonName,
name_hits.matchPriority, name_hits.matchPriority
name_hits.nameListHitType
from ( from (
<include refid="suspiciousTransactionBaseSql"/> <include refid="suspiciousTransactionBaseSql"/>
) base ) base
inner join ( inner join (
<include refid="suspiciousTransactionNameHitSql"/> <include refid="suspiciousTransactionNameHitSql"/>
) name_hits on name_hits.bankStatementId = base.bankStatementId ) name_hits on name_hits.bankStatementId = base.bankStatementId
union all
select
external_hits.bankStatementId,
external_hits.trxDate,
external_hits.relatedPersonName,
external_hits.relatedStaffName,
external_hits.relatedStaffCode,
external_hits.relationType,
external_hits.userMemo,
external_hits.cashType,
external_hits.displayAmount,
external_hits.hasModelRuleHit,
external_hits.hasNameListHit,
external_hits.suspiciousPersonName,
external_hits.matchPriority,
external_hits.nameListHitType
from (
<include refid="externalSuspiciousTransactionSql"/>
) external_hits
</sql> </sql>
<sql id="suspiciousTransactionAggregatedSql"> <sql id="suspiciousTransactionAggregatedSql">
@@ -1170,18 +663,7 @@
max(merged.cashType) as cashType, max(merged.cashType) as cashType,
max(merged.displayAmount) as displayAmount, max(merged.displayAmount) as displayAmount,
max(merged.hasModelRuleHit) as hasModelRuleHit, max(merged.hasModelRuleHit) as hasModelRuleHit,
max(merged.hasNameListHit) as hasNameListHit, max(merged.hasNameListHit) as hasNameListHit
substring_index(
min(
case
when merged.nameListHitType is not null and merged.nameListHitType != ''
then concat(lpad(merged.matchPriority, 2, '0'), '|', merged.nameListHitType)
else null
end
),
'|',
-1
) as nameListHitType
from ( from (
<include refid="suspiciousTransactionMergedSql"/> <include refid="suspiciousTransactionMergedSql"/>
) merged ) merged
@@ -1196,9 +678,6 @@
<when test="query.suspiciousType == 'MODEL_RULE'"> <when test="query.suspiciousType == 'MODEL_RULE'">
where final_result.hasModelRuleHit = 1 where final_result.hasModelRuleHit = 1
</when> </when>
<when test="query.suspiciousType == 'EXTERNAL_PERSON'">
where final_result.nameListHitType = '外部人员预警'
</when>
<otherwise> <otherwise>
where final_result.hasModelRuleHit = 1 or final_result.hasNameListHit = 1 where final_result.hasModelRuleHit = 1 or final_result.hasNameListHit = 1
</otherwise> </otherwise>
@@ -1206,7 +685,7 @@
</sql> </sql>
<select id="selectSuspiciousTransactionPage" resultMap="SuspiciousTransactionItemResultMap"> <select id="selectSuspiciousTransactionPage" resultMap="SuspiciousTransactionItemResultMap">
<!-- ccdi_bank_statement_tag_result --> <!-- rule_name like '%可疑%' -->
<!-- ccdi_biz_intermediary --> <!-- ccdi_biz_intermediary -->
<!-- ccdi_enterprise_base_info --> <!-- ccdi_enterprise_base_info -->
<!-- group by merged.bankStatementId --> <!-- group by merged.bankStatementId -->
@@ -1222,8 +701,7 @@
final_result.cashType, final_result.cashType,
final_result.displayAmount, final_result.displayAmount,
final_result.hasModelRuleHit, final_result.hasModelRuleHit,
final_result.hasNameListHit, final_result.hasNameListHit
final_result.nameListHitType
from ( from (
<include refid="suspiciousTransactionAggregatedSql"/> <include refid="suspiciousTransactionAggregatedSql"/>
) final_result ) final_result
@@ -1244,8 +722,7 @@
final_result.cashType, final_result.cashType,
final_result.displayAmount, final_result.displayAmount,
final_result.hasModelRuleHit, final_result.hasModelRuleHit,
final_result.hasNameListHit, final_result.hasNameListHit
final_result.nameListHitType
from ( from (
<include refid="suspiciousTransactionAggregatedSql"/> <include refid="suspiciousTransactionAggregatedSql"/>
) final_result ) final_result
@@ -1265,21 +742,7 @@
final_result.relatedStaffCode, final_result.relatedStaffCode,
final_result.userMemo, final_result.userMemo,
final_result.cashType, final_result.cashType,
case tag_result.hitTags,
when final_result.nameListHitType = '中介' then
replace(
replace(ifnull(tag_result.hitTags, ''), '与客户之间非正常资金往来', '疑似与中介往来'),
'异常交易',
'疑似与中介往来'
)
when final_result.nameListHitType = '信贷客户' then
replace(
replace(ifnull(tag_result.hitTags, ''), '与客户之间非正常资金往来', '与信贷客户之间非正常资金往来'),
'异常交易',
'与信贷客户之间非正常资金往来'
)
else tag_result.hitTags
end as hitTags,
final_result.displayAmount final_result.displayAmount
from ( from (
<include refid="suspiciousTransactionAggregatedSql"/> <include refid="suspiciousTransactionAggregatedSql"/>
@@ -1444,8 +907,7 @@
max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelName')))) as model_name, max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].modelName')))) as model_name,
json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) as rule_code, json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleCode'))) as rule_code,
max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleName')))) as rule_name, max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].ruleName')))) as rule_name,
max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel')))) as risk_level, max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].riskLevel')))) as risk_level
max(json_unquote(json_extract(result.hit_rules_json, concat('$[', idx.idx, '].reasonDetail')))) as reason_detail
from ccdi_project_overview_employee_result result from ccdi_project_overview_employee_result result
join ( join (
<include refid="jsonArrayIndexSql"/> <include refid="jsonArrayIndexSql"/>
@@ -1567,5 +1029,4 @@
group by base.staff_id_card group by base.staff_id_card
) agg ) agg
</select> </select>
</mapper> </mapper>

View File

@@ -82,7 +82,7 @@
<result property="spouseTotalAsset" column="asset_spouse_total_asset"/> <result property="spouseTotalAsset" column="asset_spouse_total_asset"/>
<result property="totalAsset" column="asset_total_asset"/> <result property="totalAsset" column="asset_total_asset"/>
<collection property="items" <collection property="items"
column="{projectId=project_id,staffIdCard=staff_id_card,spouseIdCard=spouse_id_card,spouseIsStaff=spouse_is_staff}" column="{projectId=project_id,staffIdCard=staff_id_card,spouseIdCard=spouse_id_card}"
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetItemVO" ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyAssetItemVO"
select="selectFamilyAssetItemsByScope"/> select="selectFamilyAssetItemsByScope"/>
</association> </association>
@@ -93,7 +93,7 @@
<result property="spouseTotalDebt" column="debt_spouse_total_debt"/> <result property="spouseTotalDebt" column="debt_spouse_total_debt"/>
<result property="totalDebt" column="debt_total_debt"/> <result property="totalDebt" column="debt_total_debt"/>
<collection property="items" <collection property="items"
column="{projectId=project_id,staffIdCard=staff_id_card,spouseIdCard=spouse_id_card,spouseIsStaff=spouse_is_staff}" column="{projectId=project_id,staffIdCard=staff_id_card,spouseIdCard=spouse_id_card}"
ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtItemVO" ofType="com.ruoyi.ccdi.project.domain.vo.CcdiProjectFamilyDebtItemVO"
select="selectFamilyDebtItemsByScope"/> select="selectFamilyDebtItemsByScope"/>
</association> </association>
@@ -114,55 +114,45 @@
<sql id="projectEmployeeScopeSql"> <sql id="projectEmployeeScopeSql">
select distinct select distinct
statement_staff.id_card as staff_id_card, coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) as staff_id_card,
cast(statement_staff.staff_id as char) as staff_code, cast(coalesce(direct_staff.staff_id, statement_staff.staff_id, family_staff.staff_id) as char) as staff_code,
statement_staff.name as staff_name, coalesce(direct_staff.name, statement_staff.name, family_staff.name) as staff_name,
dept.dept_name dept.dept_name
from ccdi_bank_statement bs from ccdi_bank_statement_tag_result tr
inner join ccdi_base_staff statement_staff left join ccdi_base_staff direct_staff
on statement_staff.id_card = trim(bs.cret_no) on tr.object_type = 'STAFF_ID_CARD'
and tr.object_key = direct_staff.id_card
left join ccdi_bank_statement bs
on tr.bank_statement_id = bs.bank_statement_id
left join ccdi_base_staff statement_staff
on (tr.object_key is null or tr.object_key = '')
and bs.cret_no = statement_staff.id_card
left join ccdi_staff_fmy_relation relation
on relation.status = 1
and (
((tr.object_key is null or tr.object_key = '') and bs.cret_no = relation.relation_cert_no)
or ((tr.object_key is not null and tr.object_key != '') and tr.object_type != 'STAFF_ID_CARD'
and tr.object_key = relation.relation_cert_no)
)
left join ccdi_base_staff family_staff
on relation.person_id = family_staff.id_card
left join sys_dept dept left join sys_dept dept
on dept.dept_id = statement_staff.dept_id on dept.dept_id = coalesce(direct_staff.dept_id, statement_staff.dept_id, family_staff.dept_id)
where bs.project_id = #{projectId} where tr.project_id = #{projectId}
and bs.cret_no is not null and coalesce(direct_staff.id_card, statement_staff.id_card, family_staff.id_card) is not null
and trim(bs.cret_no) != ''
</sql> </sql>
<sql id="spouseRelationSql"> <sql id="spouseRelationSql">
select select
relation_pair.person_id, person_id,
max(relation_pair.spouse_name) as spouse_name, max(relation_name) as spouse_name,
min(relation_pair.spouse_id_card) as spouse_id_card, min(relation_cert_no) as spouse_id_card,
max(coalesce(spouse_staff.annual_income, relation_pair.spouse_relation_income, 0)) as spouse_income, max(annual_income) as spouse_income
max(case when spouse_staff.id_card is not null then 1 else 0 end) as spouse_is_staff from ccdi_staff_fmy_relation
from ( where status = 1
select and is_emp_family = 1
relation.person_id, and relation_type = '配偶'
relation.relation_name as spouse_name, group by person_id
relation.relation_cert_no as spouse_id_card,
relation.annual_income as spouse_relation_income
from ccdi_staff_fmy_relation relation
where relation.status = 1
and relation.is_emp_family = 1
and relation.relation_type = '配偶'
union all
select
relation.relation_cert_no as person_id,
base_staff.name as spouse_name,
relation.person_id as spouse_id_card,
null as spouse_relation_income
from ccdi_staff_fmy_relation relation
inner join ccdi_base_staff current_staff
on current_staff.id_card = relation.relation_cert_no
left join ccdi_base_staff base_staff
on base_staff.id_card = relation.person_id
where relation.status = 1
and relation.is_emp_family = 1
and relation.relation_type = '配偶'
) relation_pair
left join ccdi_base_staff spouse_staff
on spouse_staff.id_card = relation_pair.spouse_id_card
group by relation_pair.person_id
</sql> </sql>
<select id="selectFamilyAssetLiabilityList" resultMap="FamilyAssetLiabilityListItemResultMap"> <select id="selectFamilyAssetLiabilityList" resultMap="FamilyAssetLiabilityListItemResultMap">
@@ -176,42 +166,19 @@
aggregated.total_debt, aggregated.total_debt,
aggregated.comparison_amount, aggregated.comparison_amount,
case case
when aggregated.missing_asset_info = 1 or aggregated.missing_debt_info = 1 then 'MISSING_INFO' when aggregated.self_asset_record_count = 0 or aggregated.self_debt_record_count = 0 then 'MISSING_INFO'
when comparison_amount &lt;= total_asset * 1.5 then 'NORMAL' when comparison_amount &lt;= total_asset * 1.5 then 'NORMAL'
when comparison_amount &gt; total_asset * 1.5 and comparison_amount &lt;= total_asset * 3 then 'RISK' when comparison_amount &gt; total_asset * 1.5 and comparison_amount &lt;= total_asset * 3 then 'RISK'
when comparison_amount &gt; total_asset * 3 then 'HIGH' when comparison_amount &gt; total_asset * 3 then 'HIGH'
else 'HIGH' else 'HIGH'
end as risk_level_code, end as risk_level_code,
case case
when aggregated.missing_asset_info = 1 or aggregated.missing_debt_info = 1 then '缺少信息' when aggregated.self_asset_record_count = 0 or aggregated.self_debt_record_count = 0 then '缺少信息'
when comparison_amount &lt;= total_asset * 1.5 then '正常' when comparison_amount &lt;= total_asset * 1.5 then '正常'
when comparison_amount &gt; total_asset * 1.5 and comparison_amount &lt;= total_asset * 3 then '存在风险' when comparison_amount &gt; total_asset * 1.5 and comparison_amount &lt;= total_asset * 3 then '存在风险'
when comparison_amount &gt; total_asset * 3 then '高风险' when comparison_amount &gt; total_asset * 3 then '高风险'
else '高风险' else '高风险'
end as risk_level_name end as risk_level_name
from (
select
source.*,
case
when source.self_asset_record_count = 0
or source.spouse_staff_asset_record_count = 0 then 1
else 0
end as missing_asset_info,
case
when source.self_debt_record_count = 0
or source.spouse_staff_debt_record_count = 0 then 1
else 0
end as missing_debt_info,
case
when source.self_asset_record_count = 0
or source.spouse_staff_asset_record_count = 0
or source.self_debt_record_count = 0
or source.spouse_staff_debt_record_count = 0 then 4
when source.comparison_amount &lt;= source.total_asset * 1.5 then 1
when source.comparison_amount &lt;= source.total_asset * 3 then 2
when source.comparison_amount &gt; source.total_asset * 3 then 3
else 3
end as risk_level_sort
from ( from (
select select
scope.staff_id_card, scope.staff_id_card,
@@ -225,46 +192,18 @@
where asset.family_id = scope.staff_id_card where asset.family_id = scope.staff_id_card
and asset.person_id = scope.staff_id_card and asset.person_id = scope.staff_id_card
), 0) as self_asset_record_count, ), 0) as self_asset_record_count,
case
when spouse.spouse_is_staff = 1 then coalesce((
select count(1)
from ccdi_asset_info asset
where asset.family_id = spouse.spouse_id_card
and asset.person_id = spouse.spouse_id_card
), 0)
else 1
end as spouse_staff_asset_record_count,
coalesce(( coalesce((
select count(1) select count(1)
from ccdi_debts_info debt from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card where debt.person_id = scope.staff_id_card
), 0) as self_debt_record_count, ), 0) as self_debt_record_count,
case
when spouse.spouse_is_staff = 1 then coalesce((
select count(1)
from ccdi_debts_info debt
where debt.person_id = spouse.spouse_id_card
), 0)
else 1
end as spouse_staff_debt_record_count,
coalesce(( coalesce((
select sum(coalesce(asset.current_value, 0)) select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset from ccdi_asset_info asset
where (asset.family_id = scope.staff_id_card and asset.person_id = scope.staff_id_card) where asset.family_id = scope.staff_id_card
or (
spouse.spouse_id_card is not null
and ( and (
( asset.person_id = scope.staff_id_card
spouse.spouse_is_staff = 1 or (spouse.spouse_id_card is not null and asset.person_id = spouse.spouse_id_card)
and asset.family_id = spouse.spouse_id_card
and asset.person_id = spouse.spouse_id_card
)
or (
(spouse.spouse_is_staff is null or spouse.spouse_is_staff != 1)
and asset.family_id = scope.staff_id_card
and asset.person_id = spouse.spouse_id_card
)
)
) )
), 0) as total_asset, ), 0) as total_asset,
coalesce(( coalesce((
@@ -280,7 +219,75 @@
from ccdi_debts_info debt from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card where debt.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and debt.person_id = spouse.spouse_id_card) or (spouse.spouse_id_card is not null and debt.person_id = spouse.spouse_id_card)
), 0) as comparison_amount ), 0) as comparison_amount,
case
when coalesce((
select count(1)
from ccdi_asset_info asset
where asset.family_id = scope.staff_id_card
and asset.person_id = scope.staff_id_card
), 0) = 0
or coalesce((
select count(1)
from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card
), 0) = 0 then 4
when (
coalesce(base_staff.annual_income, 0)
+ coalesce(spouse.spouse_income, 0)
+ coalesce((
select sum(coalesce(debt.principal_balance, 0))
from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and debt.person_id = spouse.spouse_id_card)
), 0)
) &lt;= coalesce((
select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset
where asset.family_id = scope.staff_id_card
and (
asset.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and asset.person_id = spouse.spouse_id_card)
)
), 0) * 1.5 then 1
when (
coalesce(base_staff.annual_income, 0)
+ coalesce(spouse.spouse_income, 0)
+ coalesce((
select sum(coalesce(debt.principal_balance, 0))
from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and debt.person_id = spouse.spouse_id_card)
), 0)
) &lt;= coalesce((
select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset
where asset.family_id = scope.staff_id_card
and (
asset.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and asset.person_id = spouse.spouse_id_card)
)
), 0) * 3 then 2
when (
coalesce(base_staff.annual_income, 0)
+ coalesce(spouse.spouse_income, 0)
+ coalesce((
select sum(coalesce(debt.principal_balance, 0))
from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and debt.person_id = spouse.spouse_id_card)
), 0)
) &gt; coalesce((
select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset
where asset.family_id = scope.staff_id_card
and (
asset.person_id = scope.staff_id_card
or (spouse.spouse_id_card is not null and asset.person_id = spouse.spouse_id_card)
)
), 0) * 3 then 3
else 3
end as risk_level_sort
from ( from (
<include refid="projectEmployeeScopeSql"/> <include refid="projectEmployeeScopeSql"/>
) scope ) scope
@@ -290,7 +297,6 @@
<include refid="spouseRelationSql"/> <include refid="spouseRelationSql"/>
) spouse ) spouse
on spouse.person_id = scope.staff_id_card on spouse.person_id = scope.staff_id_card
) source
) aggregated ) aggregated
order by risk_level_sort desc, comparison_amount desc, staff_name asc order by risk_level_sort desc, comparison_amount desc, staff_name asc
</select> </select>
@@ -300,7 +306,6 @@
aggregated.project_id, aggregated.project_id,
aggregated.staff_id_card, aggregated.staff_id_card,
aggregated.spouse_id_card, aggregated.spouse_id_card,
aggregated.spouse_is_staff,
aggregated.staff_code, aggregated.staff_code,
aggregated.staff_name, aggregated.staff_name,
aggregated.dept_name, aggregated.dept_name,
@@ -337,19 +342,6 @@
when comparison_amount &gt; total_asset * 3 then '高风险' when comparison_amount &gt; total_asset * 3 then '高风险'
else '高风险' else '高风险'
end as summary_risk_level_name end as summary_risk_level_name
from (
select
source.*,
case
when source.self_asset_record_count = 0
or source.spouse_staff_asset_record_count = 0 then 1
else 0
end as missing_self_asset_info,
case
when source.self_debt_record_count = 0
or source.spouse_staff_debt_record_count = 0 then 1
else 0
end as missing_self_debt_info
from ( from (
select select
#{projectId} as project_id, #{projectId} as project_id,
@@ -358,25 +350,18 @@
scope.staff_name, scope.staff_name,
scope.dept_name, scope.dept_name,
spouse.spouse_id_card, spouse.spouse_id_card,
spouse.spouse_is_staff,
coalesce(base_staff.annual_income, 0) as self_income, coalesce(base_staff.annual_income, 0) as self_income,
coalesce(spouse.spouse_income, 0) as spouse_income, coalesce(spouse.spouse_income, 0) as spouse_income,
coalesce(base_staff.annual_income, 0) + coalesce(spouse.spouse_income, 0) as total_income, coalesce(base_staff.annual_income, 0) + coalesce(spouse.spouse_income, 0) as total_income,
coalesce(( case
when coalesce((
select count(1) select count(1)
from ccdi_asset_info asset from ccdi_asset_info asset
where asset.family_id = scope.staff_id_card where asset.family_id = scope.staff_id_card
and asset.person_id = scope.staff_id_card and asset.person_id = scope.staff_id_card
), 0) as self_asset_record_count, ), 0) = 0 then 1
case else 0
when spouse.spouse_is_staff = 1 then coalesce(( end as missing_self_asset_info,
select count(1)
from ccdi_asset_info asset
where asset.family_id = spouse.spouse_id_card
and asset.person_id = spouse.spouse_id_card
), 0)
else 1
end as spouse_staff_asset_record_count,
coalesce(( coalesce((
select sum(coalesce(asset.current_value, 0)) select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset from ccdi_asset_info asset
@@ -386,53 +371,27 @@
coalesce(( coalesce((
select sum(coalesce(asset.current_value, 0)) select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset from ccdi_asset_info asset
where spouse.spouse_id_card is not null where asset.family_id = scope.staff_id_card
and ( and spouse.spouse_id_card is not null
(
spouse.spouse_is_staff = 1
and asset.family_id = spouse.spouse_id_card
and asset.person_id = spouse.spouse_id_card and asset.person_id = spouse.spouse_id_card
)
or (
(spouse.spouse_is_staff is null or spouse.spouse_is_staff != 1)
and asset.family_id = scope.staff_id_card
and asset.person_id = spouse.spouse_id_card
)
)
), 0) as spouse_total_asset, ), 0) as spouse_total_asset,
coalesce(( coalesce((
select sum(coalesce(asset.current_value, 0)) select sum(coalesce(asset.current_value, 0))
from ccdi_asset_info asset from ccdi_asset_info asset
where (asset.family_id = scope.staff_id_card and asset.person_id = scope.staff_id_card) where asset.family_id = scope.staff_id_card
or (
spouse.spouse_id_card is not null
and ( and (
( asset.person_id = scope.staff_id_card
spouse.spouse_is_staff = 1 or (spouse.spouse_id_card is not null and asset.person_id = spouse.spouse_id_card)
and asset.family_id = spouse.spouse_id_card
and asset.person_id = spouse.spouse_id_card
)
or (
(spouse.spouse_is_staff is null or spouse.spouse_is_staff != 1)
and asset.family_id = scope.staff_id_card
and asset.person_id = spouse.spouse_id_card
)
)
) )
), 0) as total_asset, ), 0) as total_asset,
coalesce(( case
when coalesce((
select count(1) select count(1)
from ccdi_debts_info debt from ccdi_debts_info debt
where debt.person_id = scope.staff_id_card where debt.person_id = scope.staff_id_card
), 0) as self_debt_record_count, ), 0) = 0 then 1
case else 0
when spouse.spouse_is_staff = 1 then coalesce(( end as missing_self_debt_info,
select count(1)
from ccdi_debts_info debt
where debt.person_id = spouse.spouse_id_card
), 0)
else 1
end as spouse_staff_debt_record_count,
coalesce(( coalesce((
select sum(coalesce(debt.principal_balance, 0)) select sum(coalesce(debt.principal_balance, 0))
from ccdi_debts_info debt from ccdi_debts_info debt
@@ -468,7 +427,6 @@
) spouse ) spouse
on spouse.person_id = scope.staff_id_card on spouse.person_id = scope.staff_id_card
where scope.staff_id_card = #{staffIdCard} where scope.staff_id_card = #{staffIdCard}
) source
) aggregated ) aggregated
</select> </select>
@@ -479,15 +437,7 @@
asset.asset_sub_type, asset.asset_sub_type,
case case
when asset.person_id = #{staffIdCard} then base_staff.name when asset.person_id = #{staffIdCard} then base_staff.name
else coalesce(holder_staff.name, ( else spouse.relation_name
select max(relation.relation_name)
from ccdi_staff_fmy_relation relation
where relation.person_id = #{staffIdCard}
and relation.relation_cert_no = asset.person_id
and relation.status = 1
and relation.is_emp_family = 1
and relation.relation_type = '配偶'
))
end as holder_name, end as holder_name,
asset.person_id as holder_id_card, asset.person_id as holder_id_card,
asset.current_value, asset.current_value,
@@ -495,23 +445,15 @@
from ccdi_asset_info asset from ccdi_asset_info asset
left join ccdi_base_staff base_staff left join ccdi_base_staff base_staff
on base_staff.id_card = #{staffIdCard} on base_staff.id_card = #{staffIdCard}
left join ccdi_base_staff holder_staff left join ccdi_staff_fmy_relation spouse
on holder_staff.id_card = asset.person_id on spouse.person_id = #{staffIdCard}
where (asset.family_id = #{staffIdCard} and asset.person_id = #{staffIdCard}) and spouse.status = 1
or ( and spouse.relation_type = '配偶'
#{spouseIdCard} is not null and spouse.relation_cert_no = asset.person_id
where asset.family_id = #{staffIdCard}
and ( and (
( asset.person_id = #{staffIdCard}
#{spouseIsStaff} = 1 or (#{spouseIdCard} is not null and asset.person_id = #{spouseIdCard})
and asset.family_id = #{spouseIdCard}
and asset.person_id = #{spouseIdCard}
)
or (
(#{spouseIsStaff} is null or #{spouseIsStaff} != 1)
and asset.family_id = #{staffIdCard}
and asset.person_id = #{spouseIdCard}
)
)
) )
order by order by
case when asset.person_id = #{staffIdCard} then 1 else 2 end, case when asset.person_id = #{staffIdCard} then 1 else 2 end,
@@ -527,15 +469,7 @@
debt.creditor_type, debt.creditor_type,
case case
when debt.person_id = #{staffIdCard} then base_staff.name when debt.person_id = #{staffIdCard} then base_staff.name
else coalesce(owner_staff.name, ( else spouse.relation_name
select max(relation.relation_name)
from ccdi_staff_fmy_relation relation
where relation.person_id = #{staffIdCard}
and relation.relation_cert_no = debt.person_id
and relation.status = 1
and relation.is_emp_family = 1
and relation.relation_type = '配偶'
))
end as owner_name, end as owner_name,
debt.person_id as owner_id_card, debt.person_id as owner_id_card,
debt.principal_balance, debt.principal_balance,
@@ -543,8 +477,11 @@
from ccdi_debts_info debt from ccdi_debts_info debt
left join ccdi_base_staff base_staff left join ccdi_base_staff base_staff
on base_staff.id_card = #{staffIdCard} on base_staff.id_card = #{staffIdCard}
left join ccdi_base_staff owner_staff left join ccdi_staff_fmy_relation spouse
on owner_staff.id_card = debt.person_id on spouse.person_id = #{staffIdCard}
and spouse.status = 1
and spouse.relation_type = '配偶'
and spouse.relation_cert_no = debt.person_id
where debt.person_id = #{staffIdCard} where debt.person_id = #{staffIdCard}
or (#{spouseIdCard} is not null and debt.person_id = #{spouseIdCard}) or (#{spouseIdCard} is not null and debt.person_id = #{spouseIdCard})
order by order by

View File

@@ -1,330 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.ccdi.project.mapper.CcdiRelationGraphMapper">
<resultMap id="RelationGraphNodeResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphNodeVO">
<id property="objectKey" column="objectKey"/>
<result property="nodeKey" column="nodeKey"/>
<result property="nodeName" column="nodeName"/>
<result property="idNumber" column="idNumber"/>
<result property="subjectType" column="subjectType"/>
<result property="sourceType" column="sourceType"/>
<result property="createdTime" column="createdTime"/>
<result property="updatedTime" column="updatedTime"/>
<result property="canExpand" column="canExpand"/>
<result property="depth" column="depth"/>
</resultMap>
<resultMap id="RelationGraphEdgeResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphEdgeVO">
<id property="objectKey" column="objectKey"/>
<result property="fromKey" column="fromKey"/>
<result property="toKey" column="toKey"/>
<result property="edgeTable" column="edgeTable"/>
<result property="relationType" column="relationType"/>
<result property="companyName" column="companyName"/>
<result property="stockName" column="stockName"/>
<result property="stockType" column="stockType"/>
<result property="stockPercent" column="stockPercent"/>
<result property="shouldCapi" column="shouldCapi"/>
<result property="shouldCapiValue" column="shouldCapiValue"/>
<result property="shouldCapiUnit" column="shouldCapiUnit"/>
<result property="shoudDate" column="shoudDate"/>
<result property="pKeyNo" column="pKeyNo"/>
<result property="operName" column="operName"/>
<result property="operKeyNo" column="operKeyNo"/>
<result property="personId" column="personId"/>
<result property="relationName" column="relationName"/>
<result property="relationCertNo" column="relationCertNo"/>
<result property="gender" column="gender"/>
<result property="birthDate" column="birthDate"/>
<result property="relationCertType" column="relationCertType"/>
<result property="mobilePhone1" column="mobilePhone1"/>
<result property="mobilePhone2" column="mobilePhone2"/>
<result property="wechatNo1" column="wechatNo1"/>
<result property="wechatNo2" column="wechatNo2"/>
<result property="wechatNo3" column="wechatNo3"/>
<result property="contactAddress" column="contactAddress"/>
<result property="annualIncome" column="annualIncome"/>
<result property="relationDesc" column="relationDesc"/>
<result property="status" column="status"/>
<result property="effectiveDate" column="effectiveDate"/>
<result property="invalidDate" column="invalidDate"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="dataSource"/>
</resultMap>
<resultMap id="SuspectedEnterpriseResultMap" type="com.ruoyi.ccdi.project.domain.vo.CcdiRelationGraphSuspectedEnterpriseItemVO">
<result property="candidateKeyNo" column="candidateKeyNo"/>
<result property="personName" column="personName"/>
<result property="companyId" column="companyId"/>
<result property="companyName" column="companyName"/>
<result property="creditCode" column="creditCode"/>
<result property="enterpriseStatus" column="enterpriseStatus"/>
<result property="industryName" column="industryName"/>
<result property="relationType" column="relationType"/>
<result property="stockPercent" column="stockPercent"/>
<result property="establishDate" column="establishDate"/>
</resultMap>
<sql id="nodeColumns">
n.object_key AS objectKey,
CONCAT('rel_node/', n.object_key) AS nodeKey,
n.node_name AS nodeName,
n.id_number AS idNumber,
n.subject_type AS subjectType,
n.source_type AS sourceType,
DATE_FORMAT(n.created_time, '%Y-%m-%d %H:%i:%s') AS createdTime,
DATE_FORMAT(n.updated_time, '%Y-%m-%d %H:%i:%s') AS updatedTime,
1 AS canExpand,
0 AS depth
</sql>
<select id="selectRelationGraphSubjects" resultMap="RelationGraphNodeResultMap">
SELECT
<include refid="nodeColumns"/>
FROM lx_rel_node n
WHERE 1 = 1
<if test="query.objectKey != null and query.objectKey != ''">
AND n.object_key = (#{query.objectKey} COLLATE utf8mb4_general_ci)
</if>
<if test="query.objectKey == null or query.objectKey == ''">
<if test="query.keyword != null and query.keyword != ''">
AND (
n.object_key = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
OR n.id_number = (TRIM(#{query.keyword}) COLLATE utf8mb4_general_ci)
OR n.node_name LIKE (CONCAT('%', TRIM(#{query.keyword}), '%') COLLATE utf8mb4_general_ci)
)
</if>
</if>
ORDER BY
CASE
WHEN n.object_key = (TRIM(IFNULL(#{query.keyword}, '')) COLLATE utf8mb4_general_ci) THEN 0
WHEN n.id_number = (TRIM(IFNULL(#{query.keyword}, '')) COLLATE utf8mb4_general_ci) THEN 1
ELSE 2
END,
n.node_name
LIMIT 20
</select>
<select id="selectRelationGraphNodesByKeys" resultMap="RelationGraphNodeResultMap">
SELECT
<include refid="nodeColumns"/>
FROM lx_rel_node n
WHERE n.object_key IN
<foreach collection="objectKeys" item="objectKey" open="(" separator="," close=")">
#{objectKey}
</foreach>
</select>
<select id="selectRelationGraphEdges" resultMap="RelationGraphEdgeResultMap">
SELECT *
FROM (
SELECT
e.object_key AS objectKey,
e.from_key AS fromKey,
e.to_key AS toKey,
'lx_rel_family_edge' AS edgeTable,
e.relation_type AS relationType,
NULL AS companyName,
NULL AS stockName,
NULL AS stockType,
NULL AS stockPercent,
NULL AS shouldCapi,
NULL AS shouldCapiValue,
NULL AS shouldCapiUnit,
NULL AS shoudDate,
NULL AS pKeyNo,
NULL AS operName,
NULL AS operKeyNo,
e.person_id AS personId,
e.relation_name AS relationName,
e.relation_cert_no AS relationCertNo,
NULL AS gender,
NULL AS birthDate,
NULL AS relationCertType,
NULL AS mobilePhone1,
NULL AS mobilePhone2,
NULL AS wechatNo1,
NULL AS wechatNo2,
NULL AS wechatNo3,
NULL AS contactAddress,
NULL AS annualIncome,
e.relation_desc AS relationDesc,
NULL AS status,
NULL AS effectiveDate,
NULL AS invalidDate,
NULL AS remark,
NULL AS dataSource
FROM lx_rel_family_edge e
WHERE e.from_key = CONCAT('rel_node/', #{query.objectKey})
OR e.to_key = CONCAT('rel_node/', #{query.objectKey})
UNION ALL
SELECT
e.object_key AS objectKey,
e.from_key AS fromKey,
e.to_key AS toKey,
'lx_rel_stock_edge' AS edgeTable,
e.stock_type AS relationType,
e.company_name AS companyName,
e.stock_name AS stockName,
e.stock_type AS stockType,
e.stock_percent AS stockPercent,
e.should_capi AS shouldCapi,
e.should_capi_value AS shouldCapiValue,
e.should_capi_unit AS shouldCapiUnit,
e.shoud_date AS shoudDate,
e.p_key_no AS pKeyNo,
NULL AS operName,
NULL AS operKeyNo,
NULL AS personId,
NULL AS relationName,
NULL AS relationCertNo,
NULL AS gender,
NULL AS birthDate,
NULL AS relationCertType,
NULL AS mobilePhone1,
NULL AS mobilePhone2,
NULL AS wechatNo1,
NULL AS wechatNo2,
NULL AS wechatNo3,
NULL AS contactAddress,
NULL AS annualIncome,
NULL AS relationDesc,
NULL AS status,
NULL AS effectiveDate,
NULL AS invalidDate,
NULL AS remark,
NULL AS dataSource
FROM lx_rel_stock_edge e
WHERE e.from_key = CONCAT('rel_node/', #{query.objectKey})
OR e.to_key = CONCAT('rel_node/', #{query.objectKey})
UNION ALL
SELECT
e.object_key AS objectKey,
e.from_key AS fromKey,
e.to_key AS toKey,
'lx_rel_represent_edge' AS edgeTable,
'法定代表人' AS relationType,
NULL AS companyName,
NULL AS stockName,
NULL AS stockType,
NULL AS stockPercent,
NULL AS shouldCapi,
NULL AS shouldCapiValue,
NULL AS shouldCapiUnit,
NULL AS shoudDate,
NULL AS pKeyNo,
e.oper_name AS operName,
e.oper_key_no AS operKeyNo,
NULL AS personId,
NULL AS relationName,
NULL AS relationCertNo,
NULL AS gender,
NULL AS birthDate,
NULL AS relationCertType,
NULL AS mobilePhone1,
NULL AS mobilePhone2,
NULL AS wechatNo1,
NULL AS wechatNo2,
NULL AS wechatNo3,
NULL AS contactAddress,
NULL AS annualIncome,
NULL AS relationDesc,
NULL AS status,
NULL AS effectiveDate,
NULL AS invalidDate,
NULL AS remark,
NULL AS dataSource
FROM lx_rel_represent_edge e
WHERE e.from_key = CONCAT('rel_node/', #{query.objectKey})
OR e.to_key = CONCAT('rel_node/', #{query.objectKey})
) graph_edges
ORDER BY
CASE edgeTable
WHEN 'lx_rel_family_edge' THEN 0
WHEN 'lx_rel_stock_edge' THEN 1
ELSE 2
END,
relationType,
objectKey
LIMIT #{query.limit}
</select>
<select id="countSuspectedEnterpriseKeyNos" resultType="int">
SELECT COUNT(DISTINCT candidate_key_no)
FROM (
SELECT e.oper_key_no AS candidate_key_no
FROM lx_rel_represent_edge e
WHERE e.oper_name = (#{personName} COLLATE utf8mb4_general_ci)
AND e.oper_key_no IS NOT NULL
AND e.oper_key_no != ''
UNION ALL
SELECT e.p_key_no AS candidate_key_no
FROM lx_rel_stock_edge e
WHERE e.stock_name = (#{personName} COLLATE utf8mb4_general_ci)
AND e.stock_type = '自然人股东'
AND e.p_key_no IS NOT NULL
AND e.p_key_no != ''
) same_name_candidates
</select>
<select id="selectSuspectedEnterprises" resultMap="SuspectedEnterpriseResultMap">
SELECT *
FROM (
SELECT
e.oper_key_no AS candidateKeyNo,
e.oper_name AS personName,
REPLACE(e.to_key, 'rel_node/', '') AS companyId,
COALESCE(ent.enterprise_name, company_node.node_name) AS companyName,
COALESCE(ent.social_credit_code, company_node.id_number) AS creditCode,
ent.status AS enterpriseStatus,
ent.industry_name AS industryName,
'法定代表人' AS relationType,
NULL AS stockPercent,
DATE_FORMAT(ent.establish_date, '%Y-%m-%d') AS establishDate,
0 AS relationSort
FROM lx_rel_represent_edge e
LEFT JOIN lx_rel_node company_node
ON company_node.object_key = REPLACE(e.to_key, 'rel_node/', '')
LEFT JOIN ccdi_enterprise_base_info ent
ON ent.social_credit_code = company_node.id_number
WHERE e.oper_name = (#{personName} COLLATE utf8mb4_general_ci)
AND e.oper_key_no IS NOT NULL
AND e.oper_key_no != ''
UNION ALL
SELECT
e.p_key_no AS candidateKeyNo,
e.stock_name AS personName,
REPLACE(e.to_key, 'rel_node/', '') AS companyId,
COALESCE(ent.enterprise_name, e.company_name, company_node.node_name) AS companyName,
COALESCE(ent.social_credit_code, company_node.id_number) AS creditCode,
ent.status AS enterpriseStatus,
ent.industry_name AS industryName,
e.stock_type AS relationType,
e.stock_percent AS stockPercent,
COALESCE(DATE_FORMAT(ent.establish_date, '%Y-%m-%d'), e.shoud_date) AS establishDate,
1 AS relationSort
FROM lx_rel_stock_edge e
LEFT JOIN lx_rel_node company_node
ON company_node.object_key = REPLACE(e.to_key, 'rel_node/', '')
LEFT JOIN ccdi_enterprise_base_info ent
ON ent.social_credit_code = company_node.id_number
WHERE e.stock_name = (#{personName} COLLATE utf8mb4_general_ci)
AND e.stock_type = '自然人股东'
AND e.p_key_no IS NOT NULL
AND e.p_key_no != ''
) suspected_enterprises
ORDER BY relationSort, companyName, companyId
LIMIT #{limit}
</select>
</mapper>

View File

@@ -281,8 +281,7 @@ class CcdiProjectOverviewControllerTest {
MockHttpServletResponse response = new MockHttpServletResponse(); MockHttpServletResponse response = new MockHttpServletResponse();
CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO(); CcdiProjectSuspiciousTransactionQueryDTO queryDTO = new CcdiProjectSuspiciousTransactionQueryDTO();
CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel(); CcdiProjectSuspiciousTransactionExcel row = new CcdiProjectSuspiciousTransactionExcel();
row.setLeAccountName("张三"); row.setSuspiciousPersonName("张三");
row.setCustomerAccountName("测试对手方");
row.setDisplayAmount(new java.math.BigDecimal("10.00")); row.setDisplayAmount(new java.math.BigDecimal("10.00"));
when(overviewService.exportSuspiciousTransactions(same(queryDTO))).thenReturn(List.of(row)); when(overviewService.exportSuspiciousTransactions(same(queryDTO))).thenReturn(List.of(row));

View File

@@ -104,26 +104,4 @@ class CcdiBankStatementTest {
assertEquals("330101199001011234", entity.getCustomerCertNo()); assertEquals("330101199001011234", entity.getCustomerCertNo());
assertEquals("91330100123456789X", entity.getCustomerSocialCreditCode()); assertEquals("91330100123456789X", entity.getCustomerSocialCreditCode());
} }
@Test
void testFromResponse_ShouldNormalizeCretNoBeforeDash() {
BankStatementItem item = new BankStatementItem();
item.setCretNo(" 330100198801010033 - 测试人员 ");
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
assertNotNull(entity);
assertEquals("330100198801010033", entity.getCretNo());
}
@Test
void testFromResponse_ShouldNormalizeCretNoWithChineseDash() {
BankStatementItem item = new BankStatementItem();
item.setCretNo("330100198801010033测试人员");
CcdiBankStatement entity = CcdiBankStatement.fromResponse(item);
assertNotNull(entity);
assertEquals("330100198801010033", entity.getCretNo());
}
} }

View File

@@ -220,20 +220,6 @@ class CcdiBankStatementMapperXmlTest {
} }
} }
@Test
void targetCount_shouldOnlyUseStatementCretNoMatchedStaff() throws Exception {
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(RESOURCE)) {
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);
String selectSql = extractSelect(xml, "countMatchedStaffCountByProjectId");
assertTrue(selectSql.contains("select count(distinct trim(bs.cret_no))"), selectSql);
assertTrue(selectSql.contains("inner join ccdi_base_staff staff on staff.id_card = trim(bs.cret_no)"), selectSql);
assertFalse(selectSql.contains("ccdi_staff_fmy_relation"), selectSql);
assertFalse(selectSql.contains("ccdi_account_info"), selectSql);
assertFalse(selectSql.contains("LE_ACCOUNT_NO"), selectSql);
}
}
private MappedStatement loadMappedStatement(String statementId) throws Exception { private MappedStatement loadMappedStatement(String statementId) throws Exception {
Configuration configuration = new Configuration(); Configuration configuration = new Configuration();
configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource())); configuration.setEnvironment(new Environment("test", new JdbcTransactionFactory(), new NoOpDataSource()));
@@ -256,15 +242,6 @@ class CcdiBankStatementMapperXmlTest {
return boundSql.getSql().replaceAll("\\s+", " ").trim(); return boundSql.getSql().replaceAll("\\s+", " ").trim();
} }
private String extractSelect(String xml, String selectId) {
String start = "<select id=\"" + selectId + "\"";
int startIndex = xml.indexOf(start);
assertTrue(startIndex >= 0, "missing select: " + selectId);
int endIndex = xml.indexOf("</select>", startIndex);
assertTrue(endIndex >= 0, "missing closing select tag: " + selectId);
return xml.substring(startIndex, endIndex);
}
private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) { private void registerTypeAliases(TypeAliasRegistry typeAliasRegistry) {
typeAliasRegistry.registerAlias("map", Map.class); typeAliasRegistry.registerAlias("map", Map.class);
} }

View File

@@ -13,7 +13,6 @@ import java.util.regex.Pattern;
import static org.junit.jupiter.api.Assertions.assertAll; import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals; import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertFalse;
import static org.junit.jupiter.api.Assertions.assertTrue; import static org.junit.jupiter.api.Assertions.assertTrue;
class CcdiBankTagAnalysisMapperXmlTest { class CcdiBankTagAnalysisMapperXmlTest {
@@ -27,18 +26,11 @@ class CcdiBankTagAnalysisMapperXmlTest {
"selectForexSellAmtStatements", "selectForexSellAmtStatements",
"selectLargePurchaseTransactionStatements", "selectLargePurchaseTransactionStatements",
"selectStockTfrLargeStatements", "selectStockTfrLargeStatements",
"selectLargeStockTradingStatements", "selectLargeStockTradingStatements"
"selectExternalSingleLargeAmountStatements",
"selectExternalNightTransactionStatements",
"selectExternalGamblingMemoStatements",
"selectExternalToStaffOrFamilyTransactionStatements"
); );
private static final List<String> PHASE_TWO_OBJECT_SELECT_IDS = List.of( private static final List<String> PHASE_TWO_OBJECT_SELECT_IDS = List.of(
"selectLowIncomeRelativeLargeTransactionObjects", "selectLowIncomeRelativeLargeTransactionObjects",
"selectMultiPartyGamblingTransferObjects", "selectMultiPartyGamblingTransferObjects",
"selectExternalCumulativeTransactionAmountObjects",
"selectExternalAnnualTurnoverObjects",
"selectExternalMultiPartyGamblingTransferObjects",
"selectMonthlyFixedIncomeObjects", "selectMonthlyFixedIncomeObjects",
"selectFixedCounterpartyTransferObjects", "selectFixedCounterpartyTransferObjects",
"selectSupplierConcentrationObjects", "selectSupplierConcentrationObjects",
@@ -108,10 +100,7 @@ class CcdiBankTagAnalysisMapperXmlTest {
void placeholderRules_shouldUseEmptyResultSqlTemplate() throws Exception { void placeholderRules_shouldUseEmptyResultSqlTemplate() throws Exception {
String xml = readXml(RESOURCE); String xml = readXml(RESOURCE);
assertTrue(xml.contains("占位SQL待补充真实规则")); assertTrue(xml.contains("占位SQL待补充真实规则"));
assertEquals( assertEquals(6, countMatches(xml, "where 1 = 0"));
countMatches(xml, "占位SQL待补充真实规则"),
countMatches(xml, "where 1 = 0")
);
} }
@Test @Test
@@ -127,31 +116,6 @@ class CcdiBankTagAnalysisMapperXmlTest {
} }
} }
@Test
void lowIncomeRelativeRule_shouldIgnoreNullAnnualIncome() throws Exception {
String xml = readXml(RESOURCE);
String selectSql = extractSelectSql(xml, "selectLowIncomeRelativeLargeTransactionObjects");
assertTrue(selectSql.contains("relation.annual_income is not null"));
assertTrue(!selectSql.contains("relation.annual_income is null"));
}
@Test
void abnormalCustomerTransactionRule_shouldUseCreditCustomerAndIntermediaryAccountRules() throws Exception {
String xml = readXml(RESOURCE);
String selectSql = extractSelectSql(xml, "selectAbnormalCustomerTransactionStatements");
assertTrue(selectSql.contains("account.owner_type = 'CREDIT_CUSTOMER'"));
assertTrue(selectSql.contains("account.owner_type = 'INTERMEDIARY'"));
assertTrue(selectSql.contains("account.account_no = bs.customer_account_no"));
assertTrue(selectSql.contains("enterprise.ent_source = 'INTERMEDIARY'"));
assertTrue(selectSql.contains("intermediary.name = bs.CUSTOMER_ACCOUNT_NAME"));
assertTrue(selectSql.contains("bs.CUSTOMER_ACCOUNT_NAME like concat('%', intermediary.name, '%')"));
assertTrue(selectSql.contains("bs.bank in ('ALIPAY', 'WECHAT')"));
assertEquals(5, countMatches(selectSql, "GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000"));
assertTrue(!selectSql.contains("customer_cert_no"));
assertTrue(!selectSql.contains("social_credit_code = bs"));
}
@Test @Test
void withdrawCntObjectRule_shouldUseRealSqlAndKeepObjectHitFields() throws Exception { void withdrawCntObjectRule_shouldUseRealSqlAndKeepObjectHitFields() throws Exception {
String xml = readXml(RESOURCE); String xml = readXml(RESOURCE);
@@ -167,11 +131,7 @@ class CcdiBankTagAnalysisMapperXmlTest {
String xml = readXml(RESOURCE); String xml = readXml(RESOURCE);
for (String selectId : PHASE_TWO_OBJECT_SELECT_IDS) { for (String selectId : PHASE_TWO_OBJECT_SELECT_IDS) {
String selectSql = extractSelectSql(xml, selectId); String selectSql = extractSelectSql(xml, selectId);
assertTrue( assertTrue(selectSql.contains("'STAFF_ID_CARD' AS objectType"), () -> selectId + " 缺少 objectType");
selectSql.contains("'STAFF_ID_CARD' AS objectType")
|| selectSql.contains("'EXTERNAL_CERT_NO' AS objectType"),
() -> selectId + " 缺少 objectType"
);
assertTrue(selectSql.contains("AS objectKey"), () -> selectId + " 缺少 objectKey"); assertTrue(selectSql.contains("AS objectKey"), () -> selectId + " 缺少 objectKey");
assertTrue(selectSql.contains("reasonDetail"), () -> selectId + " 缺少 reasonDetail"); assertTrue(selectSql.contains("reasonDetail"), () -> selectId + " 缺少 reasonDetail");
assertTrue(!selectSql.contains("where 1 = 0"), () -> selectId + " 仍是占位 SQL"); assertTrue(!selectSql.contains("where 1 = 0"), () -> selectId + " 仍是占位 SQL");
@@ -236,31 +196,6 @@ class CcdiBankTagAnalysisMapperXmlTest {
factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml))); factory.newDocumentBuilder().parse(new InputSource(new StringReader(xml)));
} }
@Test
void externalPersonRules_shouldUseExternalSubjectScopeAndCounterpartyEmployeeMatching() throws Exception {
String xml = readXml(RESOURCE);
String scopeSql = extractSqlFragment(xml, "externalPersonPredicateSql");
String relationSql = extractSelectSql(xml, "selectExternalToStaffOrFamilyTransactionStatements");
String annualSql = extractSelectSql(xml, "selectExternalAnnualTurnoverObjects");
String gamblingSql = extractSelectSql(xml, "selectExternalMultiPartyGamblingTransferObjects");
assertTrue(scopeSql.contains("bs.cret_no is not null"));
assertTrue(scopeSql.contains("staff.id_card = bs.cret_no"));
assertTrue(scopeSql.contains("relation.relation_cert_no = bs.cret_no"));
assertTrue(scopeSql.contains("trim(IFNULL(bs.LE_ACCOUNT_NAME, '')) &lt;&gt; trim(IFNULL(bs.CUSTOMER_ACCOUNT_NAME, ''))"));
assertFalse(scopeSql.contains("LE_ACCOUNT_NO"));
assertTrue(annualSql.contains("'EXTERNAL_CERT_NO' AS objectType"));
assertTrue(annualSql.contains("bs.cret_no AS certNo"));
assertTrue(gamblingSql.contains("'EXTERNAL_CERT_NO' AS objectType"));
assertTrue(gamblingSql.contains("having COUNT(1) > 2"));
assertTrue(relationSql.contains("counter_account.owner_type in ('EMPLOYEE', 'RELATION', 'INTERMEDIARY', 'CREDIT_CUSTOMER')"));
assertTrue(relationSql.contains("on counter_account.account_no is null"));
assertTrue(relationSql.contains("counter_staff.name = trim(bs.CUSTOMER_ACCOUNT_NAME)"));
assertTrue(relationSql.contains("counter_relation.relation_name = trim(bs.CUSTOMER_ACCOUNT_NAME)"));
assertTrue(relationSql.contains("counter_account.owner_type in ('EMPLOYEE', 'RELATION')"));
assertFalse(relationSql.contains("customer_cert_no"));
}
private String readXml(String resource) throws Exception { private String readXml(String resource) throws Exception {
try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource)) { try (InputStream inputStream = getClass().getClassLoader().getResourceAsStream(resource)) {
return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8); return new String(inputStream.readAllBytes(), StandardCharsets.UTF_8);

View File

@@ -90,77 +90,13 @@ class CcdiProjectOverviewMapperSqlTest {
void shouldExposeSuspiciousTransactionAggregationQuery() throws Exception { void shouldExposeSuspiciousTransactionAggregationQuery() throws Exception {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml")); String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
String suspiciousSql = extractSelect(xml, "selectSuspiciousTransactionPage"); String suspiciousSql = extractSelect(xml, "selectSuspiciousTransactionPage");
String modelHitSql = extractSqlFragment(xml, "suspiciousTransactionModelHitSql");
String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");
assertTrue(modelHitSql.contains("from ccdi_bank_statement_tag_result tr"), modelHitSql); assertTrue(suspiciousSql.contains("rule_name like '%可疑%'"), suspiciousSql);
assertTrue(modelHitSql.contains("tr.bank_statement_id is not null"), modelHitSql);
assertFalse(modelHitSql.contains("rule_name like '%可疑%'"), modelHitSql);
assertFalse(modelHitSql.contains("ABNORMAL_CUSTOMER_TRANSACTION"), modelHitSql);
assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"), suspiciousSql); assertTrue(suspiciousSql.contains("ccdi_biz_intermediary"), suspiciousSql);
assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"), suspiciousSql); assertTrue(suspiciousSql.contains("ccdi_enterprise_base_info"), suspiciousSql);
assertTrue(suspiciousSql.contains("group by merged.bankStatementId"), suspiciousSql); assertTrue(suspiciousSql.contains("group by merged.bankStatementId"), suspiciousSql);
assertTrue(aggregatedSql.contains("lpad(merged.matchPriority, 2, '0')"), aggregatedSql);
assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql); assertTrue(suspiciousSql.contains("hasModelRuleHit"), suspiciousSql);
assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql); assertTrue(suspiciousSql.contains("hasNameListHit"), suspiciousSql);
assertTrue(suspiciousSql.contains("final_result.nameListHitType"), suspiciousSql);
String reportSuspiciousSql = extractSelect(xml, "selectReportSuspiciousTransactionList");
assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '中介'"), reportSuspiciousSql);
assertTrue(reportSuspiciousSql.contains("疑似与中介往来"), reportSuspiciousSql);
assertTrue(reportSuspiciousSql.contains("final_result.nameListHitType = '信贷客户'"), reportSuspiciousSql);
assertTrue(reportSuspiciousSql.contains("与信贷客户之间非正常资金往来"), reportSuspiciousSql);
}
@Test
void externalPersonSourceSql_shouldIncludeStatementAndObjectHits() throws Exception {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
String externalSourceSql = extractSqlFragment(xml, "externalPersonSourceSql");
String normalizedExternalSourceSql = externalSourceSql.replace("\r\n", "\n");
assertTrue(externalSourceSql.contains("tr.bank_statement_id = bs.bank_statement_id"), externalSourceSql);
assertTrue(externalSourceSql.contains("union all"), externalSourceSql);
assertTrue(externalSourceSql.contains("tr.object_type = 'EXTERNAL_CERT_NO'"), externalSourceSql);
assertTrue(externalSourceSql.contains("tr.object_key = subject.cert_no"), externalSourceSql);
assertTrue(externalSourceSql.contains("'资金' as related_object"), externalSourceSql);
assertFalse(externalSourceSql.contains("counter_staff.id_card = bs.customer_cert_no"), externalSourceSql);
assertFalse(externalSourceSql.contains("counter_relation.relation_cert_no = bs.customer_cert_no"), externalSourceSql);
assertFalse(externalSourceSql.contains("counter_intermediary.person_id = bs.customer_cert_no"), externalSourceSql);
assertInOrder(
externalSourceSql,
"when counter_account.owner_type = 'EMPLOYEE' then '员工'",
"when counter_account.owner_type = 'RELATION' then '员工亲属'",
"when counter_account.owner_type = 'CREDIT_CUSTOMER' then '信贷客户'",
"when counter_account.owner_type = 'INTERMEDIARY' then '中介库人员'",
"when counter_staff.id_card is not null then '员工'",
"when counter_relation.relation_cert_no is not null then '员工亲属'"
);
assertTrue(normalizedExternalSourceSql.contains("left join ccdi_base_staff counter_staff\n on counter_account.account_no is null"), externalSourceSql);
assertTrue(normalizedExternalSourceSql.contains("left join ccdi_staff_fmy_relation counter_relation\n on counter_account.account_no is null"), externalSourceSql);
}
@Test
void suspiciousTransactionNameListSql_shouldKeepCreditCustomerAndIntermediaryRulesScopedByAmount() throws Exception {
String xml = Files.readString(Path.of("src/main/resources/mapper/ccdi/project/CcdiProjectOverviewMapper.xml"));
String nameHitSql = extractSqlFragment(xml, "suspiciousTransactionNameHitSql");
String aggregatedSql = extractSqlFragment(xml, "suspiciousTransactionAggregatedSql");
assertTrue(nameHitSql.contains("account.owner_type = 'CREDIT_CUSTOMER'"), nameHitSql);
assertTrue(nameHitSql.contains("account.owner_type = 'INTERMEDIARY'"), nameHitSql);
assertTrue(nameHitSql.contains("account.account_no = bs.customer_account_no"), nameHitSql);
assertTrue(nameHitSql.contains("'信贷客户' as nameListHitType"), nameHitSql);
assertTrue(nameHitSql.contains("'中介' as nameListHitType"), nameHitSql);
assertTrue(nameHitSql.contains("intermediary.name = bs.CUSTOMER_ACCOUNT_NAME"), nameHitSql);
assertTrue(nameHitSql.contains("enterprise.ent_source = 'INTERMEDIARY'"), nameHitSql);
assertTrue(
nameHitSql.contains("GREATEST(IFNULL(bs.AMOUNT_DR, 0), IFNULL(bs.AMOUNT_CR, 0)) > 1000"),
nameHitSql
);
assertFalse(nameHitSql.contains("customer_cert_no"), nameHitSql);
assertFalse(nameHitSql.contains("social_credit_code = bs"), nameHitSql);
assertTrue(aggregatedSql.contains("group by merged.bankStatementId"), aggregatedSql);
assertTrue(aggregatedSql.contains("max(merged.hasModelRuleHit) as hasModelRuleHit"), aggregatedSql);
assertTrue(aggregatedSql.contains("max(merged.hasNameListHit) as hasNameListHit"), aggregatedSql);
} }
@Test @Test
@@ -223,22 +159,4 @@ class CcdiProjectOverviewMapperSqlTest {
assertTrue(endIndex >= 0, "missing closing select tag: " + selectId); assertTrue(endIndex >= 0, "missing closing select tag: " + selectId);
return xml.substring(startIndex, endIndex); return xml.substring(startIndex, endIndex);
} }
private String extractSqlFragment(String xml, String sqlId) {
String start = "<sql id=\"" + sqlId + "\"";
int startIndex = xml.indexOf(start);
assertTrue(startIndex >= 0, "missing sql fragment: " + sqlId);
int endIndex = xml.indexOf("</sql>", startIndex);
assertTrue(endIndex >= 0, "missing closing sql tag: " + sqlId);
return xml.substring(startIndex, endIndex);
}
private void assertInOrder(String sql, String... fragments) {
int previousIndex = -1;
for (String fragment : fragments) {
int currentIndex = sql.indexOf(fragment);
assertTrue(currentIndex > previousIndex, () -> "fragment order mismatch: " + fragment + "\n" + sql);
previousIndex = currentIndex;
}
}
} }

View File

@@ -17,13 +17,6 @@ class CcdiProjectSpecialCheckMapperDetailSqlTest {
assertTrue(xml.contains("select id=\"selectFamilyAssetItemsByScope\"")); assertTrue(xml.contains("select id=\"selectFamilyAssetItemsByScope\""));
assertTrue(xml.contains("select id=\"selectFamilyDebtItemsByScope\"")); assertTrue(xml.contains("select id=\"selectFamilyDebtItemsByScope\""));
assertTrue(xml.contains("scope.staff_id_card = #{staffIdCard}")); assertTrue(xml.contains("scope.staff_id_card = #{staffIdCard}"));
assertTrue(xml.contains("spouseIsStaff=spouse_is_staff"));
assertTrue(xml.contains("relation.relation_cert_no as person_id"));
assertTrue(xml.contains("spouse_staff.annual_income"));
assertTrue(xml.contains("spouse.spouse_is_staff = 1"));
assertTrue(xml.contains("asset.family_id = spouse.spouse_id_card"));
assertTrue(xml.contains("source.spouse_staff_asset_record_count = 0"));
assertTrue(xml.contains("source.spouse_staff_debt_record_count = 0"));
assertTrue(xml.contains("incomeDetail")); assertTrue(xml.contains("incomeDetail"));
assertTrue(xml.contains("assetDetail")); assertTrue(xml.contains("assetDetail"));
assertTrue(xml.contains("debtDetail")); assertTrue(xml.contains("debtDetail"));
@@ -38,9 +31,6 @@ class CcdiProjectSpecialCheckMapperDetailSqlTest {
assertTrue(xml.contains("asset_main_type")); assertTrue(xml.contains("asset_main_type"));
assertTrue(xml.contains("asset_sub_type")); assertTrue(xml.contains("asset_sub_type"));
assertTrue(xml.contains("holder_name")); assertTrue(xml.contains("holder_name"));
assertTrue(xml.contains("holder_staff.name"));
assertTrue(xml.contains("select max(relation.relation_name)"));
assertTrue(xml.contains("relation.relation_cert_no = asset.person_id"));
assertTrue(xml.contains("current_value")); assertTrue(xml.contains("current_value"));
assertTrue(xml.contains("valuation_date")); assertTrue(xml.contains("valuation_date"));
assertTrue(xml.contains("debt_name")); assertTrue(xml.contains("debt_name"));
@@ -48,8 +38,6 @@ class CcdiProjectSpecialCheckMapperDetailSqlTest {
assertTrue(xml.contains("debt_sub_type")); assertTrue(xml.contains("debt_sub_type"));
assertTrue(xml.contains("creditor_type")); assertTrue(xml.contains("creditor_type"));
assertTrue(xml.contains("owner_name")); assertTrue(xml.contains("owner_name"));
assertTrue(xml.contains("owner_staff.name"));
assertTrue(xml.contains("relation.relation_cert_no = debt.person_id"));
assertTrue(xml.contains("principal_balance")); assertTrue(xml.contains("principal_balance"));
assertTrue(xml.contains("query_date")); assertTrue(xml.contains("query_date"));
assertFalse(xml.contains("ccdi_project_overview_employee_result")); assertFalse(xml.contains("ccdi_project_overview_employee_result"));

View File

@@ -15,31 +15,15 @@ class CcdiProjectSpecialCheckMapperListSqlTest {
String listSql = extractSelect(xml, "selectFamilyAssetLiabilityList"); String listSql = extractSelect(xml, "selectFamilyAssetLiabilityList");
assertTrue(listSql.contains("order by risk_level_sort desc, comparison_amount desc, staff_name asc")); assertTrue(listSql.contains("order by risk_level_sort desc, comparison_amount desc, staff_name asc"));
assertTrue(xml.contains("from ccdi_bank_statement bs")); assertTrue(xml.contains("from ccdi_bank_statement_tag_result"));
assertTrue(xml.contains("statement_staff.id_card = trim(bs.cret_no)"));
assertTrue(xml.contains("bs.project_id = #{projectId}"));
assertTrue(xml.contains("ccdi_base_staff")); assertTrue(xml.contains("ccdi_base_staff"));
assertTrue(xml.contains("ccdi_staff_fmy_relation")); assertTrue(xml.contains("ccdi_staff_fmy_relation"));
assertTrue(xml.contains("relation_type = '配偶'")); assertTrue(xml.contains("relation_type = '配偶'"));
assertTrue(xml.contains("union all"));
assertTrue(xml.contains("relation.relation_cert_no as person_id"));
assertTrue(xml.contains("inner join ccdi_base_staff current_staff"));
assertTrue(xml.contains("spouse_staff.annual_income"));
assertTrue(xml.contains("spouse_is_staff"));
assertTrue(xml.contains("annual_income")); assertTrue(xml.contains("annual_income"));
assertTrue(xml.contains("current_value")); assertTrue(xml.contains("current_value"));
assertTrue(xml.contains("principal_balance")); assertTrue(xml.contains("principal_balance"));
assertTrue(listSql.contains("self_asset_record_count")); assertTrue(listSql.contains("self_asset_record_count"));
assertTrue(listSql.contains("spouse_staff_asset_record_count"));
assertTrue(listSql.contains("self_debt_record_count")); assertTrue(listSql.contains("self_debt_record_count"));
assertTrue(listSql.contains("spouse_staff_debt_record_count"));
assertTrue(listSql.contains("source.self_asset_record_count = 0"));
assertTrue(listSql.contains("source.spouse_staff_asset_record_count = 0"));
assertTrue(listSql.contains("source.self_debt_record_count = 0"));
assertTrue(listSql.contains("source.spouse_staff_debt_record_count = 0"));
assertTrue(listSql.contains("asset.family_id = spouse.spouse_id_card"));
assertTrue(listSql.contains("asset.person_id = spouse.spouse_id_card"));
assertTrue(listSql.contains("debt.person_id = spouse.spouse_id_card"));
assertTrue(listSql.contains("then 'MISSING_INFO'")); assertTrue(listSql.contains("then 'MISSING_INFO'"));
assertTrue(listSql.contains("then '缺少信息'")); assertTrue(listSql.contains("then '缺少信息'"));
assertTrue(listSql.contains("comparison_amount")); assertTrue(listSql.contains("comparison_amount"));

View File

@@ -132,49 +132,6 @@ class BankTagRuleConfigResolverTest {
assertEquals("8888", config.getThresholdValue("ANNUAL_TURNOVER")); assertEquals("8888", config.getThresholdValue("ANNUAL_TURNOVER"));
} }
@Test
void resolve_shouldUseLargeTransactionParamsForExternalLargeTransactionRule() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
project.setConfigType("default");
when(projectMapper.selectById(40L)).thenReturn(project);
when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
buildParam("LARGE_TRANSACTION", "FREQUENT_TRANSFER", "100000")
));
CcdiBankTagRule ruleMeta = new CcdiBankTagRule();
ruleMeta.setModelCode("EXTERNAL_LARGE_TRANSACTION");
ruleMeta.setRuleCode("EXTERNAL_SINGLE_LARGE_AMOUNT");
ruleMeta.setIndicatorCode("FREQUENT_TRANSFER");
BankTagRuleExecutionConfig config = resolver.resolve(40L, ruleMeta);
assertEquals("100000", config.getThresholdValue("FREQUENT_TRANSFER"));
}
@Test
void resolve_shouldUseOriginalModelParamsForExternalObjectRules() {
CcdiProject project = new CcdiProject();
project.setProjectId(40L);
project.setConfigType("default");
when(projectMapper.selectById(40L)).thenReturn(project);
when(modelParamMapper.selectByProjectAndModel(0L, "LARGE_TRANSACTION")).thenReturn(List.of(
buildParam("LARGE_TRANSACTION", "CUMULATIVE_TRANSACTION_AMOUNT", "500000"),
buildParam("LARGE_TRANSACTION", "ANNUAL_TURNOVER", "800000")
));
when(modelParamMapper.selectByProjectAndModel(0L, "SUSPICIOUS_GAMBLING")).thenReturn(List.of(
buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MIN", "500"),
buildParam("SUSPICIOUS_GAMBLING", "MULTI_PARTY_AMT_MAX", "5000")
));
assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT",
Map.of("CUMULATIVE_TRANSACTION_AMOUNT", "500000"));
assertRuleThresholds("EXTERNAL_LARGE_TRANSACTION", "EXTERNAL_ANNUAL_TURNOVER",
Map.of("ANNUAL_TURNOVER", "800000"));
assertRuleThresholds("EXTERNAL_SUSPICIOUS_GAMBLING", "EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER",
Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000"));
}
@Test @Test
void resolve_shouldMapPhaseOneThresholdRulesToUppercaseParamCodes() { void resolve_shouldMapPhaseOneThresholdRulesToUppercaseParamCodes() {
CcdiProject project = new CcdiProject(); CcdiProject project = new CcdiProject();

View File

@@ -317,7 +317,7 @@ class CcdiBankTagServiceImplTest {
} }
@Test @Test
void shouldMarkProjectTagFailedWhenRebuildFails() { void shouldRollbackProjectStatusToProcessingWhenRebuildFails() {
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run); ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易", CcdiBankTagRule rule = buildRule("LARGE_TRANSACTION", "大额交易",
@@ -329,7 +329,7 @@ class CcdiBankTagServiceImplTest {
assertThrows(RuntimeException.class, assertThrows(RuntimeException.class,
() -> service.rebuildProject(40L, null, "tester", TriggerType.MANUAL)); () -> service.rebuildProject(40L, null, "tester", TriggerType.MANUAL));
verify(projectService).updateProjectStatus(40L, "4", "tester"); verify(projectService).updateProjectStatus(40L, "0", "tester");
} }
@Test @Test
@@ -496,95 +496,6 @@ class CcdiBankTagServiceImplTest {
))); )));
} }
@Test
void rebuildProject_shouldDispatchExternalPersonStatementRules() {
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
CcdiBankTagRule largeRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
"EXTERNAL_SINGLE_LARGE_AMOUNT", "外部人员单笔大额交易", "STATEMENT");
CcdiBankTagRule nightRule = buildRule("EXTERNAL_ABNORMAL_TRANSACTION", "外部人员异常交易",
"EXTERNAL_NIGHT_TRANSACTION", "外部人员夜间集中交易", "STATEMENT");
CcdiBankTagRule gamblingRule = buildRule("EXTERNAL_SUSPICIOUS_GAMBLING", "外部人员可疑赌博",
"EXTERNAL_GAMBLING_MEMO", "外部人员疑似赌博摘要", "STATEMENT");
CcdiBankTagRule relationRule = buildRule("EXTERNAL_SUSPICIOUS_RELATION", "外部人员可疑关系",
"EXTERNAL_TO_STAFF_FAMILY_TRANSACTION", "外部人员与员工或员工亲属交易", "STATEMENT");
BankTagRuleExecutionConfig largeConfig = buildConfig(40L, largeRule);
largeConfig.setThresholdValues(Map.of("FREQUENT_TRANSFER", "100000"));
BankTagStatementHitVO hit = new BankTagStatementHitVO();
hit.setBankStatementId(100L);
hit.setGroupId(40);
hit.setLogId(40001);
hit.setReasonDetail("外部人员命中");
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(largeRule, nightRule, gamblingRule, relationRule));
when(configResolver.resolve(40L, largeRule)).thenReturn(largeConfig);
when(configResolver.resolve(40L, nightRule)).thenReturn(buildConfig(40L, nightRule));
when(configResolver.resolve(40L, gamblingRule)).thenReturn(buildConfig(40L, gamblingRule));
when(configResolver.resolve(40L, relationRule)).thenReturn(buildConfig(40L, relationRule));
when(analysisMapper.selectExternalSingleLargeAmountStatements(40L, new BigDecimal("100000"))).thenReturn(List.of(hit));
when(analysisMapper.selectExternalNightTransactionStatements(40L)).thenReturn(List.of());
when(analysisMapper.selectExternalGamblingMemoStatements(40L)).thenReturn(List.of());
when(analysisMapper.selectExternalToStaffOrFamilyTransactionStatements(40L)).thenReturn(List.of());
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
verify(analysisMapper).selectExternalSingleLargeAmountStatements(40L, new BigDecimal("100000"));
verify(analysisMapper).selectExternalNightTransactionStatements(40L);
verify(analysisMapper).selectExternalGamblingMemoStatements(40L);
verify(analysisMapper).selectExternalToStaffOrFamilyTransactionStatements(40L);
verify(resultMapper).insertBatch(argThat(results -> results.stream().anyMatch(item ->
"EXTERNAL_LARGE_TRANSACTION".equals(item.getModelCode())
&& "EXTERNAL_SINGLE_LARGE_AMOUNT".equals(item.getRuleCode())
&& "STATEMENT".equals(item.getResultType())
&& Long.valueOf(100L).equals(item.getBankStatementId())
)));
}
@Test
void rebuildProject_shouldDispatchExternalPersonObjectRules() {
ReflectionTestUtils.setField(service, "tagRuleExecutor", (Executor) Runnable::run);
CcdiBankTagRule cumulativeRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
"EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT", "外部人员累计交易超限", "OBJECT");
CcdiBankTagRule annualRule = buildRule("EXTERNAL_LARGE_TRANSACTION", "外部人员大额交易",
"EXTERNAL_ANNUAL_TURNOVER", "外部人员年流水交易额超限", "OBJECT");
CcdiBankTagRule gamblingRule = buildRule("EXTERNAL_SUSPICIOUS_GAMBLING", "外部人员可疑赌博",
"EXTERNAL_MULTI_PARTY_GAMBLING_TRANSFER", "外部人员同日多对手方疑似赌博交易", "OBJECT");
BankTagRuleExecutionConfig cumulativeConfig = buildConfig(40L, cumulativeRule);
cumulativeConfig.setThresholdValues(Map.of("CUMULATIVE_TRANSACTION_AMOUNT", "500000"));
BankTagRuleExecutionConfig annualConfig = buildConfig(40L, annualRule);
annualConfig.setThresholdValues(Map.of("ANNUAL_TURNOVER", "800000"));
BankTagRuleExecutionConfig gamblingConfig = buildConfig(40L, gamblingRule);
gamblingConfig.setThresholdValues(Map.of("MULTI_PARTY_AMT_MIN", "500", "MULTI_PARTY_AMT_MAX", "5000"));
BankTagObjectHitVO hit = new BankTagObjectHitVO();
hit.setObjectType("EXTERNAL_CERT_NO");
hit.setObjectKey("330100198801010033");
hit.setReasonDetail("外部人员累计交易超限");
when(ruleMapper.selectEnabledRules(null)).thenReturn(List.of(cumulativeRule, annualRule, gamblingRule));
when(configResolver.resolve(40L, cumulativeRule)).thenReturn(cumulativeConfig);
when(configResolver.resolve(40L, annualRule)).thenReturn(annualConfig);
when(configResolver.resolve(40L, gamblingRule)).thenReturn(gamblingConfig);
when(analysisMapper.selectExternalCumulativeTransactionAmountObjects(40L, new BigDecimal("500000"))).thenReturn(List.of(hit));
when(analysisMapper.selectExternalAnnualTurnoverObjects(40L, new BigDecimal("800000"))).thenReturn(List.of());
when(analysisMapper.selectExternalMultiPartyGamblingTransferObjects(40L, new BigDecimal("500"), new BigDecimal("5000"))).thenReturn(List.of());
service.rebuildProject(40L, null, "admin", TriggerType.MANUAL);
verify(analysisMapper).selectExternalCumulativeTransactionAmountObjects(40L, new BigDecimal("500000"));
verify(analysisMapper).selectExternalAnnualTurnoverObjects(40L, new BigDecimal("800000"));
verify(analysisMapper).selectExternalMultiPartyGamblingTransferObjects(40L, new BigDecimal("500"), new BigDecimal("5000"));
verify(resultMapper).insertBatch(argThat(results -> results.stream().anyMatch(item ->
"EXTERNAL_LARGE_TRANSACTION".equals(item.getModelCode())
&& "EXTERNAL_CUMULATIVE_TRANSACTION_AMOUNT".equals(item.getRuleCode())
&& "OBJECT".equals(item.getResultType())
&& "EXTERNAL_CERT_NO".equals(item.getObjectType())
&& "330100198801010033".equals(item.getObjectKey())
)));
}
@Test @Test
void buildSafeTaskErrorMessage_shouldKeepLongMessageForLongTextColumn() throws Exception { void buildSafeTaskErrorMessage_shouldKeepLongMessageForLongTextColumn() throws Exception {
Method method = CcdiBankTagServiceImpl.class.getDeclaredMethod( Method method = CcdiBankTagServiceImpl.class.getDeclaredMethod(

View File

@@ -112,7 +112,7 @@ class CcdiBankTagServiceRiskCountRefreshTest {
verify(taskMapper).updateTask(argThat(task -> "FAILED".equals(task.getStatus()) verify(taskMapper).updateTask(argThat(task -> "FAILED".equals(task.getStatus())
&& "refresh failed".equals(task.getErrorMessage()))); && "refresh failed".equals(task.getErrorMessage())));
verify(projectService).updateProjectStatus(40L, "4", "tester"); verify(projectService).updateProjectStatus(40L, "0", "tester");
} }
private CcdiBankTagRule buildRule() { private CcdiBankTagRule buildRule() {

Some files were not shown because too many files have changed in this diff Show More