Compare commits
54 Commits
95ac01d7dc
...
dev
| Author | SHA1 | Date | |
|---|---|---|---|
| c5b2033a3d | |||
| d45e9410ef | |||
| 457e6c1d27 | |||
| 850f97ea22 | |||
| de6e6bd628 | |||
| 3a867e5857 | |||
| 19a60c987e | |||
| 7ce721ef93 | |||
| 000e8698a5 | |||
| 9d3e8beceb | |||
| 0ea504f6b3 | |||
| a39594faf8 | |||
| 1b45296df3 | |||
| 1fadb38d99 | |||
| 9917d10e59 | |||
| be443d1b31 | |||
| b822cc202e | |||
| 598f5dec1c | |||
| 0bf73a923f | |||
| ec67794f88 | |||
| 3ef45bc398 | |||
| 37e17ac903 | |||
| d561d068d6 | |||
| 43bc0e4f65 | |||
| 3fe78d8d3a | |||
| 4c58966529 | |||
| 3bc60fedeb | |||
| 4d1acc7484 | |||
| 402a0c3e2f | |||
| 5980ed0790 | |||
| 75cb8967da | |||
| 90a5c42313 | |||
| 356bcdd6de | |||
| 9a60371a8f | |||
| 380f9b4e7a | |||
| 928f65dfca | |||
| c64146ac40 | |||
| 0541ce0ac6 | |||
| 26c639134e | |||
| 0f7b57e824 | |||
| 104e8697fe | |||
| bbc6a2050b | |||
| bf7a4c0538 | |||
| b2e177dd24 | |||
| 2071d04c08 | |||
| 4988ab5944 | |||
| c00d5475e6 | |||
| 0b64532959 | |||
| 9f0ad4ce87 | |||
| 75b5989774 | |||
| 369c682564 | |||
| d8c069a836 | |||
| 6f2ea5994a | |||
| 26be75adad |
6
.gitignore
vendored
6
.gitignore
vendored
@@ -84,6 +84,8 @@ logs/
|
|||||||
|
|
||||||
ruoyi-ui/vue.config.js
|
ruoyi-ui/vue.config.js
|
||||||
|
|
||||||
|
ruoyi-ui/dist.zip
|
||||||
|
|
||||||
*/src/test/
|
*/src/test/
|
||||||
|
|
||||||
.pytest_cache/
|
.pytest_cache/
|
||||||
@@ -95,3 +97,7 @@ tongweb_62318.properties
|
|||||||
.superpowers/
|
.superpowers/
|
||||||
|
|
||||||
tmp/
|
tmp/
|
||||||
|
|
||||||
|
.codegraph/
|
||||||
|
|
||||||
|
.claude/
|
||||||
@@ -67,6 +67,7 @@
|
|||||||
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
- 前端相关安装、构建、调试、测试命令执行前,必须先通过 `nvm` 切换并确认 Node 版本
|
||||||
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
- 测试结束后,自动关闭测试过程中启动的前后端进程
|
||||||
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
|
- 重启后端时,必须优先使用 `bin/restart_java_backend.sh`
|
||||||
|
- 禁止在前端源码、配置、示例数据或页面默认值中硬编码或预填真实账号密码;登录页不得将密码保存到 Cookie、localStorage 或 sessionStorage
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -257,6 +258,7 @@ return AjaxResult.success(result);
|
|||||||
- 请求统一使用 `@/utils/request`
|
- 请求统一使用 `@/utils/request`
|
||||||
- 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识
|
- 新增页面或功能入口时,同步检查 `sys_menu`、路由、权限标识
|
||||||
- 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录
|
- 优先延续现有 `ccdi*` 业务目录与命名方式,不随意新造平行目录
|
||||||
|
- 登录页只能在用户主动选择时保存用户名,不允许保存密码或预填默认密码
|
||||||
|
|
||||||
### 导入功能规范
|
### 导入功能规范
|
||||||
|
|
||||||
|
|||||||
669
CLAUDE.md
669
CLAUDE.md
@@ -1,669 +0,0 @@
|
|||||||
# CLAUDE.md
|
|
||||||
|
|
||||||
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
||||||
|
|
||||||
## 快速参考
|
|
||||||
|
|
||||||
**启动项目:**
|
|
||||||
- 后端: `mvn spring-boot:run` 或运行 `ry.bat`
|
|
||||||
- 前端: `cd ruoyi-ui && npm run dev`
|
|
||||||
|
|
||||||
**访问地址:**
|
|
||||||
- 前端: http://localhost:80
|
|
||||||
- 后端: http://localhost:8080
|
|
||||||
- Swagger: http://localhost:8080/swagger-ui/index.html
|
|
||||||
- Druid 监控: http://localhost:8080/druid/ (ruoyi/123456)
|
|
||||||
|
|
||||||
**测试账号:**
|
|
||||||
- 用户名: `admin`
|
|
||||||
- 密码: `admin123`
|
|
||||||
|
|
||||||
**获取 Token:**
|
|
||||||
```bash
|
|
||||||
POST http://localhost:8080/login/test?username=admin&password=admin123
|
|
||||||
```
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 项目概述
|
|
||||||
|
|
||||||
**纪检初核系统** - 基于 **若依管理系统 v3.9.1** 构建的企业级前后端分离管理系统,用于员工异常行为风险识别。
|
|
||||||
|
|
||||||
### 技术栈版本
|
|
||||||
|
|
||||||
| 后端技术 | 版本 | 前端技术 | 版本 |
|
|
||||||
|-----------------------------|--------|------------|---------|
|
|
||||||
| Spring Boot | 3.5.8 | Vue.js | 2.6.12 |
|
|
||||||
| Java | 21 | Element UI | 2.15.14 |
|
|
||||||
| MyBatis Spring Boot Starter | 3.0.5 | Vuex | 3.6.0 |
|
|
||||||
| MySQL Connector | 8.2.0 | Vue Router | 3.4.9 |
|
|
||||||
| SpringDoc OpenAPI | 2.8.14 | Axios | 0.28.1 |
|
|
||||||
| EasyExcel | 3.3.4 | ECharts | 5.4.0 |
|
|
||||||
| Quartz | 2.5.2 | Sass | 1.32.13 |
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 常用命令
|
|
||||||
|
|
||||||
### 后端 (Maven)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 编译项目
|
|
||||||
mvn clean compile
|
|
||||||
|
|
||||||
# 运行应用 (开发环境)
|
|
||||||
mvn spring-boot:run
|
|
||||||
|
|
||||||
# 打包部署
|
|
||||||
mvn clean package
|
|
||||||
|
|
||||||
# Windows 启动
|
|
||||||
ry.bat
|
|
||||||
|
|
||||||
# Linux/Mac 启动
|
|
||||||
./ry.sh start
|
|
||||||
```
|
|
||||||
|
|
||||||
### 前端 (npm)
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd ruoyi-ui
|
|
||||||
|
|
||||||
# 安装依赖 (推荐使用国内镜像)
|
|
||||||
npm install --registry=https://registry.npmmirror.com
|
|
||||||
|
|
||||||
# 开发服务器 (端口 80)
|
|
||||||
npm run dev
|
|
||||||
|
|
||||||
# 生产构建
|
|
||||||
npm run build:prod
|
|
||||||
|
|
||||||
# 预览生产构建
|
|
||||||
npm run preview
|
|
||||||
```
|
|
||||||
|
|
||||||
### 数据库初始化
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# 初始化若依框架基础表
|
|
||||||
mysql -u root -p < sql/ry_20250522.sql
|
|
||||||
|
|
||||||
# 初始化定时任务表
|
|
||||||
mysql -u root -p < sql/quartz.sql
|
|
||||||
|
|
||||||
# 导入业务表(根据需要执行)
|
|
||||||
mysql -u root -p ccdi < sql/dpc_employee.sql
|
|
||||||
mysql -u root -p ccdi < sql/dpc_intermediary_blacklist.sql
|
|
||||||
# ... 其他业务表脚本
|
|
||||||
```
|
|
||||||
|
|
||||||
**注意:**
|
|
||||||
- 业务表脚本文件名以 `ccdi_` 或 `dpc_` 开头
|
|
||||||
- 部分脚本包含菜单数据,需要按顺序执行
|
|
||||||
- 数据库需要先创建(数据库名: `ccdi`)
|
|
||||||
|
|
||||||
---
|
|
||||||
|
|
||||||
## 模块架构
|
|
||||||
|
|
||||||
```
|
|
||||||
ccdi/
|
|
||||||
├── ruoyi-admin/ # 主应用入口 (Spring Boot 启动类)
|
|
||||||
├── ruoyi-framework/ # 核心框架 (Security, Config, Filters)
|
|
||||||
├── ruoyi-system/ # 系统管理 (Users, Roles, Menus, Depts)
|
|
||||||
├── ruoyi-common/ # 通用工具 (annotations, utils, constants)
|
|
||||||
├── ruoyi-quartz/ # 定时任务
|
|
||||||
├── ruoyi-generator/ # 代码生成器
|
|
||||||
├── ccdi-info-collection/ # 【核心业务模块】信息采集
|
|
||||||
├── ccdi-project/ # 【核心业务模块】项目管理
|
|
||||||
├── ccdi-lsfx/ # 【核心业务模块】流水分析对接
|
|
||||||
├── lsfx-mock-server/ # 流水分析模拟服务器 (Python)
|
|
||||||
├── ruoyi-ui/ # 前端 Vue 应用
|
|
||||||
├── sql/ # 数据库脚本
|
|
||||||
├── bin/ # 启动脚本
|
|
||||||
└── doc/ # 项目文档
|
|
||||||
```
|
|
||||||
|
|
||||||
### 模块依赖关系
|
|
||||||
|
|
||||||
```
|
|
||||||
ruoyi-admin (启动模块)
|
|
||||||
├── ruoyi-framework (核心安全配置)
|
|
||||||
├── ruoyi-system (系统核心业务)
|
|
||||||
├── ruoyi-common (共享工具)
|
|
||||||
├── ruoyi-quartz (定时任务)
|
|
||||||
├── ruoyi-generator (代码生成)
|
|
||||||
├── ccdi-info-collection (信息采集模块)
|
|
||||||
│ └── 依赖 ruoyi-common
|
|
||||||
├── ccdi-project (项目管理模块)
|
|
||||||
│ └── 依赖 ruoyi-common
|
|
||||||
└── ccdi-lsfx (流水分析对接模块)
|
|
||||||
└── 依赖 ruoyi-common
|
|
||||||
```
|
|
||||||
|
|
||||||
**添加新业务模块:**
|
|
||||||
1. 在根目录 `pom.xml` 的 `<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>
|
|
||||||
```
|
|
||||||
@@ -51,6 +51,7 @@
|
|||||||
"msg": "查询成功",
|
"msg": "查询成功",
|
||||||
"rows": [
|
"rows": [
|
||||||
{
|
{
|
||||||
|
"id": 1002,
|
||||||
"recruitId": "REC20250205001",
|
"recruitId": "REC20250205001",
|
||||||
"recruitName": "2025春季校园招聘",
|
"recruitName": "2025春季校园招聘",
|
||||||
"posName": "Java开发工程师",
|
"posName": "Java开发工程师",
|
||||||
@@ -80,19 +81,19 @@
|
|||||||
|
|
||||||
### 1.2 查询招聘信息详情
|
### 1.2 查询招聘信息详情
|
||||||
|
|
||||||
**接口描述:** 根据招聘项目编号查询详细信息
|
**接口描述:** 根据招聘信息主键ID查询详细信息
|
||||||
|
|
||||||
**请求方式:** `GET`
|
**请求方式:** `GET`
|
||||||
|
|
||||||
**接口路径:** `/ccdi/staffRecruitment/{recruitId}`
|
**接口路径:** `/ccdi/staffRecruitment/{id}`
|
||||||
|
|
||||||
**权限标识:** `ccdi:staffRecruitment:query`
|
**权限标识:** `ccdi:staffRecruitment:query`
|
||||||
|
|
||||||
**路径参数:**
|
**路径参数:**
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||||
|-----------|--------|----|--------|----------------|
|
|------|------|----|--------------|-----|
|
||||||
| recruitId | String | 是 | 招聘项目编号 | REC20250205001 |
|
| id | Long | 是 | 招聘信息主键ID | 1002 |
|
||||||
|
|
||||||
**响应示例:**
|
**响应示例:**
|
||||||
|
|
||||||
@@ -101,6 +102,7 @@
|
|||||||
"code": 200,
|
"code": 200,
|
||||||
"msg": "操作成功",
|
"msg": "操作成功",
|
||||||
"data": {
|
"data": {
|
||||||
|
"id": 1002,
|
||||||
"recruitId": "REC20250205001",
|
"recruitId": "REC20250205001",
|
||||||
"recruitName": "2025春季校园招聘",
|
"recruitName": "2025春季校园招聘",
|
||||||
"posName": "Java开发工程师",
|
"posName": "Java开发工程师",
|
||||||
@@ -237,15 +239,15 @@
|
|||||||
|
|
||||||
**请求方式:** `DELETE`
|
**请求方式:** `DELETE`
|
||||||
|
|
||||||
**接口路径:** `/ccdi/staffRecruitment/{recruitIds}`
|
**接口路径:** `/ccdi/staffRecruitment/{ids}`
|
||||||
|
|
||||||
**权限标识:** `ccdi:staffRecruitment:remove`
|
**权限标识:** `ccdi:staffRecruitment:remove`
|
||||||
|
|
||||||
**路径参数:**
|
**路径参数:**
|
||||||
|
|
||||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||||
|------------|----------|----|------------------|-------------------------------|
|
|------|------|----|-----------------------|----------|
|
||||||
| recruitIds | String[] | 是 | 招聘项目编号数组,多个用逗号分隔 | REC20250205001,REC20250205002 |
|
| ids | Long[] | 是 | 招聘信息主键ID数组,多个用逗号分隔 | 1002,1003 |
|
||||||
|
|
||||||
**响应示例:**
|
**响应示例:**
|
||||||
|
|
||||||
@@ -276,7 +278,7 @@
|
|||||||
|
|
||||||
| 序号 | 字段名 | 说明 | 必填 |
|
| 序号 | 字段名 | 说明 | 必填 |
|
||||||
|----|----------|-----------|----|
|
|----|----------|-----------|----|
|
||||||
| 1 | 招聘项目编号 | 唯一标识 | 是 |
|
| 1 | 招聘项目编号 | 允许重复 | 是 |
|
||||||
| 2 | 招聘项目名称 | - | 是 |
|
| 2 | 招聘项目名称 | - | 是 |
|
||||||
| 3 | 职位名称 | - | 是 |
|
| 3 | 职位名称 | - | 是 |
|
||||||
| 4 | 职位类别 | - | 是 |
|
| 4 | 职位类别 | - | 是 |
|
||||||
@@ -326,7 +328,7 @@
|
|||||||
```json
|
```json
|
||||||
{
|
{
|
||||||
"code": 500,
|
"code": 500,
|
||||||
"msg": "很抱歉,导入完成!成功 8 条,失败 2 条,错误如下:<br/>1、招聘项目编号 REC001 导入失败:该招聘项目编号已存在<br/>2、招聘项目编号 REC002 导入失败:证件号码格式不正确"
|
"msg": "很抱歉,导入完成!成功 8 条,失败 2 条,错误如下:<br/>1、招聘项目编号 REC001 导入失败:历史工作经历匹配到多条招聘主信息<br/>2、招聘项目编号 REC002 导入失败:证件号码格式不正确"
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
@@ -375,14 +377,14 @@ Excel导入导出对象,使用EasyExcel注解。
|
|||||||
| 401 | 未授权,请先登录 |
|
| 401 | 未授权,请先登录 |
|
||||||
| 403 | 无权限访问 |
|
| 403 | 无权限访问 |
|
||||||
| 404 | 资源不存在 |
|
| 404 | 资源不存在 |
|
||||||
| 409 | 主键冲突 |
|
| 409 | 数据冲突 |
|
||||||
| 500 | 服务器内部错误 |
|
| 500 | 服务器内部错误 |
|
||||||
|
|
||||||
### 常见业务错误
|
### 常见业务错误
|
||||||
|
|
||||||
| 错误信息 | 说明 |
|
| 错误信息 | 说明 |
|
||||||
|------------|--------------------|
|
|------------|--------------------|
|
||||||
| 该招聘项目编号已存在 | 新增时recruitId重复 |
|
| 历史工作经历匹配到多条招聘主信息 | 招聘项目编号重复且候选人、项目名、职位名仍无法唯一匹配从表归属 |
|
||||||
| 招聘项目编号不能为空 | recruitId字段为空 |
|
| 招聘项目编号不能为空 | recruitId字段为空 |
|
||||||
| 证件号码格式不正确 | 身份证号格式验证失败 |
|
| 证件号码格式不正确 | 身份证号格式验证失败 |
|
||||||
| 毕业年月格式不正确 | candGrad不是YYYYMM格式 |
|
| 毕业年月格式不正确 | candGrad不是YYYYMM格式 |
|
||||||
|
|||||||
@@ -1,22 +1,23 @@
|
|||||||
4.员工招聘信息表:ccdi_staff_recruitment,,,,,,
|
4.员工招聘信息表:ccdi_staff_recruitment,,,,,,
|
||||||
序号,字段名,类型,默认值,是否可为空,是否主键,注释
|
序号,字段名,类型,默认值,是否可为空,是否主键,注释
|
||||||
1,recruit_id,VARCHAR(32),,否,是,招聘项目编号
|
1,id,BIGINT,,否,是,主键ID
|
||||||
2,recruit_name,VARCHAR(100),,否,否,招聘项目名称
|
2,recruit_id,VARCHAR(32),,否,否,招聘项目编号(允许重复)
|
||||||
3,pos_name,VARCHAR(100),,否,否,职位名称
|
3,recruit_name,VARCHAR(100),,否,否,招聘项目名称
|
||||||
4,pos_category,VARCHAR(50),,否,否,职位类别
|
4,pos_name,VARCHAR(100),,否,否,职位名称
|
||||||
5,pos_desc,TEXT,,否,否,职位描述
|
5,pos_category,VARCHAR(50),,否,否,职位类别
|
||||||
6,cand_name,VARCHAR(20),,否,否,应聘人员姓名
|
6,pos_desc,TEXT,,否,否,职位描述
|
||||||
7,cand_edu,VARCHAR(20),,否,否,应聘人员学历
|
7,cand_name,VARCHAR(20),,否,否,应聘人员姓名
|
||||||
8,cand_id,VARCHAR(18),,否,否,应聘人员证件号码
|
8,cand_edu,VARCHAR(20),,否,否,应聘人员学历
|
||||||
9,cand_school,VARCHAR(50),,否,否,应聘人员毕业院校
|
9,cand_id,VARCHAR(18),,否,否,应聘人员证件号码
|
||||||
10,cand_major,VARCHAR(30),,否,否,应聘人员专业
|
10,cand_school,VARCHAR(50),,否,否,应聘人员毕业院校
|
||||||
11,cand_grad,VARCHAR(6),,否,否,应聘人员毕业年月
|
11,cand_major,VARCHAR(30),,否,否,应聘人员专业
|
||||||
12,admit_status,VARCHAR(10),,否,否,记录录用情况:录用、未录用、放弃等
|
12,cand_grad,VARCHAR(6),,否,否,应聘人员毕业年月
|
||||||
13,interviewer_name1,VARCHAR(20),,是,否,面试官1姓名
|
13,admit_status,VARCHAR(10),,否,否,记录录用情况:录用、未录用、放弃等
|
||||||
14,interviewer_id1,VARCHAR(10),,是,否,面试官1工号
|
14,interviewer_name1,VARCHAR(20),,是,否,面试官1姓名
|
||||||
13,interviewer_name2,VARCHAR(20),,是,否,面试官2姓名
|
15,interviewer_id1,VARCHAR(10),,是,否,面试官1工号
|
||||||
14,interviewer_id2,VARCHAR(10),,是,否,面试官2工号
|
16,interviewer_name2,VARCHAR(20),,是,否,面试官2姓名
|
||||||
16,created_by,VARCHAR(20),-,否,否,记录创建人
|
17,interviewer_id2,VARCHAR(10),,是,否,面试官2工号
|
||||||
17,updated_by,VARCHAR(20),-,是,否,记录更新人
|
18,created_by,VARCHAR(20),-,否,否,记录创建人
|
||||||
18,create_time,VARCHAR(10),0000-00-00,是,否,创建时间
|
19,updated_by,VARCHAR(20),-,是,否,记录更新人
|
||||||
19,update_time,VARCHAR(10),0000-00-00,是,否,更新时间
|
20,create_time,VARCHAR(10),0000-00-00,是,否,创建时间
|
||||||
|
21,update_time,VARCHAR(10),0000-00-00,是,否,更新时间
|
||||||
|
|||||||
|
3
assets/图谱.txt
Normal file
3
assets/图谱.txt
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
关系图谱:http://64.202.65.112:8082/atlas/refactor/#/home/graph/downloadService?id=lanxitest&mode=K_EXPAND&type=NORMAL&atlasToken=2C914E5E1FBFBC4AD15163E0AB03B800¶ms={"vId":"rel_node/15942f5b84bada01ccd25f5e5678ac22"}
|
||||||
|
|
||||||
|
资金流图谱:http://64.202.65.112:8082/atlas/refactor/#/home/graph/downloadService?id=ccdi_lanxi_trans&mode=K_EXPAND&type=NORMAL&atlasToken=F4BBA291A285858BAF4526C6EC312388¶ms={"vId":"idno_node/f2f797081494c5c0555a3bbf0f57c5e7"}
|
||||||
@@ -1,92 +0,0 @@
|
|||||||
#!/bin/sh
|
|
||||||
|
|
||||||
set -eu
|
|
||||||
|
|
||||||
ROOT_DIR=$(CDPATH= cd -- "$(dirname "$0")" && pwd)
|
|
||||||
DATE_STAMP=$(date "+%Y%m%d")
|
|
||||||
RELEASE_ZIP="$ROOT_DIR/ccdi_${DATE_STAMP}.zip"
|
|
||||||
STAGE_DIR="$ROOT_DIR/.deploy/ccdi-release-package"
|
|
||||||
WORK_DIR="$STAGE_DIR/files"
|
|
||||||
BACKEND_JAR_SOURCE="$ROOT_DIR/ruoyi-admin/target/ruoyi-admin.jar"
|
|
||||||
FRONTEND_DIR="$ROOT_DIR/ruoyi-ui"
|
|
||||||
FRONTEND_DIST_DIR="$FRONTEND_DIR/dist"
|
|
||||||
FRONTEND_DIST_ZIP="$WORK_DIR/dist.zip"
|
|
||||||
|
|
||||||
log_info() {
|
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1"
|
|
||||||
}
|
|
||||||
|
|
||||||
log_error() {
|
|
||||||
printf '[%s] %s\n' "$(date '+%Y-%m-%d %H:%M:%S')" "$1" >&2
|
|
||||||
}
|
|
||||||
|
|
||||||
require_command() {
|
|
||||||
if ! command -v "$1" >/dev/null 2>&1; then
|
|
||||||
log_error "缺少命令: $1"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
reset_stage_dir() {
|
|
||||||
rm -rf "$STAGE_DIR"
|
|
||||||
mkdir -p "$WORK_DIR"
|
|
||||||
}
|
|
||||||
|
|
||||||
build_backend() {
|
|
||||||
log_info "开始构建后端生产 jar"
|
|
||||||
(
|
|
||||||
cd "$ROOT_DIR"
|
|
||||||
mvn -pl ruoyi-admin -am clean package -DskipTests
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ ! -f "$BACKEND_JAR_SOURCE" ]; then
|
|
||||||
log_error "未生成后端 jar: $BACKEND_JAR_SOURCE"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
build_frontend() {
|
|
||||||
log_info "开始构建前端生产 dist"
|
|
||||||
FRONTEND_DIR="$FRONTEND_DIR" zsh -lic 'cd "$FRONTEND_DIR" && nvm use >/dev/null && npm run build:prod'
|
|
||||||
|
|
||||||
if [ ! -f "$FRONTEND_DIST_DIR/index.html" ]; then
|
|
||||||
log_error "前端生产构建失败,未找到: $FRONTEND_DIST_DIR/index.html"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
(
|
|
||||||
cd "$FRONTEND_DIR"
|
|
||||||
zip -qr "$FRONTEND_DIST_ZIP" dist
|
|
||||||
)
|
|
||||||
|
|
||||||
if [ ! -f "$FRONTEND_DIST_ZIP" ]; then
|
|
||||||
log_error "未生成前端压缩包: $FRONTEND_DIST_ZIP"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
}
|
|
||||||
|
|
||||||
package_release() {
|
|
||||||
cp "$BACKEND_JAR_SOURCE" "$WORK_DIR/ruoyi-admin.jar"
|
|
||||||
|
|
||||||
rm -f "$RELEASE_ZIP"
|
|
||||||
(
|
|
||||||
cd "$WORK_DIR"
|
|
||||||
zip -qr "$RELEASE_ZIP" ruoyi-admin.jar dist.zip
|
|
||||||
)
|
|
||||||
|
|
||||||
log_info "上线压缩包已生成: $RELEASE_ZIP"
|
|
||||||
log_info "压缩包根层内容: ruoyi-admin.jar, dist.zip"
|
|
||||||
}
|
|
||||||
|
|
||||||
main() {
|
|
||||||
require_command mvn
|
|
||||||
require_command zsh
|
|
||||||
require_command zip
|
|
||||||
|
|
||||||
reset_stage_dir
|
|
||||||
build_backend
|
|
||||||
build_frontend
|
|
||||||
package_release
|
|
||||||
}
|
|
||||||
|
|
||||||
main "$@"
|
|
||||||
@@ -7,7 +7,6 @@ import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
|
|||||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.*;
|
import com.ruoyi.info.collection.domain.vo.*;
|
||||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
|
||||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
||||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||||
@@ -47,9 +46,6 @@ public class CcdiBaseStaffController extends BaseController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiBaseStaffImportService importAsyncService;
|
private ICcdiBaseStaffImportService importAsyncService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工列表
|
* 查询员工列表
|
||||||
*/
|
*/
|
||||||
@@ -161,14 +157,7 @@ public class CcdiBaseStaffController extends BaseController {
|
|||||||
return error("至少需要一条数据");
|
return error("至少需要一条数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
|
BaseStaffImportSubmitResultVO result = baseStaffService.importBaseStaffWithAssets(staffList, assetList);
|
||||||
if (hasStaffRows) {
|
|
||||||
result.setStaffTaskId(baseStaffService.importBaseStaff(staffList));
|
|
||||||
}
|
|
||||||
if (hasAssetRows) {
|
|
||||||
result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
|
|
||||||
}
|
|
||||||
result.setMessage(buildImportSubmitMessage(hasStaffRows, hasAssetRows));
|
|
||||||
|
|
||||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||||
}
|
}
|
||||||
@@ -215,13 +204,4 @@ public class CcdiBaseStaffController extends BaseController {
|
|||||||
return getDataTable(pageData, failures.size());
|
return getDataTable(pageData, failures.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildImportSubmitMessage(boolean hasStaffRows, boolean hasAssetRows) {
|
|
||||||
if (hasStaffRows && hasAssetRows) {
|
|
||||||
return "已提交员工信息和员工资产信息导入任务";
|
|
||||||
}
|
|
||||||
if (hasStaffRows) {
|
|
||||||
return "已提交员工信息导入任务";
|
|
||||||
}
|
|
||||||
return "已提交员工资产信息导入任务";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,7 +10,6 @@ import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
||||||
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
|
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
||||||
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
import com.ruoyi.info.collection.utils.EasyExcelUtil;
|
||||||
@@ -51,9 +50,6 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiStaffFmyRelationImportService relationImportService;
|
private ICcdiStaffFmyRelationImportService relationImportService;
|
||||||
|
|
||||||
@Resource
|
|
||||||
private ICcdiAssetInfoImportService assetInfoImportService;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工亲属关系列表
|
* 查询员工亲属关系列表
|
||||||
*/
|
*/
|
||||||
@@ -157,15 +153,7 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
|||||||
return error("至少需要一条数据");
|
return error("至少需要一条数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
StaffFmyRelationImportSubmitResultVO result = new StaffFmyRelationImportSubmitResultVO();
|
StaffFmyRelationImportSubmitResultVO result = relationService.importRelationWithAssets(relationList, assetList);
|
||||||
if (hasRelationRows) {
|
|
||||||
result.setRelationTaskId(relationService.importRelation(relationList));
|
|
||||||
}
|
|
||||||
if (hasAssetRows) {
|
|
||||||
result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
|
|
||||||
}
|
|
||||||
result.setMessage(buildImportSubmitMessage(hasRelationRows, hasAssetRows));
|
|
||||||
|
|
||||||
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
return AjaxResult.success("导入任务已提交,正在后台处理", result);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -211,13 +199,4 @@ public class CcdiStaffFmyRelationController extends BaseController {
|
|||||||
return getDataTable(pageData, failures.size());
|
return getDataTable(pageData, failures.size());
|
||||||
}
|
}
|
||||||
|
|
||||||
private String buildImportSubmitMessage(boolean hasRelationRows, boolean hasAssetRows) {
|
|
||||||
if (hasRelationRows && hasAssetRows) {
|
|
||||||
return "已提交员工亲属关系和亲属资产信息导入任务";
|
|
||||||
}
|
|
||||||
if (hasRelationRows) {
|
|
||||||
return "已提交员工亲属关系导入任务";
|
|
||||||
}
|
|
||||||
return "已提交亲属资产信息导入任务";
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -69,9 +69,9 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
|||||||
*/
|
*/
|
||||||
@Operation(summary = "获取招聘信息详细信息")
|
@Operation(summary = "获取招聘信息详细信息")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:query')")
|
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:query')")
|
||||||
@GetMapping(value = "/{recruitId}")
|
@GetMapping(value = "/{id}")
|
||||||
public AjaxResult getInfo(@PathVariable String recruitId) {
|
public AjaxResult getInfo(@PathVariable Long id) {
|
||||||
return success(recruitmentService.selectRecruitmentById(recruitId));
|
return success(recruitmentService.selectRecruitmentById(id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -102,9 +102,9 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
|||||||
@Operation(summary = "删除招聘信息")
|
@Operation(summary = "删除招聘信息")
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:remove')")
|
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:remove')")
|
||||||
@Log(title = "员工招聘信息", businessType = BusinessType.DELETE)
|
@Log(title = "员工招聘信息", businessType = BusinessType.DELETE)
|
||||||
@DeleteMapping("/{recruitIds}")
|
@DeleteMapping("/{ids}")
|
||||||
public AjaxResult remove(@PathVariable String[] recruitIds) {
|
public AjaxResult remove(@PathVariable Long[] ids) {
|
||||||
return toAjax(recruitmentService.deleteRecruitmentByIds(recruitIds));
|
return toAjax(recruitmentService.deleteRecruitmentByIds(ids));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -94,6 +94,6 @@ public class CcdiEnterpriseBaseInfo implements Serializable {
|
|||||||
/** 风险等级:1-高风险, 2-中风险, 3-低风险 */
|
/** 风险等级:1-高风险, 2-中风险, 3-低风险 */
|
||||||
private String riskLevel;
|
private String riskLevel;
|
||||||
|
|
||||||
/** 企业来源:GENERAL-一般企业, EMP_RELATION-员工关系人, CREDIT_CUSTOMER-信贷客户, INTERMEDIARY-中介, BOTH-兼有 */
|
/** 企业来源:GENERAL-一般企业, EMP_RELATION-员工关系人, CREDIT_CUSTOMER-信贷客户, SUPPLIER-供应商, INTERMEDIARY-中介, BOTH-兼有 */
|
||||||
private String entSource;
|
private String entSource;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,8 +22,11 @@ public class CcdiStaffRecruitment implements Serializable {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
@TableId(type = IdType.AUTO)
|
||||||
|
private Long id;
|
||||||
|
|
||||||
/** 招聘记录编号 */
|
/** 招聘记录编号 */
|
||||||
@TableId(type = IdType.INPUT)
|
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
/** 招聘项目名称 */
|
/** 招聘项目名称 */
|
||||||
|
|||||||
@@ -28,6 +28,9 @@ public class CcdiStaffRecruitmentWork implements Serializable {
|
|||||||
@TableId(type = IdType.AUTO)
|
@TableId(type = IdType.AUTO)
|
||||||
private Long id;
|
private Long id;
|
||||||
|
|
||||||
|
/** 关联招聘信息主键ID */
|
||||||
|
private Long recruitmentId;
|
||||||
|
|
||||||
/** 关联招聘记录编号 */
|
/** 关联招聘记录编号 */
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
|
|||||||
@@ -33,6 +33,10 @@ public class CcdiCustFmyRelationQueryDTO implements Serializable {
|
|||||||
@Schema(description = "关系人姓名")
|
@Schema(description = "关系人姓名")
|
||||||
private String relationName;
|
private String relationName;
|
||||||
|
|
||||||
|
/** 关系人身份证号 */
|
||||||
|
@Schema(description = "关系人身份证号")
|
||||||
|
private String relationCertNo;
|
||||||
|
|
||||||
/** 状态 */
|
/** 状态 */
|
||||||
@Schema(description = "状态:0-无效,1-有效")
|
@Schema(description = "状态:0-无效,1-有效")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|||||||
@@ -37,6 +37,10 @@ public class CcdiStaffFmyRelationQueryDTO implements Serializable {
|
|||||||
@Schema(description = "关系人姓名")
|
@Schema(description = "关系人姓名")
|
||||||
private String relationName;
|
private String relationName;
|
||||||
|
|
||||||
|
/** 关系人身份证号 */
|
||||||
|
@Schema(description = "关系人身份证号")
|
||||||
|
private String relationCertNo;
|
||||||
|
|
||||||
/** 状态 */
|
/** 状态 */
|
||||||
@Schema(description = "状态:0-无效,1-有效")
|
@Schema(description = "状态:0-无效,1-有效")
|
||||||
private Integer status;
|
private Integer status;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.dto;
|
|||||||
import com.ruoyi.info.collection.annotation.EnumValid;
|
import com.ruoyi.info.collection.annotation.EnumValid;
|
||||||
import com.ruoyi.info.collection.enums.AdmitStatus;
|
import com.ruoyi.info.collection.enums.AdmitStatus;
|
||||||
import com.ruoyi.info.collection.enums.RecruitType;
|
import com.ruoyi.info.collection.enums.RecruitType;
|
||||||
|
import jakarta.validation.Valid;
|
||||||
import jakarta.validation.constraints.NotBlank;
|
import jakarta.validation.constraints.NotBlank;
|
||||||
import jakarta.validation.constraints.Pattern;
|
import jakarta.validation.constraints.Pattern;
|
||||||
import jakarta.validation.constraints.Size;
|
import jakarta.validation.constraints.Size;
|
||||||
@@ -10,6 +11,7 @@ import lombok.Data;
|
|||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
import java.io.Serializable;
|
import java.io.Serializable;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工招聘信息新增DTO
|
* 员工招聘信息新增DTO
|
||||||
@@ -102,4 +104,8 @@ public class CcdiStaffRecruitmentAddDTO implements Serializable {
|
|||||||
/** 面试官2工号 */
|
/** 面试官2工号 */
|
||||||
@Size(max = 10, message = "面试官2工号长度不能超过10个字符")
|
@Size(max = 10, message = "面试官2工号长度不能超过10个字符")
|
||||||
private String interviewerId2;
|
private String interviewerId2;
|
||||||
|
|
||||||
|
/** 历史工作经历列表 */
|
||||||
|
@Valid
|
||||||
|
private List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -26,8 +26,13 @@ public class CcdiStaffRecruitmentEditDTO implements Serializable {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
@NotNull(message = "招聘信息ID不能为空")
|
||||||
|
private Long id;
|
||||||
|
|
||||||
/** 招聘记录编号 */
|
/** 招聘记录编号 */
|
||||||
@NotNull(message = "招聘记录编号不能为空")
|
@NotBlank(message = "招聘记录编号不能为空")
|
||||||
|
@Size(max = 32, message = "招聘记录编号长度不能超过32个字符")
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
/** 招聘项目名称 */
|
/** 招聘项目名称 */
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -25,6 +26,7 @@ public class CcdiAccountInfoExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "证件号*", index = 1)
|
@ExcelProperty(value = "证件号*", index = 1)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@TextFormat
|
||||||
private String ownerId;
|
private String ownerId;
|
||||||
|
|
||||||
@ExcelProperty(value = "账户姓名*", index = 2)
|
@ExcelProperty(value = "账户姓名*", index = 2)
|
||||||
@@ -33,6 +35,7 @@ public class CcdiAccountInfoExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "账户号码*", index = 3)
|
@ExcelProperty(value = "账户号码*", index = 3)
|
||||||
@ColumnWidth(28)
|
@ColumnWidth(28)
|
||||||
|
@TextFormat
|
||||||
private String accountNo;
|
private String accountNo;
|
||||||
|
|
||||||
@ExcelProperty(value = "账户类型*", index = 4)
|
@ExcelProperty(value = "账户类型*", index = 4)
|
||||||
@@ -49,6 +52,7 @@ public class CcdiAccountInfoExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "银行代码", index = 7)
|
@ExcelProperty(value = "银行代码", index = 7)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(16)
|
||||||
|
@TextFormat
|
||||||
private String bankCode;
|
private String bankCode;
|
||||||
|
|
||||||
@ExcelProperty(value = "币种", index = 8)
|
@ExcelProperty(value = "币种", index = 8)
|
||||||
|
|||||||
@@ -26,14 +26,14 @@ public class CcdiBaseStaffAssetInfoExcel implements Serializable {
|
|||||||
|
|
||||||
/** 员工身份证号 */
|
/** 员工身份证号 */
|
||||||
@ExcelProperty(value = "员工身份证号*", index = 0)
|
@ExcelProperty(value = "员工身份证号*", index = 0)
|
||||||
@ColumnWidth(22)
|
@ColumnWidth(24)
|
||||||
@Required
|
@Required
|
||||||
@TextFormat
|
@TextFormat
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
/** 资产大类 */
|
/** 资产大类 */
|
||||||
@ExcelProperty(value = "资产大类*", index = 1)
|
@ExcelProperty(value = "资产大类*", index = 1)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(18)
|
||||||
@Required
|
@Required
|
||||||
private String assetMainType;
|
private String assetMainType;
|
||||||
|
|
||||||
@@ -51,39 +51,39 @@ public class CcdiBaseStaffAssetInfoExcel implements Serializable {
|
|||||||
|
|
||||||
/** 产权占比 */
|
/** 产权占比 */
|
||||||
@ExcelProperty(value = "产权占比", index = 4)
|
@ExcelProperty(value = "产权占比", index = 4)
|
||||||
@ColumnWidth(12)
|
@ColumnWidth(14)
|
||||||
private BigDecimal ownershipRatio;
|
private BigDecimal ownershipRatio;
|
||||||
|
|
||||||
/** 购买/评估日期 */
|
/** 购买/评估日期 */
|
||||||
@ExcelProperty(value = "购买/评估日期", index = 5)
|
@ExcelProperty(value = "购买/评估日期", index = 5)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(20)
|
||||||
private Date purchaseEvalDate;
|
private Date purchaseEvalDate;
|
||||||
|
|
||||||
/** 资产原值 */
|
/** 资产原值 */
|
||||||
@ExcelProperty(value = "资产原值", index = 6)
|
@ExcelProperty(value = "资产原值", index = 6)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(18)
|
||||||
private BigDecimal originalValue;
|
private BigDecimal originalValue;
|
||||||
|
|
||||||
/** 当前估值 */
|
/** 当前估值 */
|
||||||
@ExcelProperty(value = "当前估值*", index = 7)
|
@ExcelProperty(value = "当前估值*", index = 7)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(18)
|
||||||
@Required
|
@Required
|
||||||
private BigDecimal currentValue;
|
private BigDecimal currentValue;
|
||||||
|
|
||||||
/** 估值截止日期 */
|
/** 估值截止日期 */
|
||||||
@ExcelProperty(value = "估值截止日期", index = 8)
|
@ExcelProperty(value = "估值截止日期", index = 8)
|
||||||
@ColumnWidth(16)
|
@ColumnWidth(20)
|
||||||
private Date valuationDate;
|
private Date valuationDate;
|
||||||
|
|
||||||
/** 资产状态 */
|
/** 资产状态 */
|
||||||
@ExcelProperty(value = "资产状态*", index = 9)
|
@ExcelProperty(value = "资产状态*", index = 9)
|
||||||
@ColumnWidth(14)
|
@ColumnWidth(16)
|
||||||
@DictDropdown(dictType = "ccdi_asset_status")
|
@DictDropdown(dictType = "ccdi_asset_status")
|
||||||
@Required
|
@Required
|
||||||
private String assetStatus;
|
private String assetStatus;
|
||||||
|
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
@ExcelProperty(value = "备注", index = 10)
|
@ExcelProperty(value = "备注", index = 10)
|
||||||
@ColumnWidth(28)
|
@ColumnWidth(32)
|
||||||
private String remarks;
|
private String remarks;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
|
|||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -25,54 +26,56 @@ public class CcdiBaseStaffExcel implements Serializable {
|
|||||||
|
|
||||||
/** 姓名 */
|
/** 姓名 */
|
||||||
@ExcelProperty(value = "姓名", index = 0)
|
@ExcelProperty(value = "姓名", index = 0)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(16)
|
||||||
@Required
|
@Required
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
/** 员工ID */
|
/** 员工ID */
|
||||||
@ExcelProperty(value = "员工ID", index = 1)
|
@ExcelProperty(value = "员工ID", index = 1)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(18)
|
||||||
@Required
|
@Required
|
||||||
private Long staffId;
|
private Long staffId;
|
||||||
|
|
||||||
/** 所属部门ID */
|
/** 所属部门ID */
|
||||||
@ExcelProperty(value = "所属部门ID", index = 2)
|
@ExcelProperty(value = "所属部门ID", index = 2)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
private Long deptId;
|
private Long deptId;
|
||||||
|
|
||||||
/** 身份证号 */
|
/** 身份证号 */
|
||||||
@ExcelProperty(value = "身份证号", index = 3)
|
@ExcelProperty(value = "身份证号", index = 3)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(24)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String idCard;
|
private String idCard;
|
||||||
|
|
||||||
/** 电话 */
|
/** 电话 */
|
||||||
@ExcelProperty(value = "电话", index = 4)
|
@ExcelProperty(value = "电话", index = 4)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(18)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String phone;
|
private String phone;
|
||||||
|
|
||||||
/** 年收入 */
|
/** 年收入 */
|
||||||
@ExcelProperty(value = "年收入(元/年)", index = 5)
|
@ExcelProperty(value = "年收入(元/年)", index = 5)
|
||||||
@ColumnWidth(18)
|
@ColumnWidth(20)
|
||||||
private BigDecimal annualIncome;
|
private BigDecimal annualIncome;
|
||||||
|
|
||||||
/** 入职时间 */
|
/** 入职时间 */
|
||||||
@ExcelProperty(value = "入职时间", index = 6)
|
@ExcelProperty(value = "入职时间", index = 6)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(18)
|
||||||
private Date hireDate;
|
private Date hireDate;
|
||||||
|
|
||||||
/** 是否党员 */
|
/** 是否党员 */
|
||||||
@ExcelProperty(value = "是否党员", index = 7)
|
@ExcelProperty(value = "是否党员", index = 7)
|
||||||
@ColumnWidth(12)
|
@ColumnWidth(16)
|
||||||
@DictDropdown(dictType = "ccdi_yes_no_flag")
|
@DictDropdown(dictType = "ccdi_yes_no_flag")
|
||||||
@Required
|
@Required
|
||||||
private Integer partyMember;
|
private Integer partyMember;
|
||||||
|
|
||||||
/** 状态 */
|
/** 状态 */
|
||||||
@ExcelProperty(value = "状态", index = 8)
|
@ExcelProperty(value = "状态", index = 8)
|
||||||
@ColumnWidth(10)
|
@ColumnWidth(14)
|
||||||
@DictDropdown(dictType = "ccdi_employee_status")
|
@DictDropdown(dictType = "ccdi_employee_status")
|
||||||
@Required
|
@Required
|
||||||
private String status;
|
private String status;
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ public class CcdiCustEnterpriseRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "身份证号", index = 0)
|
@ExcelProperty(value = "身份证号", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
@Schema(description = "身份证号")
|
@Schema(description = "身份证号")
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ public class CcdiCustEnterpriseRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||||
@ColumnWidth(25)
|
@ColumnWidth(25)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
@Schema(description = "统一社会信用代码")
|
@Schema(description = "统一社会信用代码")
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
|
|||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -26,6 +27,7 @@ public class CcdiCustFmyRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "信贷客户身份证号*", index = 0)
|
@ExcelProperty(value = "信贷客户身份证号*", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
/** 关系类型 */
|
/** 关系类型 */
|
||||||
@@ -63,16 +65,19 @@ public class CcdiCustFmyRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "关系人证件号码*", index = 6)
|
@ExcelProperty(value = "关系人证件号码*", index = 6)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String relationCertNo;
|
private String relationCertNo;
|
||||||
|
|
||||||
/** 手机号码1 */
|
/** 手机号码1 */
|
||||||
@ExcelProperty(value = "手机号码1", index = 7)
|
@ExcelProperty(value = "手机号码1", index = 7)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String mobilePhone1;
|
private String mobilePhone1;
|
||||||
|
|
||||||
/** 手机号码2 */
|
/** 手机号码2 */
|
||||||
@ExcelProperty(value = "手机号码2", index = 8)
|
@ExcelProperty(value = "手机号码2", index = 8)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String mobilePhone2;
|
private String mobilePhone2;
|
||||||
|
|
||||||
/** 微信名称1 */
|
/** 微信名称1 */
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -23,6 +24,7 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "统一社会信用代码*", index = 0)
|
@ExcelProperty(value = "统一社会信用代码*", index = 0)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@TextFormat
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
@ExcelProperty(value = "企业名称*", index = 1)
|
@ExcelProperty(value = "企业名称*", index = 1)
|
||||||
@@ -66,6 +68,7 @@ public class CcdiEnterpriseBaseInfoExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@TextFormat
|
||||||
private String legalCertNo;
|
private String legalCertNo;
|
||||||
|
|
||||||
@ExcelProperty(value = "股东1", index = 11)
|
@ExcelProperty(value = "股东1", index = 11)
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -17,17 +19,21 @@ public class CcdiIntermediaryEnterpriseRelationExcel implements Serializable {
|
|||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 中介本人证件号码 */
|
/** 中介本人证件号码 */
|
||||||
@ExcelProperty(value = "中介本人证件号码*", index = 0)
|
@ExcelProperty(value = "中介本人证件号码", index = 0)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String ownerPersonId;
|
private String ownerPersonId;
|
||||||
|
|
||||||
/** 统一社会信用代码 */
|
/** 统一社会信用代码 */
|
||||||
@ExcelProperty(value = "统一社会信用代码*", index = 1)
|
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
/** 关联人职务 */
|
/** 关联职务 */
|
||||||
@ExcelProperty(value = "关联人职务", index = 2)
|
@ExcelProperty(value = "关联职务", index = 2)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
private String relationPersonPost;
|
private String relationPersonPost;
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -29,6 +30,7 @@ public class CcdiIntermediaryEntityExcel implements Serializable {
|
|||||||
/** 统一社会信用代码 */
|
/** 统一社会信用代码 */
|
||||||
@ExcelProperty(value = "统一社会信用代码*", index = 1)
|
@ExcelProperty(value = "统一社会信用代码*", index = 1)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
|
@TextFormat
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
/** 主体类型 */
|
/** 主体类型 */
|
||||||
@@ -77,6 +79,7 @@ public class CcdiIntermediaryEntityExcel implements Serializable {
|
|||||||
/** 法定代表人证件号码 */
|
/** 法定代表人证件号码 */
|
||||||
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
@ExcelProperty(value = "法定代表人证件号码", index = 10)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
|
@TextFormat
|
||||||
private String legalCertNo;
|
private String legalCertNo;
|
||||||
|
|
||||||
/** 股东1 */
|
/** 股东1 */
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -52,11 +53,13 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
|
|||||||
/** 证件号码 */
|
/** 证件号码 */
|
||||||
@ExcelProperty(value = "证件号码*", index = 5)
|
@ExcelProperty(value = "证件号码*", index = 5)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
|
@TextFormat
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
/** 手机号码 */
|
/** 手机号码 */
|
||||||
@ExcelProperty(value = "手机号码", index = 6)
|
@ExcelProperty(value = "手机号码", index = 6)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String mobile;
|
private String mobile;
|
||||||
|
|
||||||
/** 微信号 */
|
/** 微信号 */
|
||||||
@@ -77,6 +80,7 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
|
|||||||
/** 企业统一信用码 */
|
/** 企业统一信用码 */
|
||||||
@ExcelProperty(value = "企业统一信用码", index = 10)
|
@ExcelProperty(value = "企业统一信用码", index = 10)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
|
@TextFormat
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
/** 职位 */
|
/** 职位 */
|
||||||
@@ -87,6 +91,7 @@ public class CcdiIntermediaryPersonExcel implements Serializable {
|
|||||||
/** 关联中介本人证件号码 */
|
/** 关联中介本人证件号码 */
|
||||||
@ExcelProperty(value = "关联中介本人证件号码", index = 12)
|
@ExcelProperty(value = "关联中介本人证件号码", index = 12)
|
||||||
@ColumnWidth(24)
|
@ColumnWidth(24)
|
||||||
|
@TextFormat
|
||||||
private String relatedNumId;
|
private String relatedNumId;
|
||||||
|
|
||||||
/** 备注 */
|
/** 备注 */
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -26,6 +27,7 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "采购事项ID", index = 0)
|
@ExcelProperty(value = "采购事项ID", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String purchaseId;
|
private String purchaseId;
|
||||||
|
|
||||||
/** 采购类别 */
|
/** 采购类别 */
|
||||||
@@ -138,6 +140,7 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "申请人工号", index = 21)
|
@ExcelProperty(value = "申请人工号", index = 21)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String applicantId;
|
private String applicantId;
|
||||||
|
|
||||||
/** 申请人姓名 */
|
/** 申请人姓名 */
|
||||||
@@ -155,6 +158,7 @@ public class CcdiPurchaseTransactionExcel implements Serializable {
|
|||||||
/** 采购负责人工号 */
|
/** 采购负责人工号 */
|
||||||
@ExcelProperty(value = "采购负责人工号", index = 24)
|
@ExcelProperty(value = "采购负责人工号", index = 24)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String purchaseLeaderId;
|
private String purchaseLeaderId;
|
||||||
|
|
||||||
/** 采购负责人姓名 */
|
/** 采购负责人姓名 */
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
|
|||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -21,6 +22,7 @@ public class CcdiPurchaseTransactionSupplierExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "采购事项ID", index = 0)
|
@ExcelProperty(value = "采购事项ID", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String purchaseId;
|
private String purchaseId;
|
||||||
|
|
||||||
@ExcelProperty(value = "供应商名称", index = 1)
|
@ExcelProperty(value = "供应商名称", index = 1)
|
||||||
@@ -30,6 +32,7 @@ public class CcdiPurchaseTransactionSupplierExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "供应商统一信用代码", index = 2)
|
@ExcelProperty(value = "供应商统一信用代码", index = 2)
|
||||||
@ColumnWidth(25)
|
@ColumnWidth(25)
|
||||||
|
@TextFormat
|
||||||
private String supplierUscc;
|
private String supplierUscc;
|
||||||
|
|
||||||
@ExcelProperty(value = "供应商联系人", index = 3)
|
@ExcelProperty(value = "供应商联系人", index = 3)
|
||||||
@@ -38,10 +41,12 @@ public class CcdiPurchaseTransactionSupplierExcel implements Serializable {
|
|||||||
|
|
||||||
@ExcelProperty(value = "供应商联系电话", index = 4)
|
@ExcelProperty(value = "供应商联系电话", index = 4)
|
||||||
@ColumnWidth(18)
|
@ColumnWidth(18)
|
||||||
|
@TextFormat
|
||||||
private String contactPhone;
|
private String contactPhone;
|
||||||
|
|
||||||
@ExcelProperty(value = "供应商银行账户", index = 5)
|
@ExcelProperty(value = "供应商银行账户", index = 5)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
|
@TextFormat
|
||||||
private String supplierBankAccount;
|
private String supplierBankAccount;
|
||||||
|
|
||||||
@ExcelProperty(value = "是否中标", index = 6)
|
@ExcelProperty(value = "是否中标", index = 6)
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
@@ -26,6 +27,7 @@ public class CcdiStaffEnterpriseRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "亲属身份证号", index = 0)
|
@ExcelProperty(value = "亲属身份证号", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
@Schema(description = "亲属身份证号")
|
@Schema(description = "亲属身份证号")
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
@@ -33,6 +35,7 @@ public class CcdiStaffEnterpriseRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
@ExcelProperty(value = "统一社会信用代码", index = 1)
|
||||||
@ColumnWidth(25)
|
@ColumnWidth(25)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
@Schema(description = "统一社会信用代码")
|
@Schema(description = "统一社会信用代码")
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ public class CcdiStaffFmyRelationExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "员工身份证号*", index = 0)
|
@ExcelProperty(value = "员工身份证号*", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String personId;
|
private String personId;
|
||||||
|
|
||||||
/** 关系类型 */
|
/** 关系类型 */
|
||||||
@@ -71,11 +72,13 @@ public class CcdiStaffFmyRelationExcel implements Serializable {
|
|||||||
/** 手机号码1 */
|
/** 手机号码1 */
|
||||||
@ExcelProperty(value = "手机号码1", index = 7)
|
@ExcelProperty(value = "手机号码1", index = 7)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String mobilePhone1;
|
private String mobilePhone1;
|
||||||
|
|
||||||
/** 手机号码2 */
|
/** 手机号码2 */
|
||||||
@ExcelProperty(value = "手机号码2", index = 8)
|
@ExcelProperty(value = "手机号码2", index = 8)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String mobilePhone2;
|
private String mobilePhone2;
|
||||||
|
|
||||||
/** 家庭成员年收入 */
|
/** 家庭成员年收入 */
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ import com.alibaba.excel.annotation.ExcelProperty;
|
|||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.DictDropdown;
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -21,10 +22,11 @@ public class CcdiStaffRecruitmentExcel implements Serializable {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
/** 招聘项目编号 */
|
/** 招聘记录编号 */
|
||||||
@ExcelProperty(value = "招聘项目编号", index = 0)
|
@ExcelProperty(value = "招聘记录编号", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
/** 招聘项目名称 */
|
/** 招聘项目名称 */
|
||||||
@@ -51,66 +53,76 @@ public class CcdiStaffRecruitmentExcel implements Serializable {
|
|||||||
@Required
|
@Required
|
||||||
private String posDesc;
|
private String posDesc;
|
||||||
|
|
||||||
/** 应聘人员姓名 */
|
|
||||||
@ExcelProperty(value = "应聘人员姓名", index = 5)
|
|
||||||
@ColumnWidth(15)
|
|
||||||
@Required
|
|
||||||
private String candName;
|
|
||||||
|
|
||||||
/** 应聘人员学历 */
|
|
||||||
@ExcelProperty(value = "应聘人员学历", index = 6)
|
|
||||||
@ColumnWidth(15)
|
|
||||||
@Required
|
|
||||||
private String candEdu;
|
|
||||||
|
|
||||||
/** 应聘人员证件号码 */
|
|
||||||
@ExcelProperty(value = "应聘人员证件号码", index = 7)
|
|
||||||
@ColumnWidth(20)
|
|
||||||
@Required
|
|
||||||
private String candId;
|
|
||||||
|
|
||||||
/** 应聘人员毕业院校 */
|
|
||||||
@ExcelProperty(value = "应聘人员毕业院校", index = 8)
|
|
||||||
@ColumnWidth(20)
|
|
||||||
@Required
|
|
||||||
private String candSchool;
|
|
||||||
|
|
||||||
/** 应聘人员专业 */
|
|
||||||
@ExcelProperty(value = "应聘人员专业", index = 9)
|
|
||||||
@ColumnWidth(15)
|
|
||||||
@Required
|
|
||||||
private String candMajor;
|
|
||||||
|
|
||||||
/** 应聘人员毕业年月 */
|
|
||||||
@ExcelProperty(value = "应聘人员毕业年月", index = 10)
|
|
||||||
@ColumnWidth(15)
|
|
||||||
@Required
|
|
||||||
private String candGrad;
|
|
||||||
|
|
||||||
/** 录用情况 */
|
/** 录用情况 */
|
||||||
@ExcelProperty(value = "录用情况", index = 11)
|
@ExcelProperty(value = "录用情况", index = 5)
|
||||||
@ColumnWidth(10)
|
@ColumnWidth(10)
|
||||||
@DictDropdown(dictType = "ccdi_admit_status")
|
@DictDropdown(dictType = "ccdi_admit_status")
|
||||||
@Required
|
@Required
|
||||||
private String admitStatus;
|
private String admitStatus;
|
||||||
|
|
||||||
|
/** 候选人姓名 */
|
||||||
|
@ExcelProperty(value = "候选人姓名", index = 6)
|
||||||
|
@ColumnWidth(15)
|
||||||
|
@Required
|
||||||
|
private String candName;
|
||||||
|
|
||||||
|
/** 招聘类型 */
|
||||||
|
@ExcelProperty(value = "招聘类型", index = 7)
|
||||||
|
@ColumnWidth(12)
|
||||||
|
@DictDropdown(dictType = "ccdi_recruit_type")
|
||||||
|
@Required
|
||||||
|
private String recruitType;
|
||||||
|
|
||||||
|
/** 应聘人员学历 */
|
||||||
|
@ExcelProperty(value = "学历", index = 8)
|
||||||
|
@ColumnWidth(15)
|
||||||
|
@Required
|
||||||
|
private String candEdu;
|
||||||
|
|
||||||
|
/** 应聘人员证件号码 */
|
||||||
|
@ExcelProperty(value = "证件号码", index = 9)
|
||||||
|
@ColumnWidth(20)
|
||||||
|
@Required
|
||||||
|
@TextFormat
|
||||||
|
private String candId;
|
||||||
|
|
||||||
|
/** 应聘人员毕业年月 */
|
||||||
|
@ExcelProperty(value = "毕业年月", index = 10)
|
||||||
|
@ColumnWidth(15)
|
||||||
|
@Required
|
||||||
|
private String candGrad;
|
||||||
|
|
||||||
|
/** 应聘人员毕业院校 */
|
||||||
|
@ExcelProperty(value = "毕业院校", index = 11)
|
||||||
|
@ColumnWidth(20)
|
||||||
|
@Required
|
||||||
|
private String candSchool;
|
||||||
|
|
||||||
|
/** 应聘人员专业 */
|
||||||
|
@ExcelProperty(value = "专业", index = 12)
|
||||||
|
@ColumnWidth(15)
|
||||||
|
@Required
|
||||||
|
private String candMajor;
|
||||||
|
|
||||||
/** 面试官1姓名 */
|
/** 面试官1姓名 */
|
||||||
@ExcelProperty(value = "面试官1姓名", index = 12)
|
@ExcelProperty(value = "面试官1姓名", index = 13)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
private String interviewerName1;
|
private String interviewerName1;
|
||||||
|
|
||||||
/** 面试官1工号 */
|
/** 面试官1工号 */
|
||||||
@ExcelProperty(value = "面试官1工号", index = 13)
|
@ExcelProperty(value = "面试官1工号", index = 14)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String interviewerId1;
|
private String interviewerId1;
|
||||||
|
|
||||||
/** 面试官2姓名 */
|
/** 面试官2姓名 */
|
||||||
@ExcelProperty(value = "面试官2姓名", index = 14)
|
@ExcelProperty(value = "面试官2姓名", index = 15)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
private String interviewerName2;
|
private String interviewerName2;
|
||||||
|
|
||||||
/** 面试官2工号 */
|
/** 面试官2工号 */
|
||||||
@ExcelProperty(value = "面试官2工号", index = 15)
|
@ExcelProperty(value = "面试官2工号", index = 16)
|
||||||
@ColumnWidth(15)
|
@ColumnWidth(15)
|
||||||
|
@TextFormat
|
||||||
private String interviewerId2;
|
private String interviewerId2;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.info.collection.domain.excel;
|
|||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
import com.alibaba.excel.annotation.write.style.ColumnWidth;
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
|
import com.ruoyi.common.annotation.TextFormat;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
|
||||||
import java.io.Serial;
|
import java.io.Serial;
|
||||||
@@ -24,6 +25,7 @@ public class CcdiStaffRecruitmentWorkExcel implements Serializable {
|
|||||||
@ExcelProperty(value = "招聘记录编号", index = 0)
|
@ExcelProperty(value = "招聘记录编号", index = 0)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
|
@TextFormat
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
/** 候选人姓名 */
|
/** 候选人姓名 */
|
||||||
@@ -61,20 +63,20 @@ public class CcdiStaffRecruitmentWorkExcel implements Serializable {
|
|||||||
@ColumnWidth(18)
|
@ColumnWidth(18)
|
||||||
private String departmentName;
|
private String departmentName;
|
||||||
|
|
||||||
/** 岗位 */
|
/** 岗位名称 */
|
||||||
@ExcelProperty(value = "岗位", index = 7)
|
@ExcelProperty(value = "岗位名称", index = 7)
|
||||||
@ColumnWidth(20)
|
@ColumnWidth(20)
|
||||||
@Required
|
@Required
|
||||||
private String positionName;
|
private String positionName;
|
||||||
|
|
||||||
/** 入职年月 */
|
/** 入职年月 */
|
||||||
@ExcelProperty(value = "入职年月", index = 8)
|
@ExcelProperty(value = "入职时间", index = 8)
|
||||||
@ColumnWidth(12)
|
@ColumnWidth(12)
|
||||||
@Required
|
@Required
|
||||||
private String jobStartMonth;
|
private String jobStartMonth;
|
||||||
|
|
||||||
/** 离职年月 */
|
/** 离职年月 */
|
||||||
@ExcelProperty(value = "离职年月", index = 9)
|
@ExcelProperty(value = "离职时间", index = 9)
|
||||||
@ColumnWidth(12)
|
@ColumnWidth(12)
|
||||||
private String jobEndMonth;
|
private String jobEndMonth;
|
||||||
|
|
||||||
@@ -83,8 +85,8 @@ public class CcdiStaffRecruitmentWorkExcel implements Serializable {
|
|||||||
@ColumnWidth(30)
|
@ColumnWidth(30)
|
||||||
private String departureReason;
|
private String departureReason;
|
||||||
|
|
||||||
/** 工作内容 */
|
/** 主要工作内容 */
|
||||||
@ExcelProperty(value = "工作内容", index = 11)
|
@ExcelProperty(value = "主要工作内容", index = 11)
|
||||||
@ColumnWidth(35)
|
@ColumnWidth(35)
|
||||||
private String workContent;
|
private String workContent;
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,9 @@ public class CcdiStaffRecruitmentVO implements Serializable {
|
|||||||
@Serial
|
@Serial
|
||||||
private static final long serialVersionUID = 1L;
|
private static final long serialVersionUID = 1L;
|
||||||
|
|
||||||
|
/** 主键ID */
|
||||||
|
private Long id;
|
||||||
|
|
||||||
/** 招聘记录编号 */
|
/** 招聘记录编号 */
|
||||||
private String recruitId;
|
private String recruitId;
|
||||||
|
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ public class IntermediaryEnterpriseRelationImportFailureVO implements Serializab
|
|||||||
@Schema(description = "统一社会信用代码")
|
@Schema(description = "统一社会信用代码")
|
||||||
private String socialCreditCode;
|
private String socialCreditCode;
|
||||||
|
|
||||||
@Schema(description = "关联人职务")
|
@Schema(description = "关联职务")
|
||||||
private String relationPersonPost;
|
private String relationPersonPost;
|
||||||
|
|
||||||
@Schema(description = "备注")
|
@Schema(description = "备注")
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ public enum EnterpriseSource {
|
|||||||
GENERAL("GENERAL", "一般企业"),
|
GENERAL("GENERAL", "一般企业"),
|
||||||
EMP_RELATION("EMP_RELATION", "员工关系人"),
|
EMP_RELATION("EMP_RELATION", "员工关系人"),
|
||||||
CREDIT_CUSTOMER("CREDIT_CUSTOMER", "信贷客户"),
|
CREDIT_CUSTOMER("CREDIT_CUSTOMER", "信贷客户"),
|
||||||
|
SUPPLIER("SUPPLIER", "供应商"),
|
||||||
INTERMEDIARY("INTERMEDIARY", "中介"),
|
INTERMEDIARY("INTERMEDIARY", "中介"),
|
||||||
BOTH("BOTH", "兼有");
|
BOTH("BOTH", "兼有");
|
||||||
|
|
||||||
|
|||||||
@@ -1,15 +1,28 @@
|
|||||||
package com.ruoyi.info.collection.handler;
|
package com.ruoyi.info.collection.handler;
|
||||||
|
|
||||||
import com.alibaba.excel.annotation.ExcelProperty;
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
import com.alibaba.excel.write.handler.SheetWriteHandler;
|
import com.alibaba.excel.metadata.Head;
|
||||||
|
import com.alibaba.excel.metadata.data.WriteCellData;
|
||||||
|
import com.alibaba.excel.write.handler.CellWriteHandler;
|
||||||
|
import com.alibaba.excel.write.metadata.holder.WriteTableHolder;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
import com.alibaba.excel.write.metadata.holder.WriteSheetHolder;
|
||||||
import com.alibaba.excel.write.metadata.holder.WriteWorkbookHolder;
|
|
||||||
import com.ruoyi.common.annotation.Required;
|
import com.ruoyi.common.annotation.Required;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.poi.ss.usermodel.*;
|
import org.apache.poi.ss.usermodel.BorderStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.CellStyle;
|
||||||
|
import org.apache.poi.ss.usermodel.Font;
|
||||||
|
import org.apache.poi.ss.usermodel.HorizontalAlignment;
|
||||||
|
import org.apache.poi.ss.usermodel.IndexedColors;
|
||||||
|
import org.apache.poi.ss.usermodel.VerticalAlignment;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
|
||||||
import java.lang.reflect.Field;
|
import java.lang.reflect.Field;
|
||||||
import java.util.*;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* EasyExcel必填字段标注处理器
|
* EasyExcel必填字段标注处理器
|
||||||
@@ -18,13 +31,18 @@ import java.util.*;
|
|||||||
* @author ruoyi
|
* @author ruoyi
|
||||||
*/
|
*/
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class RequiredFieldWriteHandler implements SheetWriteHandler {
|
public class RequiredFieldWriteHandler implements CellWriteHandler {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 实体类Class对象
|
* 实体类Class对象
|
||||||
*/
|
*/
|
||||||
private final Class<?> modelClass;
|
private final Class<?> modelClass;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 必填字段列索引集合
|
||||||
|
*/
|
||||||
|
private final Set<Integer> requiredColumns;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构造函数
|
* 构造函数
|
||||||
*
|
*
|
||||||
@@ -32,39 +50,30 @@ public class RequiredFieldWriteHandler implements SheetWriteHandler {
|
|||||||
*/
|
*/
|
||||||
public RequiredFieldWriteHandler(Class<?> modelClass) {
|
public RequiredFieldWriteHandler(Class<?> modelClass) {
|
||||||
this.modelClass = modelClass;
|
this.modelClass = modelClass;
|
||||||
|
this.requiredColumns = parseRequiredFields();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void afterSheetCreate(WriteWorkbookHolder writeWorkbookHolder, WriteSheetHolder writeSheetHolder) {
|
public void afterCellDispose(WriteSheetHolder writeSheetHolder,
|
||||||
// 获取工作表
|
WriteTableHolder writeTableHolder,
|
||||||
Sheet sheet = writeSheetHolder.getSheet();
|
List<WriteCellData<?>> cellDataList,
|
||||||
|
Cell cell,
|
||||||
// 获取表头行(第1行,索引为0)
|
Head head,
|
||||||
Row headerRow = sheet.getRow(0);
|
Integer relativeRowIndex,
|
||||||
if (headerRow == null) {
|
Boolean isHead) {
|
||||||
log.warn("表头行不存在,跳过必填字段标注");
|
if (!Boolean.TRUE.equals(isHead) || cell == null || !requiredColumns.contains(cell.getColumnIndex())) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// 创建红色字体样式
|
Workbook workbook = cell.getSheet().getWorkbook();
|
||||||
Workbook workbook = writeWorkbookHolder.getWorkbook();
|
|
||||||
CellStyle redStyle = createRedFontStyle(workbook);
|
CellStyle redStyle = createRedFontStyle(workbook);
|
||||||
|
|
||||||
// 解析实体类中的必填字段
|
String originalValue = cell.getStringCellValue();
|
||||||
Set<Integer> requiredColumns = parseRequiredFields();
|
if (originalValue != null && !originalValue.endsWith("*")) {
|
||||||
|
cell.setCellValue(originalValue + "*");
|
||||||
// 为必填字段的表头添加红色星号
|
|
||||||
for (Integer columnIndex : requiredColumns) {
|
|
||||||
Cell cell = headerRow.getCell(columnIndex);
|
|
||||||
if (cell != null) {
|
|
||||||
String originalValue = cell.getStringCellValue();
|
|
||||||
// 添加红色星号
|
|
||||||
cell.setCellValue(originalValue + "*");
|
|
||||||
// 应用红色样式到星号
|
|
||||||
cell.setCellStyle(redStyle);
|
|
||||||
log.info("为列[{}]的表头添加必填标记(*)", columnIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
cell.setCellStyle(redStyle);
|
||||||
|
log.info("为列[{}]的表头添加必填标记(*)", cell.getColumnIndex());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -30,10 +30,10 @@ public interface CcdiStaffRecruitmentMapper extends BaseMapper<CcdiStaffRecruitm
|
|||||||
/**
|
/**
|
||||||
* 查询招聘信息详情
|
* 查询招聘信息详情
|
||||||
*
|
*
|
||||||
* @param recruitId 招聘项目编号
|
* @param id 主键ID
|
||||||
* @return 招聘信息VO
|
* @return 招聘信息VO
|
||||||
*/
|
*/
|
||||||
CcdiStaffRecruitmentVO selectRecruitmentById(@Param("recruitId") String recruitId);
|
CcdiStaffRecruitmentVO selectRecruitmentById(@Param("id") Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 批量插入招聘信息数据
|
* 批量插入招聘信息数据
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.ruoyi.info.collection.domain.vo.AssetImportFailureVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 亲属资产信息异步导入 服务层
|
* 亲属资产信息异步导入 服务层
|
||||||
@@ -31,6 +33,19 @@ public interface ICcdiAssetInfoImportService {
|
|||||||
*/
|
*/
|
||||||
void importAssetInfoAsync(List<CcdiAssetInfoExcel> excelList, String taskId, String userName);
|
void importAssetInfoAsync(List<CcdiAssetInfoExcel> excelList, String taskId, String userName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步执行亲属资产导入,可附加同一文件亲属关系Sheet成功导入的归属映射
|
||||||
|
*
|
||||||
|
* @param excelList Excel实体列表
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param userName 用户名
|
||||||
|
* @param extraOwnerMappings 附加归属映射,key为亲属证件号,value为归属员工证件号集合
|
||||||
|
*/
|
||||||
|
void importAssetInfoSync(List<CcdiAssetInfoExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName,
|
||||||
|
Map<String, Set<String>> extraOwnerMappings);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询导入状态
|
* 查询导入状态
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,8 @@ import com.ruoyi.info.collection.domain.vo.BaseStaffAssetImportFailureVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工资产信息异步导入 服务层
|
* 员工资产信息异步导入 服务层
|
||||||
@@ -31,6 +33,19 @@ public interface ICcdiBaseStaffAssetImportService {
|
|||||||
*/
|
*/
|
||||||
void importAssetInfoAsync(List<CcdiBaseStaffAssetInfoExcel> excelList, String taskId, String userName);
|
void importAssetInfoAsync(List<CcdiBaseStaffAssetInfoExcel> excelList, String taskId, String userName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步执行员工资产导入,可附加同一文件员工Sheet成功导入的归属映射
|
||||||
|
*
|
||||||
|
* @param excelList Excel实体列表
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param userName 用户名
|
||||||
|
* @param extraOwnerMappings 附加归属映射,key为资产持有人证件号,value为归属员工证件号集合
|
||||||
|
*/
|
||||||
|
void importAssetInfoSync(List<CcdiBaseStaffAssetInfoExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName,
|
||||||
|
Map<String, Set<String>> extraOwnerMappings);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询导入状态
|
* 查询导入状态
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @Author: wkc
|
* @Author: wkc
|
||||||
@@ -19,6 +20,15 @@ public interface ICcdiBaseStaffImportService {
|
|||||||
*/
|
*/
|
||||||
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId);
|
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步执行员工导入并返回本轮成功员工身份证号
|
||||||
|
*
|
||||||
|
* @param excelList Excel数据列表
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @return 成功导入的身份证号集合
|
||||||
|
*/
|
||||||
|
Set<String> importBaseStaffSync(List<CcdiBaseStaffExcel> excelList, String taskId);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询导入状态
|
* 查询导入状态
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,7 +4,9 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.BaseStaffImportSubmitResultVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
|
||||||
|
|
||||||
@@ -83,6 +85,16 @@ public interface ICcdiBaseStaffService {
|
|||||||
*/
|
*/
|
||||||
String importBaseStaff(List<CcdiBaseStaffExcel> excelList);
|
String importBaseStaff(List<CcdiBaseStaffExcel> excelList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入员工信息和员工资产双Sheet数据
|
||||||
|
*
|
||||||
|
* @param staffList 员工信息Sheet
|
||||||
|
* @param assetList 员工资产Sheet
|
||||||
|
* @return 提交结果
|
||||||
|
*/
|
||||||
|
BaseStaffImportSubmitResultVO importBaseStaffWithAssets(List<CcdiBaseStaffExcel> staffList,
|
||||||
|
List<CcdiBaseStaffAssetInfoExcel> assetList);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工下拉列表
|
* 查询员工下拉列表
|
||||||
* 支持按员工ID或姓名模糊搜索,只返回在职员工
|
* 支持按员工ID或姓名模糊搜索,只返回在职员工
|
||||||
|
|||||||
@@ -5,6 +5,7 @@ import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工亲属关系异步导入 服务层
|
* 员工亲属关系异步导入 服务层
|
||||||
@@ -23,6 +24,16 @@ public interface ICcdiStaffFmyRelationImportService {
|
|||||||
*/
|
*/
|
||||||
void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName);
|
void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 同步执行员工亲属关系导入并返回本轮成功关系映射
|
||||||
|
*
|
||||||
|
* @param excelList Excel实体列表
|
||||||
|
* @param taskId 任务ID
|
||||||
|
* @param userName 用户名
|
||||||
|
* @return key为亲属证件号,value为归属员工证件号
|
||||||
|
*/
|
||||||
|
Map<String, String> importRelationSync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询导入失败记录
|
* 查询导入失败记录
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -4,8 +4,10 @@ import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@@ -81,4 +83,14 @@ public interface ICcdiStaffFmyRelationService {
|
|||||||
* @return 任务ID
|
* @return 任务ID
|
||||||
*/
|
*/
|
||||||
String importRelation(List<CcdiStaffFmyRelationExcel> excelList);
|
String importRelation(List<CcdiStaffFmyRelationExcel> excelList);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导入员工亲属关系和亲属资产双Sheet数据
|
||||||
|
*
|
||||||
|
* @param relationList 员工亲属关系Sheet
|
||||||
|
* @param assetList 亲属资产Sheet
|
||||||
|
* @return 提交结果
|
||||||
|
*/
|
||||||
|
StaffFmyRelationImportSubmitResultVO importRelationWithAssets(List<CcdiStaffFmyRelationExcel> relationList,
|
||||||
|
List<CcdiAssetInfoExcel> assetList);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -46,10 +46,10 @@ public interface ICcdiStaffRecruitmentService {
|
|||||||
/**
|
/**
|
||||||
* 查询招聘信息详情
|
* 查询招聘信息详情
|
||||||
*
|
*
|
||||||
* @param recruitId 招聘项目编号
|
* @param id 主键ID
|
||||||
* @return 招聘信息VO
|
* @return 招聘信息VO
|
||||||
*/
|
*/
|
||||||
CcdiStaffRecruitmentVO selectRecruitmentById(String recruitId);
|
CcdiStaffRecruitmentVO selectRecruitmentById(Long id);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 新增招聘信息
|
* 新增招聘信息
|
||||||
@@ -70,10 +70,10 @@ public interface ICcdiStaffRecruitmentService {
|
|||||||
/**
|
/**
|
||||||
* 批量删除招聘信息
|
* 批量删除招聘信息
|
||||||
*
|
*
|
||||||
* @param recruitIds 需要删除的招聘项目编号
|
* @param ids 需要删除的招聘信息ID
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
int deleteRecruitmentByIds(String[] recruitIds);
|
int deleteRecruitmentByIds(Long[] ids);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入招聘信息数据
|
* 导入招聘信息数据
|
||||||
|
|||||||
@@ -82,6 +82,15 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
|||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
public void importAssetInfoAsync(List<CcdiAssetInfoExcel> excelList, String taskId, String userName) {
|
public void importAssetInfoAsync(List<CcdiAssetInfoExcel> excelList, String taskId, String userName) {
|
||||||
|
importAssetInfoSync(excelList, taskId, userName, Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void importAssetInfoSync(List<CcdiAssetInfoExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName,
|
||||||
|
Map<String, Set<String>> extraOwnerMappings) {
|
||||||
List<CcdiAssetInfo> successList = new ArrayList<>();
|
List<CcdiAssetInfo> successList = new ArrayList<>();
|
||||||
List<AssetImportFailureVO> failures = new ArrayList<>();
|
List<AssetImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
@@ -92,6 +101,7 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
|
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
|
||||||
|
mergeOwnerMappings(ownerMap, extraOwnerMappings);
|
||||||
|
|
||||||
for (int i = 0; i < excelList.size(); i++) {
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
CcdiAssetInfoExcel excel = excelList.get(i);
|
CcdiAssetInfoExcel excel = excelList.get(i);
|
||||||
@@ -189,6 +199,18 @@ public class CcdiAssetInfoImportServiceImpl implements ICcdiAssetInfoImportServi
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mergeOwnerMappings(Map<String, Set<String>> result, Map<String, Set<String>> mappings) {
|
||||||
|
if (mappings == null || mappings.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, Set<String>> entry : mappings.entrySet()) {
|
||||||
|
if (StringUtils.isEmpty(entry.getKey()) || entry.getValue() == null || entry.getValue().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.computeIfAbsent(entry.getKey(), key -> new java.util.LinkedHashSet<>()).addAll(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateExcel(CcdiAssetInfoExcel excel) {
|
private void validateExcel(CcdiAssetInfoExcel excel) {
|
||||||
if (StringUtils.isEmpty(excel.getPersonId())) {
|
if (StringUtils.isEmpty(excel.getPersonId())) {
|
||||||
throw new RuntimeException("亲属证件号不能为空");
|
throw new RuntimeException("亲属证件号不能为空");
|
||||||
|
|||||||
@@ -81,6 +81,15 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
|||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
public void importAssetInfoAsync(List<CcdiBaseStaffAssetInfoExcel> excelList, String taskId, String userName) {
|
public void importAssetInfoAsync(List<CcdiBaseStaffAssetInfoExcel> excelList, String taskId, String userName) {
|
||||||
|
importAssetInfoSync(excelList, taskId, userName, Map.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void importAssetInfoSync(List<CcdiBaseStaffAssetInfoExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName,
|
||||||
|
Map<String, Set<String>> extraOwnerMappings) {
|
||||||
List<CcdiAssetInfo> successList = new ArrayList<>();
|
List<CcdiAssetInfo> successList = new ArrayList<>();
|
||||||
List<BaseStaffAssetImportFailureVO> failures = new ArrayList<>();
|
List<BaseStaffAssetImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
@@ -91,6 +100,7 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
|||||||
.toList();
|
.toList();
|
||||||
|
|
||||||
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
|
Map<String, Set<String>> ownerMap = buildOwnerMap(personIds);
|
||||||
|
mergeOwnerMappings(ownerMap, extraOwnerMappings);
|
||||||
Set<String> existingAssetKeys = buildExistingAssetKeys(personIds);
|
Set<String> existingAssetKeys = buildExistingAssetKeys(personIds);
|
||||||
Set<String> importedAssetKeys = new java.util.LinkedHashSet<>();
|
Set<String> importedAssetKeys = new java.util.LinkedHashSet<>();
|
||||||
|
|
||||||
@@ -207,6 +217,18 @@ public class CcdiBaseStaffAssetImportServiceImpl implements ICcdiBaseStaffAssetI
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mergeOwnerMappings(Map<String, Set<String>> result, Map<String, Set<String>> mappings) {
|
||||||
|
if (mappings == null || mappings.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, Set<String>> entry : mappings.entrySet()) {
|
||||||
|
if (StringUtils.isEmpty(entry.getKey()) || entry.getValue() == null || entry.getValue().isEmpty()) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
result.computeIfAbsent(entry.getKey(), key -> new java.util.LinkedHashSet<>()).addAll(entry.getValue());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void validateExcel(CcdiBaseStaffAssetInfoExcel excel) {
|
private void validateExcel(CcdiBaseStaffAssetInfoExcel excel) {
|
||||||
if (StringUtils.isEmpty(excel.getPersonId())) {
|
if (StringUtils.isEmpty(excel.getPersonId())) {
|
||||||
throw new RuntimeException("员工身份证号不能为空");
|
throw new RuntimeException("员工身份证号不能为空");
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
@@ -51,6 +52,12 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
|||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId) {
|
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, String taskId) {
|
||||||
|
importBaseStaffSync(excelList, taskId);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Set<String> importBaseStaffSync(List<CcdiBaseStaffExcel> excelList, String taskId) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// 记录导入开始
|
// 记录导入开始
|
||||||
@@ -153,6 +160,11 @@ public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportServi
|
|||||||
long duration = System.currentTimeMillis() - startTime;
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
ImportLogUtils.logImportComplete(log, taskId, "员工基础信息",
|
ImportLogUtils.logImportComplete(log, taskId, "员工基础信息",
|
||||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||||
|
|
||||||
|
return newRecords.stream()
|
||||||
|
.map(CcdiBaseStaff::getIdCard)
|
||||||
|
.filter(StringUtils::isNotEmpty)
|
||||||
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,15 +6,19 @@ import com.ruoyi.info.collection.domain.CcdiBaseStaff;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.BaseStaffImportSubmitResultVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
|
||||||
import com.ruoyi.info.collection.enums.EmployeeStatus;
|
import com.ruoyi.info.collection.enums.EmployeeStatus;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
|
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
|
import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
|
||||||
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
@@ -46,6 +50,12 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiAssetInfoService assetInfoService;
|
private ICcdiAssetInfoService assetInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiDualSheetImportOrchestrationService dualSheetImportOrchestrationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工列表
|
* 查询员工列表
|
||||||
*
|
*
|
||||||
@@ -218,28 +228,52 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
|||||||
@Transactional
|
@Transactional
|
||||||
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList) {
|
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList) {
|
||||||
String taskId = UUID.randomUUID().toString();
|
String taskId = UUID.randomUUID().toString();
|
||||||
long startTime = System.currentTimeMillis();
|
initializeImportStatus("import:baseStaff:", taskId, excelList.size());
|
||||||
|
|
||||||
// 初始化Redis状态
|
|
||||||
String statusKey = "import:baseStaff:" + taskId;
|
|
||||||
Map<String, Object> statusData = new HashMap<>();
|
|
||||||
statusData.put("taskId", taskId);
|
|
||||||
statusData.put("status", "PROCESSING");
|
|
||||||
statusData.put("totalCount", excelList.size());
|
|
||||||
statusData.put("successCount", 0);
|
|
||||||
statusData.put("failureCount", 0);
|
|
||||||
statusData.put("progress", 0);
|
|
||||||
statusData.put("startTime", startTime);
|
|
||||||
statusData.put("message", "正在处理...");
|
|
||||||
|
|
||||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
|
||||||
redisTemplate.expire(statusKey, 7, java.util.concurrent.TimeUnit.DAYS);
|
|
||||||
|
|
||||||
importAsyncService.importBaseStaffAsync(excelList, taskId);
|
importAsyncService.importBaseStaffAsync(excelList, taskId);
|
||||||
|
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public BaseStaffImportSubmitResultVO importBaseStaffWithAssets(List<CcdiBaseStaffExcel> staffList,
|
||||||
|
List<CcdiBaseStaffAssetInfoExcel> assetList) {
|
||||||
|
boolean hasStaffRows = staffList != null && !staffList.isEmpty();
|
||||||
|
boolean hasAssetRows = assetList != null && !assetList.isEmpty();
|
||||||
|
if (!hasStaffRows && !hasAssetRows) {
|
||||||
|
throw new RuntimeException("至少需要一条数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
BaseStaffImportSubmitResultVO result = new BaseStaffImportSubmitResultVO();
|
||||||
|
result.setMessage(buildImportSubmitMessage(hasStaffRows, hasAssetRows));
|
||||||
|
|
||||||
|
if (hasStaffRows && !hasAssetRows) {
|
||||||
|
result.setStaffTaskId(importBaseStaff(staffList));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (!hasStaffRows) {
|
||||||
|
result.setAssetTaskId(baseStaffAssetImportService.importAssetInfo(assetList));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String staffTaskId = UUID.randomUUID().toString();
|
||||||
|
String assetTaskId = UUID.randomUUID().toString();
|
||||||
|
initializeImportStatus("import:baseStaff:", staffTaskId, staffList.size());
|
||||||
|
initializeImportStatus("import:baseStaffAsset:", assetTaskId, assetList.size());
|
||||||
|
|
||||||
|
result.setStaffTaskId(staffTaskId);
|
||||||
|
result.setAssetTaskId(assetTaskId);
|
||||||
|
dualSheetImportOrchestrationService.importBaseStaffWithAssetsAsync(
|
||||||
|
staffList,
|
||||||
|
staffTaskId,
|
||||||
|
assetList,
|
||||||
|
assetTaskId,
|
||||||
|
currentUserName()
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工下拉列表
|
* 查询员工下拉列表
|
||||||
* 支持按员工ID或姓名模糊搜索,只返回在职员工
|
* 支持按员工ID或姓名模糊搜索,只返回在职员工
|
||||||
@@ -252,6 +286,40 @@ public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
|
|||||||
return baseStaffMapper.selectStaffOptions(query);
|
return baseStaffMapper.selectStaffOptions(query);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void initializeImportStatus(String keyPrefix, String taskId, int totalCount) {
|
||||||
|
Map<String, Object> statusData = new HashMap<>();
|
||||||
|
statusData.put("taskId", taskId);
|
||||||
|
statusData.put("status", "PROCESSING");
|
||||||
|
statusData.put("totalCount", totalCount);
|
||||||
|
statusData.put("successCount", 0);
|
||||||
|
statusData.put("failureCount", 0);
|
||||||
|
statusData.put("progress", 0);
|
||||||
|
statusData.put("startTime", System.currentTimeMillis());
|
||||||
|
statusData.put("message", "正在处理...");
|
||||||
|
|
||||||
|
String statusKey = keyPrefix + taskId;
|
||||||
|
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||||
|
redisTemplate.expire(statusKey, 7, java.util.concurrent.TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildImportSubmitMessage(boolean hasStaffRows, boolean hasAssetRows) {
|
||||||
|
if (hasStaffRows && hasAssetRows) {
|
||||||
|
return "已提交员工信息和员工资产信息导入任务";
|
||||||
|
}
|
||||||
|
if (hasStaffRows) {
|
||||||
|
return "已提交员工信息导入任务";
|
||||||
|
}
|
||||||
|
return "已提交员工资产信息导入任务";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String currentUserName() {
|
||||||
|
try {
|
||||||
|
return SecurityUtils.getUsername();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "system";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 构建查询条件
|
* 构建查询条件
|
||||||
*/
|
*/
|
||||||
|
|||||||
@@ -14,8 +14,10 @@ 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;
|
||||||
@@ -23,8 +25,6 @@ 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,9 +36,16 @@ 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;
|
||||||
|
|
||||||
@@ -141,37 +148,20 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleSingleFile(MultipartFile multipartFile, String userName) throws IOException {
|
private void handleSingleFile(MultipartFile multipartFile, String userName) throws Exception {
|
||||||
File tempFile = createTempFile(multipartFile);
|
CreditHtmlStorageService.StoredCreditHtml storedHtml = creditHtmlStorageService.save(multipartFile);
|
||||||
try {
|
CreditParseInvokeResponse response = creditParseClient.parse(storedHtml.remotePath());
|
||||||
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"));
|
String personName = stringValue(header.get("query_cust_name"));
|
||||||
String personName = stringValue(header.get("query_cust_name"));
|
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
|
||||||
LocalDate queryDate = parseQueryDate(stringValue(header.get("report_time")));
|
ensurePersonIdPresent(personId);
|
||||||
ensurePersonIdPresent(personId);
|
ensureLatestQueryDate(personId, queryDate);
|
||||||
ensureLatestQueryDate(personId, queryDate);
|
|
||||||
|
|
||||||
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) {
|
||||||
@@ -185,14 +175,41 @@ public class CcdiCreditInfoServiceImpl implements ICcdiCreditInfoService {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreditParseResponse requireResponse(CreditParseResponse response) {
|
private CreditParseResponse requireResponse(CreditParseInvokeResponse response) {
|
||||||
if (response == null || response.getPayload() == null) {
|
if (response == null || response.getData() == null || response.getData().getMappingOutputFields() == null) {
|
||||||
throw new RuntimeException("征信解析结果为空");
|
throw new RuntimeException("征信解析结果为空");
|
||||||
}
|
}
|
||||||
if (!"0".equals(response.getStatusCode())) {
|
CreditParseResponse mappingOutputFields = response.getData().getMappingOutputFields();
|
||||||
throw new RuntimeException(stringValue(response.getMessage(), "征信解析失败"));
|
if (!Boolean.TRUE.equals(response.getSuccess())) {
|
||||||
|
throw new RuntimeException(stringValue(mappingOutputFields.getMessage(), "征信解析平台调用失败"));
|
||||||
}
|
}
|
||||||
return response;
|
if (!Integer.valueOf(CREDIT_PARSE_SUCCESS_CODE).equals(response.getCode())) {
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -7,8 +7,11 @@ import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
|
|||||||
import com.ruoyi.info.collection.domain.vo.CustEnterpriseRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.CustEnterpriseRelationImportFailureVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -43,6 +46,9 @@ public class CcdiCustEnterpriseRelationImportServiceImpl implements ICcdiCustEnt
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -127,6 +133,15 @@ public class CcdiCustEnterpriseRelationImportServiceImpl implements ICcdiCustEnt
|
|||||||
|
|
||||||
// 批量插入新数据
|
// 批量插入新数据
|
||||||
if (!newRecords.isEmpty()) {
|
if (!newRecords.isEmpty()) {
|
||||||
|
enterpriseAutoFillService.ensureExistsBatch(newRecords.stream()
|
||||||
|
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
item.getSocialCreditCode(),
|
||||||
|
item.getEnterpriseName(),
|
||||||
|
EnterpriseSource.CREDIT_CUSTOMER.getCode(),
|
||||||
|
DataSource.IMPORT.getCode(),
|
||||||
|
userName
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
||||||
(newRecords.size() + 499) / 500, 500);
|
(newRecords.size() + 499) / 500, 500);
|
||||||
saveBatch(newRecords, 500);
|
saveBatch(newRecords, 500);
|
||||||
|
|||||||
@@ -8,9 +8,12 @@ import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationEditDTO;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationService;
|
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -43,6 +46,9 @@ public class CcdiCustEnterpriseRelationServiceImpl implements ICcdiCustEnterpris
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询信贷客户实体关联列表
|
* 查询信贷客户实体关联列表
|
||||||
*
|
*
|
||||||
@@ -135,6 +141,14 @@ public class CcdiCustEnterpriseRelationServiceImpl implements ICcdiCustEnterpris
|
|||||||
relation.setDataSource("MANUAL");
|
relation.setDataSource("MANUAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
addDTO.getSocialCreditCode(),
|
||||||
|
addDTO.getEnterpriseName(),
|
||||||
|
EnterpriseSource.CREDIT_CUSTOMER.getCode(),
|
||||||
|
DataSource.MANUAL.getCode(),
|
||||||
|
SecurityUtils.getUsername()
|
||||||
|
));
|
||||||
|
|
||||||
int result = relationMapper.insert(relation);
|
int result = relationMapper.insert(relation);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -0,0 +1,90 @@
|
|||||||
|
package com.ruoyi.info.collection.service.impl;
|
||||||
|
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffAssetImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.scheduling.annotation.Async;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.LinkedHashSet;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 双Sheet导入后台顺序编排。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class CcdiDualSheetImportOrchestrationService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiBaseStaffImportService baseStaffImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiBaseStaffAssetImportService baseStaffAssetImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiStaffFmyRelationImportService relationImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiAssetInfoImportService assetInfoImportService;
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void importBaseStaffWithAssetsAsync(List<CcdiBaseStaffExcel> staffList,
|
||||||
|
String staffTaskId,
|
||||||
|
List<CcdiBaseStaffAssetInfoExcel> assetList,
|
||||||
|
String assetTaskId,
|
||||||
|
String userName) {
|
||||||
|
Set<String> successIdCards = baseStaffImportService.importBaseStaffSync(staffList, staffTaskId);
|
||||||
|
baseStaffAssetImportService.importAssetInfoSync(
|
||||||
|
assetList,
|
||||||
|
assetTaskId,
|
||||||
|
userName,
|
||||||
|
buildSelfOwnerMappings(successIdCards)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Async
|
||||||
|
public void importRelationWithAssetsAsync(List<CcdiStaffFmyRelationExcel> relationList,
|
||||||
|
String relationTaskId,
|
||||||
|
List<CcdiAssetInfoExcel> assetList,
|
||||||
|
String assetTaskId,
|
||||||
|
String userName) {
|
||||||
|
Map<String, String> successRelationMappings = relationImportService.importRelationSync(relationList, relationTaskId, userName);
|
||||||
|
assetInfoImportService.importAssetInfoSync(
|
||||||
|
assetList,
|
||||||
|
assetTaskId,
|
||||||
|
userName,
|
||||||
|
buildRelationOwnerMappings(successRelationMappings)
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Set<String>> buildSelfOwnerMappings(Set<String> idCards) {
|
||||||
|
Map<String, Set<String>> result = new LinkedHashMap<>();
|
||||||
|
if (idCards == null || idCards.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (String idCard : idCards) {
|
||||||
|
result.computeIfAbsent(idCard, key -> new LinkedHashSet<>()).add(idCard);
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Set<String>> buildRelationOwnerMappings(Map<String, String> relationMappings) {
|
||||||
|
Map<String, Set<String>> result = new LinkedHashMap<>();
|
||||||
|
if (relationMappings == null || relationMappings.isEmpty()) {
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
for (Map.Entry<String, String> entry : relationMappings.entrySet()) {
|
||||||
|
result.computeIfAbsent(entry.getKey(), key -> new LinkedHashSet<>()).add(entry.getValue());
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -131,14 +131,18 @@ public class CcdiEnterpriseBaseInfoImportServiceImpl implements ICcdiEnterpriseB
|
|||||||
if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
|
if (!excel.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
|
||||||
throw new RuntimeException("统一社会信用代码格式不正确");
|
throw new RuntimeException("统一社会信用代码格式不正确");
|
||||||
}
|
}
|
||||||
String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel()));
|
|
||||||
if (riskLevel == null) {
|
|
||||||
throw new RuntimeException("风险等级不在允许范围内");
|
|
||||||
}
|
|
||||||
String entSource = EnterpriseSource.resolveCode(StringUtils.trim(excel.getEntSource()));
|
String entSource = EnterpriseSource.resolveCode(StringUtils.trim(excel.getEntSource()));
|
||||||
if (entSource == null) {
|
if (entSource == null) {
|
||||||
throw new RuntimeException("企业来源不在允许范围内");
|
throw new RuntimeException("企业来源不在允许范围内");
|
||||||
}
|
}
|
||||||
|
String riskLevel = EnterpriseRiskLevel.resolveCode(StringUtils.trim(excel.getRiskLevel()));
|
||||||
|
if (riskLevel == null) {
|
||||||
|
if (EnterpriseSource.INTERMEDIARY.getCode().equals(entSource) && StringUtils.isEmpty(excel.getRiskLevel())) {
|
||||||
|
riskLevel = "1";
|
||||||
|
} else {
|
||||||
|
throw new RuntimeException("风险等级不在允许范围内");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
||||||
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
|
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
|
||||||
|
|||||||
@@ -3,16 +3,17 @@ package com.ruoyi.info.collection.service.impl;
|
|||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
||||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
|
||||||
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
|
||||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImportService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||||
import com.ruoyi.common.utils.IdCardUtil;
|
import com.ruoyi.common.utils.IdCardUtil;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
@@ -54,10 +55,10 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
private CcdiBizIntermediaryMapper intermediaryMapper;
|
private CcdiBizIntermediaryMapper intermediaryMapper;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@@ -67,7 +68,6 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
ImportLogUtils.logImportStart(log, taskId, "中介实体关联关系", excelList.size(), userName);
|
ImportLogUtils.logImportStart(log, taskId, "中介实体关联关系", excelList.size(), userName);
|
||||||
|
|
||||||
Map<String, String> ownerBizIdByPersonId = getOwnerBizIdByPersonId(excelList);
|
Map<String, String> ownerBizIdByPersonId = getOwnerBizIdByPersonId(excelList);
|
||||||
Set<String> existingEnterpriseCodes = getExistingEnterpriseCodes(excelList);
|
|
||||||
Set<String> existingCombinations = getExistingRelationCombinations(ownerBizIdByPersonId, excelList);
|
Set<String> existingCombinations = getExistingRelationCombinations(ownerBizIdByPersonId, excelList);
|
||||||
|
|
||||||
List<CcdiIntermediaryEnterpriseRelation> successRecords = new ArrayList<>();
|
List<CcdiIntermediaryEnterpriseRelation> successRecords = new ArrayList<>();
|
||||||
@@ -79,15 +79,14 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
try {
|
try {
|
||||||
validateExcel(excel);
|
validateExcel(excel);
|
||||||
|
|
||||||
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
|
String ownerPersonId = trim(excel.getOwnerPersonId());
|
||||||
|
String socialCreditCode = trim(excel.getSocialCreditCode());
|
||||||
|
String ownerBizId = ownerBizIdByPersonId.get(ownerPersonId);
|
||||||
if (StringUtils.isEmpty(ownerBizId)) {
|
if (StringUtils.isEmpty(ownerBizId)) {
|
||||||
throw new RuntimeException("中介本人不存在,请先导入或维护中介本人信息");
|
throw new RuntimeException("中介本人不存在,请先导入或维护中介本人信息");
|
||||||
}
|
}
|
||||||
if (!existingEnterpriseCodes.contains(excel.getSocialCreditCode())) {
|
|
||||||
throw new RuntimeException("统一社会信用代码不存在于系统机构表");
|
|
||||||
}
|
|
||||||
|
|
||||||
String combination = ownerBizId + "|" + excel.getSocialCreditCode();
|
String combination = ownerBizId + "|" + socialCreditCode;
|
||||||
if (existingCombinations.contains(combination)) {
|
if (existingCombinations.contains(combination)) {
|
||||||
throw new RuntimeException("中介实体关联关系已存在,请勿重复导入");
|
throw new RuntimeException("中介实体关联关系已存在,请勿重复导入");
|
||||||
}
|
}
|
||||||
@@ -98,6 +97,9 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
||||||
BeanUtils.copyProperties(excel, relation);
|
BeanUtils.copyProperties(excel, relation);
|
||||||
relation.setIntermediaryBizId(ownerBizId);
|
relation.setIntermediaryBizId(ownerBizId);
|
||||||
|
relation.setSocialCreditCode(socialCreditCode);
|
||||||
|
relation.setRelationPersonPost(trim(excel.getRelationPersonPost()));
|
||||||
|
relation.setRemark(trim(excel.getRemark()));
|
||||||
relation.setCreatedBy(userName);
|
relation.setCreatedBy(userName);
|
||||||
relation.setUpdatedBy(userName);
|
relation.setUpdatedBy(userName);
|
||||||
successRecords.add(relation);
|
successRecords.add(relation);
|
||||||
@@ -109,6 +111,15 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!successRecords.isEmpty()) {
|
if (!successRecords.isEmpty()) {
|
||||||
|
enterpriseAutoFillService.ensureExistsBatch(successRecords.stream()
|
||||||
|
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
item.getSocialCreditCode(),
|
||||||
|
null,
|
||||||
|
EnterpriseSource.INTERMEDIARY.getCode(),
|
||||||
|
DataSource.IMPORT.getCode(),
|
||||||
|
userName
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
saveBatch(successRecords, 500);
|
saveBatch(successRecords, 500);
|
||||||
}
|
}
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
@@ -159,6 +170,7 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
private Map<String, String> getOwnerBizIdByPersonId(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
|
private Map<String, String> getOwnerBizIdByPersonId(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
|
||||||
List<String> ownerPersonIds = excelList.stream()
|
List<String> ownerPersonIds = excelList.stream()
|
||||||
.map(CcdiIntermediaryEnterpriseRelationExcel::getOwnerPersonId)
|
.map(CcdiIntermediaryEnterpriseRelationExcel::getOwnerPersonId)
|
||||||
|
.map(this::trim)
|
||||||
.filter(StringUtils::isNotEmpty)
|
.filter(StringUtils::isNotEmpty)
|
||||||
.distinct()
|
.distinct()
|
||||||
.collect(Collectors.toList());
|
.collect(Collectors.toList());
|
||||||
@@ -173,32 +185,16 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
.collect(Collectors.toMap(CcdiBizIntermediary::getPersonId, CcdiBizIntermediary::getBizId, (left, right) -> left));
|
.collect(Collectors.toMap(CcdiBizIntermediary::getPersonId, CcdiBizIntermediary::getBizId, (left, right) -> left));
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getExistingEnterpriseCodes(List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
|
|
||||||
List<String> socialCreditCodes = excelList.stream()
|
|
||||||
.map(CcdiIntermediaryEnterpriseRelationExcel::getSocialCreditCode)
|
|
||||||
.filter(StringUtils::isNotEmpty)
|
|
||||||
.distinct()
|
|
||||||
.collect(Collectors.toList());
|
|
||||||
if (socialCreditCodes.isEmpty()) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.in(CcdiEnterpriseBaseInfo::getSocialCreditCode, socialCreditCodes);
|
|
||||||
return enterpriseBaseInfoMapper.selectList(wrapper).stream()
|
|
||||||
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private Set<String> getExistingRelationCombinations(Map<String, String> ownerBizIdByPersonId,
|
private Set<String> getExistingRelationCombinations(Map<String, String> ownerBizIdByPersonId,
|
||||||
List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
|
List<CcdiIntermediaryEnterpriseRelationExcel> excelList) {
|
||||||
List<String> combinations = excelList.stream()
|
List<String> combinations = excelList.stream()
|
||||||
.map(excel -> {
|
.map(excel -> {
|
||||||
String ownerBizId = ownerBizIdByPersonId.get(excel.getOwnerPersonId());
|
String ownerBizId = ownerBizIdByPersonId.get(trim(excel.getOwnerPersonId()));
|
||||||
if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(excel.getSocialCreditCode())) {
|
String socialCreditCode = trim(excel.getSocialCreditCode());
|
||||||
|
if (StringUtils.isEmpty(ownerBizId) || StringUtils.isEmpty(socialCreditCode)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
return ownerBizId + "|" + excel.getSocialCreditCode();
|
return ownerBizId + "|" + socialCreditCode;
|
||||||
})
|
})
|
||||||
.filter(StringUtils::isNotEmpty)
|
.filter(StringUtils::isNotEmpty)
|
||||||
.distinct()
|
.distinct()
|
||||||
@@ -210,24 +206,33 @@ public class CcdiIntermediaryEnterpriseRelationImportServiceImpl implements ICcd
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void validateExcel(CcdiIntermediaryEnterpriseRelationExcel excel) {
|
private void validateExcel(CcdiIntermediaryEnterpriseRelationExcel excel) {
|
||||||
if (StringUtils.isEmpty(excel.getOwnerPersonId())) {
|
String ownerPersonId = trim(excel.getOwnerPersonId());
|
||||||
|
String socialCreditCode = trim(excel.getSocialCreditCode());
|
||||||
|
String relationPersonPost = trim(excel.getRelationPersonPost());
|
||||||
|
String remark = trim(excel.getRemark());
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(ownerPersonId)) {
|
||||||
throw new RuntimeException("中介本人证件号码不能为空");
|
throw new RuntimeException("中介本人证件号码不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getSocialCreditCode())) {
|
if (StringUtils.isEmpty(socialCreditCode)) {
|
||||||
throw new RuntimeException("统一社会信用代码不能为空");
|
throw new RuntimeException("统一社会信用代码不能为空");
|
||||||
}
|
}
|
||||||
String ownerPersonIdError = IdCardUtil.getErrorMessage(excel.getOwnerPersonId());
|
String ownerPersonIdError = IdCardUtil.getErrorMessage(ownerPersonId);
|
||||||
if (ownerPersonIdError != null) {
|
if (ownerPersonIdError != null) {
|
||||||
throw new RuntimeException("中介本人证件号码" + ownerPersonIdError);
|
throw new RuntimeException("中介本人证件号码" + ownerPersonIdError);
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(excel.getRelationPersonPost()) && excel.getRelationPersonPost().length() > 100) {
|
if (StringUtils.isNotEmpty(relationPersonPost) && relationPersonPost.length() > 100) {
|
||||||
throw new RuntimeException("关联人职务长度不能超过100个字符");
|
throw new RuntimeException("关联职务长度不能超过100个字符");
|
||||||
}
|
}
|
||||||
if (StringUtils.isNotEmpty(excel.getRemark()) && excel.getRemark().length() > 500) {
|
if (StringUtils.isNotEmpty(remark) && remark.length() > 500) {
|
||||||
throw new RuntimeException("备注长度不能超过500个字符");
|
throw new RuntimeException("备注长度不能超过500个字符");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String trim(String value) {
|
||||||
|
return value == null ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
private IntermediaryEnterpriseRelationImportFailureVO createFailureVO(CcdiIntermediaryEnterpriseRelationExcel excel,
|
private IntermediaryEnterpriseRelationImportFailureVO createFailureVO(CcdiIntermediaryEnterpriseRelationExcel excel,
|
||||||
String errorMessage) {
|
String errorMessage) {
|
||||||
IntermediaryEnterpriseRelationImportFailureVO failure = new IntermediaryEnterpriseRelationImportFailureVO();
|
IntermediaryEnterpriseRelationImportFailureVO failure = new IntermediaryEnterpriseRelationImportFailureVO();
|
||||||
|
|||||||
@@ -14,6 +14,8 @@ import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryEntityDetailVO;
|
|||||||
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryPersonDetailVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryPersonDetailVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryRelativeVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryRelativeVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiIntermediaryVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
@@ -22,6 +24,7 @@ import com.ruoyi.info.collection.service.ICcdiIntermediaryEnterpriseRelationImpo
|
|||||||
import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService;
|
import com.ruoyi.info.collection.service.ICcdiIntermediaryEntityImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
|
import com.ruoyi.info.collection.service.ICcdiIntermediaryPersonImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
|
import com.ruoyi.info.collection.service.ICcdiIntermediaryService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -69,6 +72,9 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 分页查询中介列表
|
* 分页查询中介列表
|
||||||
* 使用XML联合查询实现,支持个人中介和实体中介的灵活查询
|
* 使用XML联合查询实现,支持个人中介和实体中介的灵活查询
|
||||||
@@ -302,6 +308,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
||||||
BeanUtils.copyProperties(addDTO, relation);
|
BeanUtils.copyProperties(addDTO, relation);
|
||||||
relation.setIntermediaryBizId(owner.getBizId());
|
relation.setIntermediaryBizId(owner.getBizId());
|
||||||
|
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
addDTO.getSocialCreditCode(),
|
||||||
|
null,
|
||||||
|
EnterpriseSource.INTERMEDIARY.getCode(),
|
||||||
|
DataSource.MANUAL.getCode(),
|
||||||
|
SecurityUtils.getUsername()
|
||||||
|
));
|
||||||
return enterpriseRelationMapper.insert(relation);
|
return enterpriseRelationMapper.insert(relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -317,6 +330,13 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
CcdiIntermediaryEnterpriseRelation relation = new CcdiIntermediaryEnterpriseRelation();
|
||||||
BeanUtils.copyProperties(editDTO, relation);
|
BeanUtils.copyProperties(editDTO, relation);
|
||||||
relation.setIntermediaryBizId(existing.getIntermediaryBizId());
|
relation.setIntermediaryBizId(existing.getIntermediaryBizId());
|
||||||
|
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
editDTO.getSocialCreditCode(),
|
||||||
|
null,
|
||||||
|
EnterpriseSource.INTERMEDIARY.getCode(),
|
||||||
|
DataSource.MANUAL.getCode(),
|
||||||
|
SecurityUtils.getUsername()
|
||||||
|
));
|
||||||
return enterpriseRelationMapper.updateById(relation);
|
return enterpriseRelationMapper.updateById(relation);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -520,9 +540,6 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
|
|
||||||
private void validateEnterpriseRelation(String bizId, String socialCreditCode, Long excludeId) {
|
private void validateEnterpriseRelation(String bizId, String socialCreditCode, Long excludeId) {
|
||||||
requireIntermediaryPerson(bizId);
|
requireIntermediaryPerson(bizId);
|
||||||
if (enterpriseBaseInfoMapper.selectById(socialCreditCode) == null) {
|
|
||||||
throw new RuntimeException("关联机构不存在");
|
|
||||||
}
|
|
||||||
boolean exists = enterpriseRelationMapper.existsByIntermediaryBizIdAndSocialCreditCode(bizId, socialCreditCode);
|
boolean exists = enterpriseRelationMapper.existsByIntermediaryBizIdAndSocialCreditCode(bizId, socialCreditCode);
|
||||||
if (exists) {
|
if (exists) {
|
||||||
if (excludeId == null) {
|
if (excludeId == null) {
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExc
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.PurchaseTransactionImportFailureVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
|
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
|
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
|
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -53,6 +56,9 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -183,6 +189,7 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
|
|||||||
|
|
||||||
// 批量插入新数据
|
// 批量插入新数据
|
||||||
if (!newTransactions.isEmpty()) {
|
if (!newTransactions.isEmpty()) {
|
||||||
|
autoFillSupplierEnterprises(newSuppliers, userName);
|
||||||
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
||||||
(newTransactions.size() + 499) / 500, 500);
|
(newTransactions.size() + 499) / 500, 500);
|
||||||
saveBatch(newTransactions, 500);
|
saveBatch(newTransactions, 500);
|
||||||
@@ -328,6 +335,19 @@ public class CcdiPurchaseTransactionImportServiceImpl implements ICcdiPurchaseTr
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void autoFillSupplierEnterprises(List<CcdiPurchaseTransactionSupplier> supplierList, String userName) {
|
||||||
|
enterpriseAutoFillService.ensureExistsBatch(supplierList.stream()
|
||||||
|
.filter(item -> StringUtils.isNotEmpty(item.getSupplierUscc()))
|
||||||
|
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
item.getSupplierUscc(),
|
||||||
|
item.getSupplierName(),
|
||||||
|
EnterpriseSource.SUPPLIER.getCode(),
|
||||||
|
DataSource.IMPORT.getCode(),
|
||||||
|
userName
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 验证采购交易数据
|
* 验证采购交易数据
|
||||||
*
|
*
|
||||||
|
|||||||
@@ -11,10 +11,13 @@ import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
|
|||||||
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiPurchaseTransactionSupplierVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
|
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
|
import com.ruoyi.info.collection.mapper.CcdiPurchaseTransactionSupplierMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
|
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService;
|
import com.ruoyi.info.collection.service.ICcdiPurchaseTransactionService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -55,6 +58,9 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询采购交易列表
|
* 查询采购交易列表
|
||||||
*
|
*
|
||||||
@@ -134,6 +140,7 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
|||||||
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
||||||
BeanUtils.copyProperties(addDTO, transaction);
|
BeanUtils.copyProperties(addDTO, transaction);
|
||||||
fillWinnerSummary(transaction, supplierList);
|
fillWinnerSummary(transaction, supplierList);
|
||||||
|
autoFillSupplierEnterprises(supplierList, DataSource.MANUAL.getCode(), SecurityUtils.getUsername());
|
||||||
int result = transactionMapper.insert(transaction);
|
int result = transactionMapper.insert(transaction);
|
||||||
saveSuppliers(supplierList);
|
saveSuppliers(supplierList);
|
||||||
|
|
||||||
@@ -331,6 +338,21 @@ public class CcdiPurchaseTransactionServiceImpl implements ICcdiPurchaseTransact
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void autoFillSupplierEnterprises(List<CcdiPurchaseTransactionSupplier> supplierList,
|
||||||
|
String dataSource,
|
||||||
|
String userName) {
|
||||||
|
enterpriseAutoFillService.ensureExistsBatch(supplierList.stream()
|
||||||
|
.filter(item -> StringUtils.isNotEmpty(item.getSupplierUscc()))
|
||||||
|
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
item.getSupplierUscc(),
|
||||||
|
item.getSupplierName(),
|
||||||
|
EnterpriseSource.SUPPLIER.getCode(),
|
||||||
|
dataSource,
|
||||||
|
userName
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
|
}
|
||||||
|
|
||||||
private List<CcdiPurchaseTransactionSupplierVO> selectSupplierListByPurchaseId(String purchaseId) {
|
private List<CcdiPurchaseTransactionSupplierVO> selectSupplierListByPurchaseId(String purchaseId) {
|
||||||
return supplierMapper.selectList(
|
return supplierMapper.selectList(
|
||||||
new LambdaQueryWrapper<CcdiPurchaseTransactionSupplier>()
|
new LambdaQueryWrapper<CcdiPurchaseTransactionSupplier>()
|
||||||
|
|||||||
@@ -9,9 +9,12 @@ import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
|
|||||||
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
import com.ruoyi.info.collection.domain.vo.ImportResult;
|
||||||
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
import com.ruoyi.info.collection.utils.ImportLogUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -49,6 +52,9 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiStaffFmyRelationMapper familyRelationMapper;
|
private CcdiStaffFmyRelationMapper familyRelationMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
@@ -147,6 +153,15 @@ public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffE
|
|||||||
|
|
||||||
// 批量插入新数据
|
// 批量插入新数据
|
||||||
if (!newRecords.isEmpty()) {
|
if (!newRecords.isEmpty()) {
|
||||||
|
enterpriseAutoFillService.ensureExistsBatch(newRecords.stream()
|
||||||
|
.map(item -> new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
item.getSocialCreditCode(),
|
||||||
|
item.getEnterpriseName(),
|
||||||
|
EnterpriseSource.EMP_RELATION.getCode(),
|
||||||
|
DataSource.IMPORT.getCode(),
|
||||||
|
userName
|
||||||
|
))
|
||||||
|
.toList());
|
||||||
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
|
||||||
(newRecords.size() + 499) / 500, 500);
|
(newRecords.size() + 499) / 500, 500);
|
||||||
saveBatch(newRecords, 500);
|
saveBatch(newRecords, 500);
|
||||||
|
|||||||
@@ -10,10 +10,13 @@ import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
|
|||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
|
||||||
|
import com.ruoyi.info.collection.enums.DataSource;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
|
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -49,6 +52,9 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
|||||||
@Resource
|
@Resource
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工实体关系列表
|
* 查询员工实体关系列表
|
||||||
*
|
*
|
||||||
@@ -144,6 +150,14 @@ public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpr
|
|||||||
relation.setDataSource("MANUAL");
|
relation.setDataSource("MANUAL");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
enterpriseAutoFillService.ensureExists(new EnterpriseAutoFillService.EnterpriseFillItem(
|
||||||
|
addDTO.getSocialCreditCode(),
|
||||||
|
addDTO.getEnterpriseName(),
|
||||||
|
EnterpriseSource.EMP_RELATION.getCode(),
|
||||||
|
DataSource.MANUAL.getCode(),
|
||||||
|
SecurityUtils.getUsername()
|
||||||
|
));
|
||||||
|
|
||||||
int result = relationMapper.insert(relation);
|
int result = relationMapper.insert(relation);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
|
|||||||
@@ -57,6 +57,12 @@ public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelat
|
|||||||
@Async
|
@Async
|
||||||
@Transactional
|
@Transactional
|
||||||
public void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName) {
|
public void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName) {
|
||||||
|
importRelationSync(excelList, taskId, userName);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public Map<String, String> importRelationSync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName) {
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
// 记录导入开始
|
// 记录导入开始
|
||||||
@@ -213,6 +219,15 @@ public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelat
|
|||||||
long duration = System.currentTimeMillis() - startTime;
|
long duration = System.currentTimeMillis() - startTime;
|
||||||
ImportLogUtils.logImportComplete(log, taskId, "员工亲属关系",
|
ImportLogUtils.logImportComplete(log, taskId, "员工亲属关系",
|
||||||
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
|
||||||
|
|
||||||
|
return newRecords.stream()
|
||||||
|
.filter(item -> StringUtils.isNotEmpty(item.getRelationCertNo()) && StringUtils.isNotEmpty(item.getPersonId()))
|
||||||
|
.collect(Collectors.toMap(
|
||||||
|
CcdiStaffFmyRelation::getRelationCertNo,
|
||||||
|
CcdiStaffFmyRelation::getPersonId,
|
||||||
|
(left, right) -> left,
|
||||||
|
LinkedHashMap::new
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -6,11 +6,14 @@ import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
|
|||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiAssetInfoVO;
|
||||||
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
|
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
|
||||||
|
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportSubmitResultVO;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||||
|
import com.ruoyi.info.collection.service.ICcdiAssetInfoImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
|
import com.ruoyi.info.collection.service.ICcdiAssetInfoService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
|
||||||
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
|
||||||
@@ -51,9 +54,15 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiAssetInfoService assetInfoService;
|
private ICcdiAssetInfoService assetInfoService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiAssetInfoImportService assetInfoImportService;
|
||||||
|
|
||||||
@Resource
|
@Resource
|
||||||
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
|
private CcdiStaffEnterpriseRelationMapper staffEnterpriseRelationMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiDualSheetImportOrchestrationService dualSheetImportOrchestrationService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询员工亲属关系列表
|
* 查询员工亲属关系列表
|
||||||
*
|
*
|
||||||
@@ -207,25 +216,11 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
|
|||||||
|
|
||||||
// 生成任务ID
|
// 生成任务ID
|
||||||
String taskId = UUID.randomUUID().toString();
|
String taskId = UUID.randomUUID().toString();
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
// 获取当前用户名
|
// 获取当前用户名
|
||||||
String userName = SecurityUtils.getUsername();
|
String userName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
// 初始化Redis状态
|
initializeImportStatus("import:staffFmyRelation:", taskId, excelList.size());
|
||||||
String statusKey = "import:staffFmyRelation:" + taskId;
|
|
||||||
Map<String, Object> statusData = new HashMap<>();
|
|
||||||
statusData.put("taskId", taskId);
|
|
||||||
statusData.put("status", "PROCESSING");
|
|
||||||
statusData.put("totalCount", excelList.size());
|
|
||||||
statusData.put("successCount", 0);
|
|
||||||
statusData.put("failureCount", 0);
|
|
||||||
statusData.put("progress", 0);
|
|
||||||
statusData.put("startTime", startTime);
|
|
||||||
statusData.put("message", "正在处理...");
|
|
||||||
|
|
||||||
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
|
||||||
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
|
||||||
|
|
||||||
// 调用异步导入服务
|
// 调用异步导入服务
|
||||||
relationImportService.importRelationAsync(excelList, taskId, userName);
|
relationImportService.importRelationAsync(excelList, taskId, userName);
|
||||||
@@ -233,6 +228,79 @@ public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationSer
|
|||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public StaffFmyRelationImportSubmitResultVO importRelationWithAssets(List<CcdiStaffFmyRelationExcel> relationList,
|
||||||
|
List<CcdiAssetInfoExcel> assetList) {
|
||||||
|
boolean hasRelationRows = relationList != null && !relationList.isEmpty();
|
||||||
|
boolean hasAssetRows = assetList != null && !assetList.isEmpty();
|
||||||
|
if (!hasRelationRows && !hasAssetRows) {
|
||||||
|
throw new RuntimeException("至少需要一条数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
StaffFmyRelationImportSubmitResultVO result = new StaffFmyRelationImportSubmitResultVO();
|
||||||
|
result.setMessage(buildImportSubmitMessage(hasRelationRows, hasAssetRows));
|
||||||
|
|
||||||
|
if (hasRelationRows && !hasAssetRows) {
|
||||||
|
result.setRelationTaskId(importRelation(relationList));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
if (!hasRelationRows) {
|
||||||
|
result.setAssetTaskId(assetInfoImportService.importAssetInfo(assetList));
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
String relationTaskId = UUID.randomUUID().toString();
|
||||||
|
String assetTaskId = UUID.randomUUID().toString();
|
||||||
|
initializeImportStatus("import:staffFmyRelation:", relationTaskId, relationList.size());
|
||||||
|
initializeImportStatus("import:assetInfo:", assetTaskId, assetList.size());
|
||||||
|
|
||||||
|
result.setRelationTaskId(relationTaskId);
|
||||||
|
result.setAssetTaskId(assetTaskId);
|
||||||
|
dualSheetImportOrchestrationService.importRelationWithAssetsAsync(
|
||||||
|
relationList,
|
||||||
|
relationTaskId,
|
||||||
|
assetList,
|
||||||
|
assetTaskId,
|
||||||
|
currentUserName()
|
||||||
|
);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initializeImportStatus(String keyPrefix, String taskId, int totalCount) {
|
||||||
|
Map<String, Object> statusData = new HashMap<>();
|
||||||
|
statusData.put("taskId", taskId);
|
||||||
|
statusData.put("status", "PROCESSING");
|
||||||
|
statusData.put("totalCount", totalCount);
|
||||||
|
statusData.put("successCount", 0);
|
||||||
|
statusData.put("failureCount", 0);
|
||||||
|
statusData.put("progress", 0);
|
||||||
|
statusData.put("startTime", System.currentTimeMillis());
|
||||||
|
statusData.put("message", "正在处理...");
|
||||||
|
|
||||||
|
String statusKey = keyPrefix + taskId;
|
||||||
|
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||||
|
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildImportSubmitMessage(boolean hasRelationRows, boolean hasAssetRows) {
|
||||||
|
if (hasRelationRows && hasAssetRows) {
|
||||||
|
return "已提交员工亲属关系和亲属资产信息导入任务";
|
||||||
|
}
|
||||||
|
if (hasRelationRows) {
|
||||||
|
return "已提交员工亲属关系导入任务";
|
||||||
|
}
|
||||||
|
return "已提交亲属资产信息导入任务";
|
||||||
|
}
|
||||||
|
|
||||||
|
private String currentUserName() {
|
||||||
|
try {
|
||||||
|
return SecurityUtils.getUsername();
|
||||||
|
} catch (Exception e) {
|
||||||
|
return "system";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiAssetInfoVO toAssetInfoVO(CcdiAssetInfo assetInfo) {
|
private CcdiAssetInfoVO toAssetInfoVO(CcdiAssetInfo assetInfo) {
|
||||||
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
|
CcdiAssetInfoVO assetInfoVO = new CcdiAssetInfoVO();
|
||||||
BeanUtils.copyProperties(assetInfo, assetInfoVO);
|
BeanUtils.copyProperties(assetInfo, assetInfoVO);
|
||||||
|
|||||||
@@ -165,12 +165,8 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
return new MainImportResult(Collections.emptyMap(), 0);
|
return new MainImportResult(Collections.emptyMap(), 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
Set<String> existingRecruitIds = getExistingRecruitIds(
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> importedRecruitmentMap = new LinkedHashMap<>();
|
||||||
mainRows.stream().map(MainImportRow::data).toList()
|
int successCount = 0;
|
||||||
);
|
|
||||||
Set<String> processedRecruitIds = new HashSet<>();
|
|
||||||
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
|
|
||||||
Map<String, CcdiStaffRecruitment> importedRecruitmentMap = new LinkedHashMap<>();
|
|
||||||
|
|
||||||
for (int index = 0; index < mainRows.size(); index++) {
|
for (int index = 0; index < mainRows.size(); index++) {
|
||||||
MainImportRow mainRow = mainRows.get(index);
|
MainImportRow mainRow = mainRows.get(index);
|
||||||
@@ -178,36 +174,22 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
try {
|
try {
|
||||||
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
|
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
|
||||||
BeanUtils.copyProperties(excel, addDTO);
|
BeanUtils.copyProperties(excel, addDTO);
|
||||||
addDTO.setRecruitType(RecruitType.inferCode(addDTO.getRecruitName()));
|
addDTO.setRecruitType(normalizeRecruitType(excel.getRecruitType()));
|
||||||
|
|
||||||
validateRecruitmentData(addDTO, mainRow.sheetRowNum());
|
validateRecruitmentData(addDTO, mainRow.sheetRowNum());
|
||||||
|
|
||||||
String recruitId = trim(excel.getRecruitId());
|
String recruitId = trim(excel.getRecruitId());
|
||||||
if (existingRecruitIds.contains(recruitId)) {
|
|
||||||
throw buildValidationException(
|
|
||||||
MAIN_SHEET_NAME,
|
|
||||||
List.of(mainRow.sheetRowNum()),
|
|
||||||
String.format("招聘记录编号[%s]已存在,请勿重复导入", recruitId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
if (!processedRecruitIds.add(recruitId)) {
|
|
||||||
throw buildValidationException(
|
|
||||||
MAIN_SHEET_NAME,
|
|
||||||
List.of(mainRow.sheetRowNum()),
|
|
||||||
String.format("招聘记录编号[%s]在导入文件中重复,已跳过此条记录", recruitId)
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
BeanUtils.copyProperties(excel, recruitment);
|
BeanUtils.copyProperties(excel, recruitment);
|
||||||
recruitment.setRecruitId(recruitId);
|
recruitment.setRecruitId(recruitId);
|
||||||
recruitment.setRecruitType(addDTO.getRecruitType());
|
recruitment.setRecruitType(addDTO.getRecruitType());
|
||||||
recruitment.setCreatedBy(userName);
|
recruitment.setCreatedBy(userName);
|
||||||
recruitment.setUpdatedBy(userName);
|
recruitment.setUpdatedBy(userName);
|
||||||
newRecords.add(recruitment);
|
recruitmentMapper.insert(recruitment);
|
||||||
importedRecruitmentMap.put(recruitId, recruitment);
|
successCount++;
|
||||||
|
addRecruitment(importedRecruitmentMap, recruitment);
|
||||||
|
|
||||||
ImportLogUtils.logProgress(log, taskId, index + 1, mainRows.size(), newRecords.size(), failures.size());
|
ImportLogUtils.logProgress(log, taskId, index + 1, mainRows.size(), successCount, failures.size());
|
||||||
} catch (Exception exception) {
|
} catch (Exception exception) {
|
||||||
FailureMeta failureMeta = resolveFailureMeta(exception, List.of(mainRow.sheetRowNum()), MAIN_SHEET_NAME);
|
FailureMeta failureMeta = resolveFailureMeta(exception, List.of(mainRow.sheetRowNum()), MAIN_SHEET_NAME);
|
||||||
failures.add(buildFailure(excel, failureMeta.sheetName(), failureMeta.sheetRowNum(), exception.getMessage()));
|
failures.add(buildFailure(excel, failureMeta.sheetName(), failureMeta.sheetRowNum(), exception.getMessage()));
|
||||||
@@ -221,16 +203,11 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!newRecords.isEmpty()) {
|
return new MainImportResult(importedRecruitmentMap, successCount);
|
||||||
ImportLogUtils.logBatchOperationStart(log, taskId, "插入招聘信息", (newRecords.size() + 499) / 500, 500);
|
|
||||||
saveBatch(newRecords, 500);
|
|
||||||
}
|
|
||||||
|
|
||||||
return new MainImportResult(importedRecruitmentMap, newRecords.size());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int importWorkSheet(List<WorkImportRow> workRows,
|
private int importWorkSheet(List<WorkImportRow> workRows,
|
||||||
Map<String, CcdiStaffRecruitment> importedRecruitmentMap,
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> importedRecruitmentMap,
|
||||||
List<RecruitmentImportFailureVO> failures,
|
List<RecruitmentImportFailureVO> failures,
|
||||||
String userName,
|
String userName,
|
||||||
String taskId) {
|
String taskId) {
|
||||||
@@ -238,7 +215,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Map<String, CcdiStaffRecruitment> existingRecruitmentMap =
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> existingRecruitmentMap =
|
||||||
getExistingRecruitmentMap(workRows, importedRecruitmentMap);
|
getExistingRecruitmentMap(workRows, importedRecruitmentMap);
|
||||||
Map<String, List<WorkImportRow>> groupedRows = groupWorkRows(workRows);
|
Map<String, List<WorkImportRow>> groupedRows = groupWorkRows(workRows);
|
||||||
int successCount = 0;
|
int successCount = 0;
|
||||||
@@ -248,15 +225,18 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
processedGroups++;
|
processedGroups++;
|
||||||
WorkImportRow firstRow = recruitWorkRows.get(0);
|
WorkImportRow firstRow = recruitWorkRows.get(0);
|
||||||
String recruitId = trim(firstRow.data().getRecruitId());
|
String recruitId = trim(firstRow.data().getRecruitId());
|
||||||
CcdiStaffRecruitment recruitment = importedRecruitmentMap.get(recruitId);
|
|
||||||
if (recruitment == null) {
|
|
||||||
recruitment = existingRecruitmentMap.get(recruitId);
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
RecruitmentMatchKey matchKey = buildMatchKey(firstRow.data());
|
||||||
|
CcdiStaffRecruitment recruitment = resolveMatchedRecruitment(
|
||||||
|
matchKey,
|
||||||
|
importedRecruitmentMap,
|
||||||
|
existingRecruitmentMap,
|
||||||
|
extractWorkRowNums(recruitWorkRows)
|
||||||
|
);
|
||||||
validateWorkGroup(recruitWorkRows, recruitment);
|
validateWorkGroup(recruitWorkRows, recruitment);
|
||||||
|
|
||||||
if (StringUtils.isNotEmpty(recruitId) && hasExistingWorkHistory(recruitId)) {
|
if (recruitment != null && hasExistingWorkHistory(recruitment.getId())) {
|
||||||
throw buildValidationException(
|
throw buildValidationException(
|
||||||
WORK_SHEET_NAME,
|
WORK_SHEET_NAME,
|
||||||
extractWorkRowNums(recruitWorkRows),
|
extractWorkRowNums(recruitWorkRows),
|
||||||
@@ -264,7 +244,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
List<CcdiStaffRecruitmentWork> entities = buildWorkEntities(recruitWorkRows, userName);
|
List<CcdiStaffRecruitmentWork> entities = buildWorkEntities(recruitWorkRows, recruitment, userName);
|
||||||
entities.forEach(entity -> recruitmentWorkMapper.insert(entity));
|
entities.forEach(entity -> recruitmentWorkMapper.insert(entity));
|
||||||
successCount += recruitWorkRows.size();
|
successCount += recruitWorkRows.size();
|
||||||
|
|
||||||
@@ -299,33 +279,59 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
}
|
}
|
||||||
|
|
||||||
private String buildWorkGroupKey(WorkImportRow workRow) {
|
private String buildWorkGroupKey(WorkImportRow workRow) {
|
||||||
String recruitId = trim(workRow.data().getRecruitId());
|
RecruitmentMatchKey key = buildMatchKey(workRow.data());
|
||||||
if (StringUtils.isNotEmpty(recruitId)) {
|
if (key.isComplete()) {
|
||||||
return recruitId;
|
return key.value();
|
||||||
}
|
}
|
||||||
return "__ROW__" + workRow.sheetRowNum();
|
return "__ROW__" + workRow.sheetRowNum();
|
||||||
}
|
}
|
||||||
|
|
||||||
private Map<String, CcdiStaffRecruitment> getExistingRecruitmentMap(List<WorkImportRow> workRows,
|
private Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> getExistingRecruitmentMap(
|
||||||
Map<String, CcdiStaffRecruitment> importedRecruitmentMap) {
|
List<WorkImportRow> workRows,
|
||||||
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> importedRecruitmentMap
|
||||||
|
) {
|
||||||
LinkedHashSet<String> recruitIds = workRows.stream()
|
LinkedHashSet<String> recruitIds = workRows.stream()
|
||||||
|
.filter(row -> !importedRecruitmentMap.containsKey(buildMatchKey(row.data())))
|
||||||
.map(row -> trim(row.data().getRecruitId()))
|
.map(row -> trim(row.data().getRecruitId()))
|
||||||
.filter(StringUtils::isNotEmpty)
|
.filter(StringUtils::isNotEmpty)
|
||||||
.filter(recruitId -> !importedRecruitmentMap.containsKey(recruitId))
|
|
||||||
.collect(Collectors.toCollection(LinkedHashSet::new));
|
.collect(Collectors.toCollection(LinkedHashSet::new));
|
||||||
if (recruitIds.isEmpty()) {
|
if (recruitIds.isEmpty()) {
|
||||||
return Collections.emptyMap();
|
return Collections.emptyMap();
|
||||||
}
|
}
|
||||||
List<CcdiStaffRecruitment> recruitments = recruitmentMapper.selectBatchIds(recruitIds);
|
List<CcdiStaffRecruitment> recruitments = selectRecruitmentsByRecruitIds(recruitIds);
|
||||||
return recruitments.stream().collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, item -> item));
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> result = new LinkedHashMap<>();
|
||||||
|
recruitments.forEach(item -> addRecruitment(result, item));
|
||||||
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CcdiStaffRecruitmentWork> buildWorkEntities(List<WorkImportRow> workRows, String userName) {
|
private CcdiStaffRecruitment resolveMatchedRecruitment(
|
||||||
|
RecruitmentMatchKey matchKey,
|
||||||
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> importedRecruitmentMap,
|
||||||
|
Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> existingRecruitmentMap,
|
||||||
|
List<Integer> rowNums
|
||||||
|
) {
|
||||||
|
List<CcdiStaffRecruitment> matchedRecruitments = new ArrayList<>();
|
||||||
|
matchedRecruitments.addAll(importedRecruitmentMap.getOrDefault(matchKey, Collections.emptyList()));
|
||||||
|
matchedRecruitments.addAll(existingRecruitmentMap.getOrDefault(matchKey, Collections.emptyList()));
|
||||||
|
if (matchedRecruitments.size() > 1) {
|
||||||
|
throw buildValidationException(
|
||||||
|
WORK_SHEET_NAME,
|
||||||
|
rowNums,
|
||||||
|
String.format("招聘记录编号[%s]匹配到多条招聘主信息,无法确定历史工作经历归属", matchKey.recruitId())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return matchedRecruitments.isEmpty() ? null : matchedRecruitments.get(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CcdiStaffRecruitmentWork> buildWorkEntities(List<WorkImportRow> workRows,
|
||||||
|
CcdiStaffRecruitment recruitment,
|
||||||
|
String userName) {
|
||||||
List<CcdiStaffRecruitmentWork> entities = new ArrayList<>();
|
List<CcdiStaffRecruitmentWork> entities = new ArrayList<>();
|
||||||
for (WorkImportRow workRow : workRows) {
|
for (WorkImportRow workRow : workRows) {
|
||||||
CcdiStaffRecruitmentWork entity = new CcdiStaffRecruitmentWork();
|
CcdiStaffRecruitmentWork entity = new CcdiStaffRecruitmentWork();
|
||||||
BeanUtils.copyProperties(workRow.data(), entity);
|
BeanUtils.copyProperties(workRow.data(), entity);
|
||||||
entity.setRecruitId(trim(workRow.data().getRecruitId()));
|
entity.setRecruitmentId(recruitment.getId());
|
||||||
|
entity.setRecruitId(recruitment.getRecruitId());
|
||||||
entity.setCreatedBy(userName);
|
entity.setCreatedBy(userName);
|
||||||
entity.setUpdatedBy(userName);
|
entity.setUpdatedBy(userName);
|
||||||
entities.add(entity);
|
entities.add(entity);
|
||||||
@@ -333,29 +339,9 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
return entities;
|
return entities;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Set<String> getExistingRecruitIds(List<CcdiStaffRecruitmentExcel> recruitmentList) {
|
private boolean hasExistingWorkHistory(Long recruitmentId) {
|
||||||
List<String> recruitIds = recruitmentList.stream()
|
|
||||||
.map(CcdiStaffRecruitmentExcel::getRecruitId)
|
|
||||||
.map(this::trim)
|
|
||||||
.filter(StringUtils::isNotEmpty)
|
|
||||||
.toList();
|
|
||||||
|
|
||||||
if (recruitIds.isEmpty()) {
|
|
||||||
return Collections.emptySet();
|
|
||||||
}
|
|
||||||
|
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitment> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.in(CcdiStaffRecruitment::getRecruitId, recruitIds);
|
|
||||||
List<CcdiStaffRecruitment> existingRecruitments = recruitmentMapper.selectList(wrapper);
|
|
||||||
|
|
||||||
return existingRecruitments.stream()
|
|
||||||
.map(CcdiStaffRecruitment::getRecruitId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
}
|
|
||||||
|
|
||||||
private boolean hasExistingWorkHistory(String recruitId) {
|
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId);
|
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitmentId, recruitmentId);
|
||||||
return recruitmentWorkMapper.selectCount(wrapper) > 0;
|
return recruitmentWorkMapper.selectCount(wrapper) > 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -376,22 +362,22 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位描述不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "职位描述不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandName())) {
|
if (StringUtils.isEmpty(addDTO.getCandName())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员姓名不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "候选人姓名不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandEdu())) {
|
if (StringUtils.isEmpty(addDTO.getCandEdu())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员学历不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "学历不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandId())) {
|
if (StringUtils.isEmpty(addDTO.getCandId())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "证件号码不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandSchool())) {
|
if (StringUtils.isEmpty(addDTO.getCandSchool())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业院校不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业院校不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandMajor())) {
|
if (StringUtils.isEmpty(addDTO.getCandMajor())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员专业不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "专业不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getCandGrad())) {
|
if (StringUtils.isEmpty(addDTO.getCandGrad())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "应聘人员毕业年月不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "毕业年月不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
|
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况不能为空");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "录用情况不能为空");
|
||||||
@@ -414,10 +400,23 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
|
if (RecruitType.getDescByCode(addDTO.getRecruitType()) == null) {
|
||||||
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL'或'CAMPUS'");
|
throw buildValidationException(MAIN_SHEET_NAME, List.of(sheetRowNum), "招聘类型只能填写'SOCIAL/社招'或'CAMPUS/校招'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private String normalizeRecruitType(String recruitType) {
|
||||||
|
String value = trim(recruitType);
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
for (RecruitType type : RecruitType.values()) {
|
||||||
|
if (type.getCode().equals(value) || type.getDesc().equals(value)) {
|
||||||
|
return type.getCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
private void validateWorkGroup(List<WorkImportRow> workRows, CcdiStaffRecruitment recruitment) {
|
private void validateWorkGroup(List<WorkImportRow> workRows, CcdiStaffRecruitment recruitment) {
|
||||||
Set<Integer> processedSortOrders = new HashSet<>();
|
Set<Integer> processedSortOrders = new HashSet<>();
|
||||||
for (WorkImportRow workRow : workRows) {
|
for (WorkImportRow workRow : workRows) {
|
||||||
@@ -451,14 +450,14 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "工作单位不能为空");
|
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "工作单位不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
|
if (StringUtils.isEmpty(trim(excel.getPositionName()))) {
|
||||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位不能为空");
|
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "岗位名称不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
|
if (StringUtils.isEmpty(trim(excel.getJobStartMonth()))) {
|
||||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职年月不能为空");
|
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "入职时间不能为空");
|
||||||
}
|
}
|
||||||
validateMonth(excel.getJobStartMonth(), "入职年月", sheetRowNum);
|
validateMonth(excel.getJobStartMonth(), "入职时间", sheetRowNum);
|
||||||
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
|
if (StringUtils.isNotEmpty(trim(excel.getJobEndMonth()))) {
|
||||||
validateMonth(excel.getJobEndMonth(), "离职年月", sheetRowNum);
|
validateMonth(excel.getJobEndMonth(), "离职时间", sheetRowNum);
|
||||||
}
|
}
|
||||||
if (recruitment == null) {
|
if (recruitment == null) {
|
||||||
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不存在,请先维护招聘主信息");
|
throw buildValidationException(WORK_SHEET_NAME, List.of(sheetRowNum), "招聘记录编号不存在,请先维护招聘主信息");
|
||||||
@@ -555,30 +554,36 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
redisTemplate.opsForHash().putAll(key, statusData);
|
redisTemplate.opsForHash().putAll(key, statusData);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void saveBatch(List<CcdiStaffRecruitment> list, int batchSize) {
|
private List<CcdiStaffRecruitment> selectRecruitmentsByRecruitIds(Set<String> recruitIds) {
|
||||||
for (int i = 0; i < list.size(); i += batchSize) {
|
if (recruitIds == null || recruitIds.isEmpty()) {
|
||||||
int end = Math.min(i + batchSize, list.size());
|
return Collections.emptyList();
|
||||||
List<CcdiStaffRecruitment> subList = list.subList(i, end);
|
|
||||||
|
|
||||||
List<String> recruitIds = subList.stream()
|
|
||||||
.map(CcdiStaffRecruitment::getRecruitId)
|
|
||||||
.toList();
|
|
||||||
if (recruitIds.isEmpty()) {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
|
|
||||||
List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds);
|
|
||||||
Set<String> existingIds = existingRecords.stream()
|
|
||||||
.map(CcdiStaffRecruitment::getRecruitId)
|
|
||||||
.collect(Collectors.toSet());
|
|
||||||
|
|
||||||
List<CcdiStaffRecruitment> toInsert = subList.stream()
|
|
||||||
.filter(record -> !existingIds.contains(record.getRecruitId()))
|
|
||||||
.toList();
|
|
||||||
if (!toInsert.isEmpty()) {
|
|
||||||
recruitmentMapper.insertBatch(toInsert);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
LambdaQueryWrapper<CcdiStaffRecruitment> wrapper = new LambdaQueryWrapper<>();
|
||||||
|
wrapper.in(CcdiStaffRecruitment::getRecruitId, recruitIds);
|
||||||
|
return recruitmentMapper.selectList(wrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void addRecruitment(Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> map,
|
||||||
|
CcdiStaffRecruitment recruitment) {
|
||||||
|
map.computeIfAbsent(buildMatchKey(recruitment), key -> new ArrayList<>()).add(recruitment);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecruitmentMatchKey buildMatchKey(CcdiStaffRecruitment recruitment) {
|
||||||
|
return new RecruitmentMatchKey(
|
||||||
|
trim(recruitment.getRecruitId()),
|
||||||
|
trim(recruitment.getCandName()),
|
||||||
|
trim(recruitment.getRecruitName()),
|
||||||
|
trim(recruitment.getPosName())
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
private RecruitmentMatchKey buildMatchKey(CcdiStaffRecruitmentWorkExcel excel) {
|
||||||
|
return new RecruitmentMatchKey(
|
||||||
|
trim(excel.getRecruitId()),
|
||||||
|
trim(excel.getCandName()),
|
||||||
|
trim(excel.getRecruitName()),
|
||||||
|
trim(excel.getPosName())
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<MainImportRow> buildMainImportRows(List<CcdiStaffRecruitmentExcel> recruitmentList) {
|
private List<MainImportRow> buildMainImportRows(List<CcdiStaffRecruitmentExcel> recruitmentList) {
|
||||||
@@ -628,10 +633,25 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
|
|
||||||
private record WorkImportRow(CcdiStaffRecruitmentWorkExcel data, int sheetRowNum) {}
|
private record WorkImportRow(CcdiStaffRecruitmentWorkExcel data, int sheetRowNum) {}
|
||||||
|
|
||||||
private record MainImportResult(Map<String, CcdiStaffRecruitment> importedRecruitmentMap, int successCount) {}
|
private record MainImportResult(Map<RecruitmentMatchKey, List<CcdiStaffRecruitment>> importedRecruitmentMap,
|
||||||
|
int successCount) {}
|
||||||
|
|
||||||
private record FailureMeta(String sheetName, String sheetRowNum) {}
|
private record FailureMeta(String sheetName, String sheetRowNum) {}
|
||||||
|
|
||||||
|
private record RecruitmentMatchKey(String recruitId, String candName, String recruitName, String posName) {
|
||||||
|
|
||||||
|
private boolean isComplete() {
|
||||||
|
return StringUtils.isNotEmpty(recruitId)
|
||||||
|
&& StringUtils.isNotEmpty(candName)
|
||||||
|
&& StringUtils.isNotEmpty(recruitName)
|
||||||
|
&& StringUtils.isNotEmpty(posName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String value() {
|
||||||
|
return String.join("|", recruitId, candName, recruitName, posName);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private static class ImportValidationException extends RuntimeException {
|
private static class ImportValidationException extends RuntimeException {
|
||||||
|
|
||||||
private final String sheetName;
|
private final String sheetName;
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
package com.ruoyi.info.collection.service.impl;
|
package com.ruoyi.info.collection.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
|
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
|
||||||
@@ -27,6 +28,7 @@ import org.springframework.stereotype.Service;
|
|||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -108,15 +110,15 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
/**
|
/**
|
||||||
* 查询招聘信息详情
|
* 查询招聘信息详情
|
||||||
*
|
*
|
||||||
* @param recruitId 招聘记录编号
|
* @param id 主键ID
|
||||||
* @return 招聘信息VO
|
* @return 招聘信息VO
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public CcdiStaffRecruitmentVO selectRecruitmentById(String recruitId) {
|
public CcdiStaffRecruitmentVO selectRecruitmentById(Long id) {
|
||||||
CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(recruitId);
|
CcdiStaffRecruitmentVO vo = recruitmentMapper.selectRecruitmentById(id);
|
||||||
if (vo != null) {
|
if (vo != null) {
|
||||||
vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus()));
|
vo.setAdmitStatusDesc(AdmitStatus.getDescByCode(vo.getAdmitStatus()));
|
||||||
vo.setWorkExperienceList(selectWorkExperienceList(recruitId));
|
vo.setWorkExperienceList(selectWorkExperienceList(vo.getId()));
|
||||||
}
|
}
|
||||||
return vo;
|
return vo;
|
||||||
}
|
}
|
||||||
@@ -130,15 +132,14 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) {
|
public int insertRecruitment(CcdiStaffRecruitmentAddDTO addDTO) {
|
||||||
// 检查招聘记录编号唯一性
|
String recruitId = trim(addDTO.getRecruitId());
|
||||||
if (recruitmentMapper.selectById(addDTO.getRecruitId()) != null) {
|
|
||||||
throw new RuntimeException("该招聘记录编号已存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
BeanUtils.copyProperties(addDTO, recruitment);
|
BeanUtils.copyProperties(addDTO, recruitment);
|
||||||
int result = recruitmentMapper.insert(recruitment);
|
recruitment.setRecruitId(recruitId);
|
||||||
|
|
||||||
|
int result = recruitmentMapper.insert(recruitment);
|
||||||
|
insertWorkExperienceList(recruitment.getId(), recruitId, addDTO.getRecruitType(), addDTO.getWorkExperienceList());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -151,9 +152,20 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int updateRecruitment(CcdiStaffRecruitmentEditDTO editDTO) {
|
public int updateRecruitment(CcdiStaffRecruitmentEditDTO editDTO) {
|
||||||
|
CcdiStaffRecruitment existing = recruitmentMapper.selectById(editDTO.getId());
|
||||||
|
if (existing == null) {
|
||||||
|
throw new RuntimeException("招聘信息不存在");
|
||||||
|
}
|
||||||
|
|
||||||
|
String recruitId = trim(editDTO.getRecruitId());
|
||||||
|
editDTO.setRecruitId(recruitId);
|
||||||
|
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
BeanUtils.copyProperties(editDTO, recruitment);
|
BeanUtils.copyProperties(editDTO, recruitment);
|
||||||
int result = recruitmentMapper.updateById(recruitment);
|
int result = recruitmentMapper.updateById(recruitment);
|
||||||
|
if (!Objects.equals(existing.getRecruitId(), recruitId)) {
|
||||||
|
updateWorkRecruitId(editDTO.getId(), recruitId);
|
||||||
|
}
|
||||||
replaceWorkExperienceList(editDTO);
|
replaceWorkExperienceList(editDTO);
|
||||||
|
|
||||||
return result;
|
return result;
|
||||||
@@ -162,16 +174,19 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
/**
|
/**
|
||||||
* 批量删除招聘信息
|
* 批量删除招聘信息
|
||||||
*
|
*
|
||||||
* @param recruitIds 需要删除的招聘记录编号
|
* @param ids 需要删除的招聘信息ID
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public int deleteRecruitmentByIds(String[] recruitIds) {
|
public int deleteRecruitmentByIds(Long[] ids) {
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> workWrapper = new LambdaQueryWrapper<>();
|
List<Long> idList = Arrays.asList(ids);
|
||||||
workWrapper.in(CcdiStaffRecruitmentWork::getRecruitId, List.of(recruitIds));
|
if (!idList.isEmpty()) {
|
||||||
recruitmentWorkMapper.delete(workWrapper);
|
LambdaQueryWrapper<CcdiStaffRecruitmentWork> workWrapper = new LambdaQueryWrapper<>();
|
||||||
return recruitmentMapper.deleteBatchIds(List.of(recruitIds));
|
workWrapper.in(CcdiStaffRecruitmentWork::getRecruitmentId, idList);
|
||||||
|
recruitmentWorkMapper.delete(workWrapper);
|
||||||
|
}
|
||||||
|
return recruitmentMapper.deleteBatchIds(idList);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -216,9 +231,9 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CcdiStaffRecruitmentWorkVO> selectWorkExperienceList(String recruitId) {
|
private List<CcdiStaffRecruitmentWorkVO> selectWorkExperienceList(Long recruitmentId) {
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CcdiStaffRecruitmentWork> wrapper = new LambdaQueryWrapper<>();
|
||||||
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, recruitId)
|
wrapper.eq(CcdiStaffRecruitmentWork::getRecruitmentId, recruitmentId)
|
||||||
.orderByAsc(CcdiStaffRecruitmentWork::getSortOrder)
|
.orderByAsc(CcdiStaffRecruitmentWork::getSortOrder)
|
||||||
.orderByDesc(CcdiStaffRecruitmentWork::getId);
|
.orderByDesc(CcdiStaffRecruitmentWork::getId);
|
||||||
List<CcdiStaffRecruitmentWork> workList = recruitmentWorkMapper.selectList(wrapper);
|
List<CcdiStaffRecruitmentWork> workList = recruitmentWorkMapper.selectList(wrapper);
|
||||||
@@ -232,9 +247,20 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
}).toList();
|
}).toList();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void updateWorkRecruitId(Long recruitmentId, String newRecruitId) {
|
||||||
|
LambdaUpdateWrapper<CcdiStaffRecruitmentWork> updateWrapper = new LambdaUpdateWrapper<>();
|
||||||
|
updateWrapper.eq(CcdiStaffRecruitmentWork::getRecruitmentId, recruitmentId)
|
||||||
|
.set(CcdiStaffRecruitmentWork::getRecruitId, newRecruitId);
|
||||||
|
recruitmentWorkMapper.update(null, updateWrapper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trim(String value) {
|
||||||
|
return value == null ? null : value.trim();
|
||||||
|
}
|
||||||
|
|
||||||
private void replaceWorkExperienceList(CcdiStaffRecruitmentEditDTO editDTO) {
|
private void replaceWorkExperienceList(CcdiStaffRecruitmentEditDTO editDTO) {
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitmentWork> deleteWrapper = new LambdaQueryWrapper<>();
|
LambdaQueryWrapper<CcdiStaffRecruitmentWork> deleteWrapper = new LambdaQueryWrapper<>();
|
||||||
deleteWrapper.eq(CcdiStaffRecruitmentWork::getRecruitId, editDTO.getRecruitId());
|
deleteWrapper.eq(CcdiStaffRecruitmentWork::getRecruitmentId, editDTO.getId());
|
||||||
|
|
||||||
if (!Objects.equals(RecruitType.SOCIAL.getCode(), editDTO.getRecruitType())) {
|
if (!Objects.equals(RecruitType.SOCIAL.getCode(), editDTO.getRecruitType())) {
|
||||||
recruitmentWorkMapper.delete(deleteWrapper);
|
recruitmentWorkMapper.delete(deleteWrapper);
|
||||||
@@ -246,12 +272,28 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
recruitmentWorkMapper.delete(deleteWrapper);
|
recruitmentWorkMapper.delete(deleteWrapper);
|
||||||
List<CcdiStaffRecruitmentWork> workList = buildWorkExperienceEntities(editDTO);
|
List<CcdiStaffRecruitmentWork> workList = buildWorkExperienceEntities(
|
||||||
|
editDTO.getId(),
|
||||||
|
editDTO.getRecruitId(),
|
||||||
|
editDTO.getWorkExperienceList()
|
||||||
|
);
|
||||||
workList.forEach(recruitmentWorkMapper::insert);
|
workList.forEach(recruitmentWorkMapper::insert);
|
||||||
}
|
}
|
||||||
|
|
||||||
private List<CcdiStaffRecruitmentWork> buildWorkExperienceEntities(CcdiStaffRecruitmentEditDTO editDTO) {
|
private void insertWorkExperienceList(Long recruitmentId,
|
||||||
List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList = editDTO.getWorkExperienceList();
|
String recruitId,
|
||||||
|
String recruitType,
|
||||||
|
List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList) {
|
||||||
|
if (!Objects.equals(RecruitType.SOCIAL.getCode(), recruitType)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
List<CcdiStaffRecruitmentWork> workList = buildWorkExperienceEntities(recruitmentId, recruitId, workExperienceList);
|
||||||
|
workList.forEach(recruitmentWorkMapper::insert);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<CcdiStaffRecruitmentWork> buildWorkExperienceEntities(Long recruitmentId,
|
||||||
|
String recruitId,
|
||||||
|
List<CcdiStaffRecruitmentWorkEditDTO> workExperienceList) {
|
||||||
if (workExperienceList == null || workExperienceList.isEmpty()) {
|
if (workExperienceList == null || workExperienceList.isEmpty()) {
|
||||||
return new ArrayList<>();
|
return new ArrayList<>();
|
||||||
}
|
}
|
||||||
@@ -264,7 +306,8 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
}
|
}
|
||||||
CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork();
|
CcdiStaffRecruitmentWork work = new CcdiStaffRecruitmentWork();
|
||||||
BeanUtils.copyProperties(item, work);
|
BeanUtils.copyProperties(item, work);
|
||||||
work.setRecruitId(editDTO.getRecruitId());
|
work.setRecruitmentId(recruitmentId);
|
||||||
|
work.setRecruitId(recruitId);
|
||||||
work.setSortOrder(i + 1);
|
work.setSortOrder(i + 1);
|
||||||
entityList.add(work);
|
entityList.add(work);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
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) {
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -19,6 +19,8 @@ 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", "银行", "汽车贷款", "银行", "未结清银行汽车贷款"),
|
||||||
@@ -26,7 +28,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_cart", "银行", "信用卡", "银行", "未结清信用卡")
|
new DebtMapping("uncle_credit_card", "银行", "信用卡", "银行", "未结清信用卡")
|
||||||
);
|
);
|
||||||
|
|
||||||
public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) {
|
public List<CcdiDebtsInfo> buildDebts(String personId, String personName, LocalDate queryDate, CreditParsePayload payload) {
|
||||||
@@ -61,9 +63,13 @@ 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(source.get(mapping.prefix() + "_state"));
|
String debtStatus = toStringValue(stateValue);
|
||||||
if (isEmptyMetrics(principalBalance, debtTotalAmount, debtStatus)) {
|
if (isEmptyMetrics(principalBalance, debtTotalAmount, debtStatus)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
@@ -97,6 +103,9 @@ 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;
|
||||||
}
|
}
|
||||||
@@ -111,10 +120,28 @@ 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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,130 @@
|
|||||||
|
package com.ruoyi.info.collection.service.support;
|
||||||
|
|
||||||
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
||||||
|
import com.ruoyi.info.collection.enums.EnterpriseSource;
|
||||||
|
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.dao.DuplicateKeyException;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 关联业务实体库自动补全服务。
|
||||||
|
*/
|
||||||
|
@Service
|
||||||
|
public class EnterpriseAutoFillService {
|
||||||
|
|
||||||
|
private static final int BATCH_SIZE = 500;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
||||||
|
|
||||||
|
public record EnterpriseFillItem(
|
||||||
|
String socialCreditCode,
|
||||||
|
String enterpriseName,
|
||||||
|
String entSource,
|
||||||
|
String dataSource,
|
||||||
|
String userName
|
||||||
|
) {
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void ensureExists(EnterpriseFillItem item) {
|
||||||
|
ensureExistsBatch(List.of(item));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Transactional
|
||||||
|
public void ensureExistsBatch(List<EnterpriseFillItem> items) {
|
||||||
|
if (StringUtils.isEmpty(items)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Map<String, EnterpriseFillItem> normalizedItems = normalizeItems(items);
|
||||||
|
if (normalizedItems.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Set<String> existingCodes = enterpriseBaseInfoMapper.selectBatchIds(new ArrayList<>(normalizedItems.keySet()))
|
||||||
|
.stream()
|
||||||
|
.map(CcdiEnterpriseBaseInfo::getSocialCreditCode)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
List<CcdiEnterpriseBaseInfo> missingEntities = normalizedItems.entrySet().stream()
|
||||||
|
.filter(entry -> !existingCodes.contains(entry.getKey()))
|
||||||
|
.map(entry -> buildEntity(entry.getKey(), entry.getValue()))
|
||||||
|
.toList();
|
||||||
|
if (missingEntities.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
insertBatchIgnoreDuplicate(missingEntities);
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, EnterpriseFillItem> normalizeItems(List<EnterpriseFillItem> items) {
|
||||||
|
Map<String, EnterpriseFillItem> normalizedItems = new LinkedHashMap<>();
|
||||||
|
for (EnterpriseFillItem item : items) {
|
||||||
|
if (item == null || StringUtils.isEmpty(item.socialCreditCode())) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
String socialCreditCode = item.socialCreditCode().trim();
|
||||||
|
normalizedItems.putIfAbsent(socialCreditCode, new EnterpriseFillItem(
|
||||||
|
socialCreditCode,
|
||||||
|
trimToNull(item.enterpriseName()),
|
||||||
|
trimToNull(item.entSource()),
|
||||||
|
trimToNull(item.dataSource()),
|
||||||
|
trimToNull(item.userName())
|
||||||
|
));
|
||||||
|
}
|
||||||
|
return normalizedItems;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiEnterpriseBaseInfo buildEntity(String socialCreditCode, EnterpriseFillItem item) {
|
||||||
|
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||||
|
entity.setSocialCreditCode(socialCreditCode);
|
||||||
|
entity.setEnterpriseName(item.enterpriseName());
|
||||||
|
entity.setEntSource(item.entSource());
|
||||||
|
entity.setDataSource(item.dataSource());
|
||||||
|
entity.setRiskLevel(EnterpriseSource.INTERMEDIARY.getCode().equals(item.entSource()) ? "1" : null);
|
||||||
|
entity.setCreatedBy(item.userName());
|
||||||
|
entity.setUpdatedBy(item.userName());
|
||||||
|
return entity;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertBatchIgnoreDuplicate(List<CcdiEnterpriseBaseInfo> entities) {
|
||||||
|
try {
|
||||||
|
for (int i = 0; i < entities.size(); i += BATCH_SIZE) {
|
||||||
|
int end = Math.min(i + BATCH_SIZE, entities.size());
|
||||||
|
enterpriseBaseInfoMapper.insertBatch(entities.subList(i, end));
|
||||||
|
}
|
||||||
|
} catch (DuplicateKeyException ex) {
|
||||||
|
insertOneByOneIgnoreDuplicate(entities);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void insertOneByOneIgnoreDuplicate(List<CcdiEnterpriseBaseInfo> entities) {
|
||||||
|
for (CcdiEnterpriseBaseInfo entity : entities) {
|
||||||
|
if (enterpriseBaseInfoMapper.selectById(entity.getSocialCreditCode()) != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
try {
|
||||||
|
enterpriseBaseInfoMapper.insert(entity);
|
||||||
|
} catch (DuplicateKeyException duplicate) {
|
||||||
|
if (enterpriseBaseInfoMapper.selectById(entity.getSocialCreditCode()) == null) {
|
||||||
|
throw duplicate;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String trimToNull(String value) {
|
||||||
|
if (StringUtils.isEmpty(value)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return value.trim();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -2,19 +2,36 @@ package com.ruoyi.info.collection.utils;
|
|||||||
|
|
||||||
import com.alibaba.excel.EasyExcel;
|
import com.alibaba.excel.EasyExcel;
|
||||||
import com.alibaba.excel.ExcelWriter;
|
import com.alibaba.excel.ExcelWriter;
|
||||||
|
import com.alibaba.excel.annotation.ExcelProperty;
|
||||||
|
import com.alibaba.excel.write.builder.ExcelWriterBuilder;
|
||||||
import com.alibaba.excel.write.metadata.WriteSheet;
|
import com.alibaba.excel.write.metadata.WriteSheet;
|
||||||
import com.alibaba.excel.write.handler.WriteHandler;
|
import com.alibaba.excel.write.handler.WriteHandler;
|
||||||
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy;
|
||||||
|
import com.ruoyi.common.annotation.DictDropdown;
|
||||||
|
import com.ruoyi.common.exception.ServiceException;
|
||||||
import com.ruoyi.info.collection.handler.DictDropdownWriteHandler;
|
import com.ruoyi.info.collection.handler.DictDropdownWriteHandler;
|
||||||
import com.ruoyi.info.collection.handler.RequiredFieldWriteHandler;
|
import com.ruoyi.info.collection.handler.RequiredFieldWriteHandler;
|
||||||
import com.ruoyi.info.collection.handler.TextFormatWriteHandler;
|
import com.ruoyi.info.collection.handler.TextFormatWriteHandler;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
import lombok.AccessLevel;
|
import lombok.AccessLevel;
|
||||||
import lombok.NoArgsConstructor;
|
import lombok.NoArgsConstructor;
|
||||||
|
import org.apache.poi.ss.usermodel.Cell;
|
||||||
|
import org.apache.poi.ss.usermodel.DataValidation;
|
||||||
|
import org.apache.poi.ss.usermodel.DataValidationConstraint;
|
||||||
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
|
import org.apache.poi.ss.usermodel.Sheet;
|
||||||
|
import org.apache.poi.ss.usermodel.Workbook;
|
||||||
|
import org.apache.poi.ss.usermodel.WorkbookFactory;
|
||||||
|
import org.apache.poi.ss.util.CellRangeAddress;
|
||||||
|
|
||||||
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.lang.reflect.Field;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Comparator;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -77,8 +94,10 @@ public class EasyExcelUtil {
|
|||||||
* @return 数据列表
|
* @return 数据列表
|
||||||
*/
|
*/
|
||||||
public static <T> List<T> importExcel(String fileName, Class<T> clazz) {
|
public static <T> List<T> importExcel(String fileName, Class<T> clazz) {
|
||||||
try {
|
try (InputStream inputStream = java.nio.file.Files.newInputStream(java.nio.file.Path.of(fileName))) {
|
||||||
return EasyExcel.read(fileName).head(clazz).sheet().doReadSync();
|
return importExcel(inputStream, clazz);
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("导入Excel失败", e);
|
throw new RuntimeException("导入Excel失败", e);
|
||||||
}
|
}
|
||||||
@@ -94,7 +113,11 @@ public class EasyExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz) {
|
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz) {
|
||||||
try {
|
try {
|
||||||
return EasyExcel.read(inputStream).head(clazz).sheet().doReadSync();
|
byte[] bytes = inputStream.readAllBytes();
|
||||||
|
validateDictDropdownTemplate(bytes, clazz, null);
|
||||||
|
return EasyExcel.read(new ByteArrayInputStream(bytes)).head(clazz).sheet().doReadSync();
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("导入Excel失败", e);
|
throw new RuntimeException("导入Excel失败", e);
|
||||||
}
|
}
|
||||||
@@ -111,7 +134,11 @@ public class EasyExcelUtil {
|
|||||||
*/
|
*/
|
||||||
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz, String sheetName) {
|
public static <T> List<T> importExcel(java.io.InputStream inputStream, Class<T> clazz, String sheetName) {
|
||||||
try {
|
try {
|
||||||
return EasyExcel.read(inputStream).head(clazz).sheet(sheetName).doReadSync();
|
byte[] bytes = inputStream.readAllBytes();
|
||||||
|
validateDictDropdownTemplate(bytes, clazz, sheetName);
|
||||||
|
return EasyExcel.read(new ByteArrayInputStream(bytes)).head(clazz).sheet(sheetName).doReadSync();
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
throw e;
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
throw new RuntimeException("导入Excel失败", e);
|
throw new RuntimeException("导入Excel失败", e);
|
||||||
}
|
}
|
||||||
@@ -128,9 +155,10 @@ public class EasyExcelUtil {
|
|||||||
public static <T> void importTemplateExcel(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
public static <T> void importTemplateExcel(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
||||||
try {
|
try {
|
||||||
setResponseHeader(response, sheetName + "模板");
|
setResponseHeader(response, sheetName + "模板");
|
||||||
EasyExcel.write(response.getOutputStream(), clazz)
|
templateWriter(response, clazz)
|
||||||
.sheet(sheetName)
|
.sheet(sheetName)
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
|
.registerWriteHandler(new TextFormatWriteHandler(clazz))
|
||||||
.doWrite(List.of());
|
.doWrite(List.of());
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new RuntimeException("下载导入模板失败", e);
|
throw new RuntimeException("下载导入模板失败", e);
|
||||||
@@ -151,9 +179,10 @@ public class EasyExcelUtil {
|
|||||||
WriteHandler... handlers) {
|
WriteHandler... handlers) {
|
||||||
try {
|
try {
|
||||||
setResponseHeader(response, sheetName + "模板");
|
setResponseHeader(response, sheetName + "模板");
|
||||||
var writerBuilder = EasyExcel.write(response.getOutputStream(), clazz)
|
var writerBuilder = templateWriter(response, clazz)
|
||||||
.sheet(sheetName)
|
.sheet(sheetName)
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy());
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
|
.registerWriteHandler(new TextFormatWriteHandler(clazz));
|
||||||
// 注册所有自定义处理器
|
// 注册所有自定义处理器
|
||||||
for (WriteHandler handler : handlers) {
|
for (WriteHandler handler : handlers) {
|
||||||
writerBuilder.registerWriteHandler(handler);
|
writerBuilder.registerWriteHandler(handler);
|
||||||
@@ -190,7 +219,7 @@ public class EasyExcelUtil {
|
|||||||
public static <T> void importTemplateWithDictDropdown(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
public static <T> void importTemplateWithDictDropdown(HttpServletResponse response, Class<T> clazz, String sheetName) {
|
||||||
try {
|
try {
|
||||||
setResponseHeader(response, sheetName + "模板");
|
setResponseHeader(response, sheetName + "模板");
|
||||||
EasyExcel.write(response.getOutputStream(), clazz)
|
templateWriter(response, clazz)
|
||||||
.sheet(sheetName)
|
.sheet(sheetName)
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
||||||
@@ -217,7 +246,7 @@ public class EasyExcelUtil {
|
|||||||
String sheetName, String fileName) {
|
String sheetName, String fileName) {
|
||||||
try {
|
try {
|
||||||
setResponseHeader(response, fileName);
|
setResponseHeader(response, fileName);
|
||||||
EasyExcel.write(response.getOutputStream(), clazz)
|
templateWriter(response, clazz)
|
||||||
.sheet(sheetName)
|
.sheet(sheetName)
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
||||||
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
||||||
@@ -250,7 +279,7 @@ public class EasyExcelUtil {
|
|||||||
String fileName
|
String fileName
|
||||||
) {
|
) {
|
||||||
setResponseHeader(response, fileName);
|
setResponseHeader(response, fileName);
|
||||||
try (ExcelWriter writer = EasyExcel.write(response.getOutputStream()).build()) {
|
try (ExcelWriter writer = templateWriter(response).build()) {
|
||||||
writer.write(List.of(), buildTemplateSheet(0, firstClazz, firstSheetName));
|
writer.write(List.of(), buildTemplateSheet(0, firstClazz, firstSheetName));
|
||||||
writer.write(List.of(), buildTemplateSheet(1, secondClazz, secondSheetName));
|
writer.write(List.of(), buildTemplateSheet(1, secondClazz, secondSheetName));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
@@ -261,7 +290,6 @@ public class EasyExcelUtil {
|
|||||||
private static <T> WriteSheet buildTemplateSheet(int sheetNo, Class<T> clazz, String sheetName) {
|
private static <T> WriteSheet buildTemplateSheet(int sheetNo, Class<T> clazz, String sheetName) {
|
||||||
return EasyExcel.writerSheet(sheetNo, sheetName)
|
return EasyExcel.writerSheet(sheetNo, sheetName)
|
||||||
.head(clazz)
|
.head(clazz)
|
||||||
.registerWriteHandler(new LongestMatchColumnWidthStyleStrategy())
|
|
||||||
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
.registerWriteHandler(new DictDropdownWriteHandler(clazz))
|
||||||
.registerWriteHandler(new TextFormatWriteHandler(clazz))
|
.registerWriteHandler(new TextFormatWriteHandler(clazz))
|
||||||
.registerWriteHandler(new RequiredFieldWriteHandler(clazz))
|
.registerWriteHandler(new RequiredFieldWriteHandler(clazz))
|
||||||
@@ -322,4 +350,137 @@ public class EasyExcelUtil {
|
|||||||
throw new RuntimeException("导出带字典下拉框的Excel失败", e);
|
throw new RuntimeException("导出带字典下拉框的Excel失败", e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static void validateDictDropdownTemplate(byte[] bytes, Class<?> clazz, String sheetName) {
|
||||||
|
List<DropdownColumn> dropdownColumns = resolveDropdownColumns(clazz);
|
||||||
|
if (dropdownColumns.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(bytes))) {
|
||||||
|
Sheet sheet = sheetName == null ? workbook.getSheetAt(0) : workbook.getSheet(sheetName);
|
||||||
|
if (sheet == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int lastDataRowIndex = findLastDataRowIndex(sheet);
|
||||||
|
if (lastDataRowIndex < 1) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
List<String> missingColumnTitles = new ArrayList<>();
|
||||||
|
for (DropdownColumn column : dropdownColumns) {
|
||||||
|
if (!isListValidationCovered(sheet, column.index(), lastDataRowIndex)) {
|
||||||
|
missingColumnTitles.add(column.title());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!missingColumnTitles.isEmpty()) {
|
||||||
|
throw new ServiceException(sheet.getSheetName() + " Sheet 的 "
|
||||||
|
+ String.join("、", missingColumnTitles)
|
||||||
|
+ " 列缺少下拉框,请下载最新导入模板填写后重新导入");
|
||||||
|
}
|
||||||
|
} catch (ServiceException e) {
|
||||||
|
throw e;
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("导入Excel失败", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static List<DropdownColumn> resolveDropdownColumns(Class<?> clazz) {
|
||||||
|
List<DropdownColumn> columns = new ArrayList<>();
|
||||||
|
Class<?> current = clazz;
|
||||||
|
while (current != null && current != Object.class) {
|
||||||
|
for (Field field : current.getDeclaredFields()) {
|
||||||
|
if (field.getAnnotation(DictDropdown.class) == null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ExcelProperty excelProperty = field.getAnnotation(ExcelProperty.class);
|
||||||
|
if (excelProperty == null || excelProperty.index() < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
columns.add(new DropdownColumn(excelProperty.index(), resolveColumnTitle(field, excelProperty)));
|
||||||
|
}
|
||||||
|
current = current.getSuperclass();
|
||||||
|
}
|
||||||
|
columns.sort(Comparator.comparingInt(DropdownColumn::index));
|
||||||
|
return columns;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String resolveColumnTitle(Field field, ExcelProperty excelProperty) {
|
||||||
|
if (excelProperty.value().length > 0 && excelProperty.value()[0] != null
|
||||||
|
&& !excelProperty.value()[0].isBlank()) {
|
||||||
|
return excelProperty.value()[0].replace("*", "");
|
||||||
|
}
|
||||||
|
return field.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int findLastDataRowIndex(Sheet sheet) {
|
||||||
|
int lastDataRowIndex = -1;
|
||||||
|
for (int rowIndex = 1; rowIndex <= sheet.getLastRowNum(); rowIndex++) {
|
||||||
|
Row row = sheet.getRow(rowIndex);
|
||||||
|
if (hasData(row)) {
|
||||||
|
lastDataRowIndex = rowIndex;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return lastDataRowIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean hasData(Row row) {
|
||||||
|
if (row == null || row.getLastCellNum() < 0) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int cellIndex = row.getFirstCellNum(); cellIndex < row.getLastCellNum(); cellIndex++) {
|
||||||
|
if (cellIndex < 0) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Cell cell = row.getCell(cellIndex);
|
||||||
|
if (cell != null && cell.toString() != null && !cell.toString().isBlank()) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isListValidationCovered(Sheet sheet, int columnIndex, int lastDataRowIndex) {
|
||||||
|
boolean[] coveredRows = new boolean[lastDataRowIndex + 1];
|
||||||
|
for (DataValidation validation : sheet.getDataValidations()) {
|
||||||
|
DataValidationConstraint constraint = validation.getValidationConstraint();
|
||||||
|
if (constraint == null || constraint.getValidationType() != DataValidationConstraint.ValidationType.LIST) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
for (CellRangeAddress address : validation.getRegions().getCellRangeAddresses()) {
|
||||||
|
if (address.getFirstColumn() > columnIndex || address.getLastColumn() < columnIndex) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
int firstRow = Math.max(1, address.getFirstRow());
|
||||||
|
int lastRow = Math.min(lastDataRowIndex, address.getLastRow());
|
||||||
|
for (int rowIndex = firstRow; rowIndex <= lastRow; rowIndex++) {
|
||||||
|
coveredRows[rowIndex] = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (int rowIndex = 1; rowIndex <= lastDataRowIndex; rowIndex++) {
|
||||||
|
if (hasData(sheet.getRow(rowIndex)) && !coveredRows[rowIndex]) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private record DropdownColumn(int index, String title) {}
|
||||||
|
|
||||||
|
private static <T> ExcelWriterBuilder templateWriter(HttpServletResponse response, Class<T> clazz)
|
||||||
|
throws IOException {
|
||||||
|
// 模板为空且体量小,使用内存工作簿避免 SXSSF 在无字体环境初始化 Fontconfig。
|
||||||
|
return EasyExcel.write(response.getOutputStream(), clazz).inMemory(Boolean.TRUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static ExcelWriterBuilder templateWriter(HttpServletResponse response) throws IOException {
|
||||||
|
return EasyExcel.write(response.getOutputStream()).inMemory(Boolean.TRUE);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -84,6 +84,7 @@
|
|||||||
|
|
||||||
<sql id="AccountInfoWhereClause">
|
<sql id="AccountInfoWhereClause">
|
||||||
WHERE 1 = 1
|
WHERE 1 = 1
|
||||||
|
AND ai.owner_type <> '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}, '%'))
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
AND e.status = #{query.status}
|
AND e.status = #{query.status}
|
||||||
</if>
|
</if>
|
||||||
</where>
|
</where>
|
||||||
ORDER BY e.create_time DESC
|
ORDER BY e.create_time DESC, e.staff_id DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 批量插入或更新员工信息(只更新非null字段) -->
|
<!-- 批量插入或更新员工信息(只更新非null字段) -->
|
||||||
|
|||||||
@@ -53,6 +53,9 @@
|
|||||||
<if test="query.relationName != null and query.relationName != ''">
|
<if test="query.relationName != null and query.relationName != ''">
|
||||||
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="query.relationCertNo != null and query.relationCertNo != ''">
|
||||||
|
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
|
||||||
|
</if>
|
||||||
ORDER BY r.create_time DESC
|
ORDER BY r.create_time DESC
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
|
|||||||
@@ -61,6 +61,9 @@
|
|||||||
<if test="query.relationName != null and query.relationName != ''">
|
<if test="query.relationName != null and query.relationName != ''">
|
||||||
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="query.relationCertNo != null and query.relationCertNo != ''">
|
||||||
|
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
|
||||||
|
</if>
|
||||||
<if test="query.status != null">
|
<if test="query.status != null">
|
||||||
AND r.status = #{query.status}
|
AND r.status = #{query.status}
|
||||||
</if>
|
</if>
|
||||||
@@ -115,6 +118,9 @@
|
|||||||
<if test="query.relationName != null and query.relationName != ''">
|
<if test="query.relationName != null and query.relationName != ''">
|
||||||
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
|
||||||
</if>
|
</if>
|
||||||
|
<if test="query.relationCertNo != null and query.relationCertNo != ''">
|
||||||
|
AND r.relation_cert_no LIKE CONCAT('%', #{query.relationCertNo}, '%')
|
||||||
|
</if>
|
||||||
<if test="query.status != null">
|
<if test="query.status != null">
|
||||||
AND r.status = #{query.status}
|
AND r.status = #{query.status}
|
||||||
</if>
|
</if>
|
||||||
|
|||||||
@@ -6,7 +6,8 @@
|
|||||||
|
|
||||||
<!-- 招聘信息ResultMap -->
|
<!-- 招聘信息ResultMap -->
|
||||||
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO" id="CcdiStaffRecruitmentVOResult">
|
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffRecruitmentVO" id="CcdiStaffRecruitmentVOResult">
|
||||||
<id property="recruitId" column="recruit_id"/>
|
<id property="id" column="id"/>
|
||||||
|
<result property="recruitId" column="recruit_id"/>
|
||||||
<result property="recruitName" column="recruit_name"/>
|
<result property="recruitName" column="recruit_name"/>
|
||||||
<result property="posName" column="pos_name"/>
|
<result property="posName" column="pos_name"/>
|
||||||
<result property="posCategory" column="pos_category"/>
|
<result property="posCategory" column="pos_category"/>
|
||||||
@@ -33,17 +34,17 @@
|
|||||||
<!-- 分页查询招聘信息列表 -->
|
<!-- 分页查询招聘信息列表 -->
|
||||||
<select id="selectRecruitmentPage" resultMap="CcdiStaffRecruitmentVOResult">
|
<select id="selectRecruitmentPage" resultMap="CcdiStaffRecruitmentVOResult">
|
||||||
SELECT
|
SELECT
|
||||||
r.recruit_id, r.recruit_name, r.pos_name, r.pos_category, r.pos_desc,
|
r.id, r.recruit_id, r.recruit_name, r.pos_name, r.pos_category, r.pos_desc,
|
||||||
r.cand_name, r.recruit_type, r.cand_edu, r.cand_id, r.cand_school, r.cand_major, r.cand_grad,
|
r.cand_name, r.recruit_type, r.cand_edu, r.cand_id, r.cand_school, r.cand_major, r.cand_grad,
|
||||||
r.admit_status, COALESCE(w.work_experience_count, 0) AS work_experience_count,
|
r.admit_status, COALESCE(w.work_experience_count, 0) AS work_experience_count,
|
||||||
r.interviewer_name1, r.interviewer_id1, r.interviewer_name2, r.interviewer_id2,
|
r.interviewer_name1, r.interviewer_id1, r.interviewer_name2, r.interviewer_id2,
|
||||||
r.created_by, r.create_time, r.updated_by, r.update_time
|
r.created_by, r.create_time, r.updated_by, r.update_time
|
||||||
FROM ccdi_staff_recruitment r
|
FROM ccdi_staff_recruitment r
|
||||||
LEFT JOIN (
|
LEFT JOIN (
|
||||||
SELECT recruit_id COLLATE utf8mb4_general_ci AS recruit_id, COUNT(1) AS work_experience_count
|
SELECT recruitment_id, COUNT(1) AS work_experience_count
|
||||||
FROM ccdi_staff_recruitment_work
|
FROM ccdi_staff_recruitment_work
|
||||||
GROUP BY recruit_id COLLATE utf8mb4_general_ci
|
GROUP BY recruitment_id
|
||||||
) w ON w.recruit_id COLLATE utf8mb4_general_ci = r.recruit_id COLLATE utf8mb4_general_ci
|
) w ON w.recruitment_id = r.id
|
||||||
<where>
|
<where>
|
||||||
<if test="query.recruitName != null and query.recruitName != ''">
|
<if test="query.recruitName != null and query.recruitName != ''">
|
||||||
AND r.recruit_name LIKE CONCAT('%', #{query.recruitName}, '%')
|
AND r.recruit_name LIKE CONCAT('%', #{query.recruitName}, '%')
|
||||||
@@ -78,16 +79,16 @@
|
|||||||
<!-- 查询招聘信息详情 -->
|
<!-- 查询招聘信息详情 -->
|
||||||
<select id="selectRecruitmentById" resultMap="CcdiStaffRecruitmentVOResult">
|
<select id="selectRecruitmentById" resultMap="CcdiStaffRecruitmentVOResult">
|
||||||
SELECT
|
SELECT
|
||||||
recruit_id, recruit_name, pos_name, pos_category, pos_desc,
|
id, recruit_id, recruit_name, pos_name, pos_category, pos_desc,
|
||||||
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
|
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
|
||||||
admit_status, interviewer_name1, interviewer_id1, interviewer_name2, interviewer_id2,
|
admit_status, interviewer_name1, interviewer_id1, interviewer_name2, interviewer_id2,
|
||||||
created_by, create_time, updated_by, update_time
|
created_by, create_time, updated_by, update_time
|
||||||
FROM ccdi_staff_recruitment
|
FROM ccdi_staff_recruitment
|
||||||
WHERE recruit_id = #{recruitId}
|
WHERE id = #{id}
|
||||||
</select>
|
</select>
|
||||||
|
|
||||||
<!-- 批量插入招聘信息数据 -->
|
<!-- 批量插入招聘信息数据 -->
|
||||||
<insert id="insertBatch">
|
<insert id="insertBatch" useGeneratedKeys="true" keyProperty="id">
|
||||||
INSERT INTO ccdi_staff_recruitment
|
INSERT INTO ccdi_staff_recruitment
|
||||||
(recruit_id, recruit_name, pos_name, pos_category, pos_desc,
|
(recruit_id, recruit_name, pos_name, pos_category, pos_desc,
|
||||||
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
|
cand_name, recruit_type, cand_edu, cand_id, cand_school, cand_major, cand_grad,
|
||||||
@@ -124,7 +125,7 @@
|
|||||||
interviewer_id2 = #{item.interviewerId2},
|
interviewer_id2 = #{item.interviewerId2},
|
||||||
updated_by = #{item.updatedBy},
|
updated_by = #{item.updatedBy},
|
||||||
update_time = NOW()
|
update_time = NOW()
|
||||||
WHERE recruit_id = #{item.recruitId}
|
WHERE id = #{item.id}
|
||||||
</foreach>
|
</foreach>
|
||||||
</update>
|
</update>
|
||||||
|
|
||||||
|
|||||||
@@ -35,6 +35,7 @@ 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 {
|
||||||
|
|||||||
@@ -21,4 +21,15 @@ class CcdiBaseStaffMapperTest {
|
|||||||
assertTrue(xml.contains("#{item.partyMember}"), xml);
|
assertTrue(xml.contains("#{item.partyMember}"), xml);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void mapperXml_shouldUseStableOrderForBaseStaffPagination() throws Exception {
|
||||||
|
try (InputStream inputStream = getClass().getClassLoader()
|
||||||
|
.getResourceAsStream("mapper/info/collection/CcdiBaseStaffMapper.xml")) {
|
||||||
|
String xml = new String(inputStream.readAllBytes(), StandardCharsets.UTF_8)
|
||||||
|
.replaceAll("\\s+", " ");
|
||||||
|
|
||||||
|
assertTrue(xml.contains("ORDER BY e.create_time DESC, e.staff_id DESC"), xml);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -96,6 +96,26 @@ class CcdiAssetInfoImportServiceImplTest {
|
|||||||
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importAssetInfoSync_shouldResolveFamilyIdFromCurrentWorkbookRelation() {
|
||||||
|
CcdiAssetInfoExcel excel = buildExcel("320101199001010033", "股权");
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
when(assetInfoMapper.selectOwnerCandidatesByRelationCertNos(List.of("320101199001010033")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
service.importAssetInfoSync(
|
||||||
|
List.of(excel),
|
||||||
|
"task-current-workbook",
|
||||||
|
"tester",
|
||||||
|
Map.of("320101199001010033", Set.of("320101199009090099"))
|
||||||
|
);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(assetInfoMapper).insertBatch(captor.capture());
|
||||||
|
assertEquals("320101199009090099", captor.getValue().get(0).getFamilyId());
|
||||||
|
assertEquals("320101199001010033", captor.getValue().get(0).getPersonId());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void importAssetInfoAsync_shouldFailWhenEmployeeIdCardIsUsedForFamilyAssetImport() {
|
void importAssetInfoAsync_shouldFailWhenEmployeeIdCardIsUsedForFamilyAssetImport() {
|
||||||
CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
|
CcdiAssetInfoExcel excel = buildExcel("320101199001010011", "房产");
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ import org.springframework.data.redis.core.ValueOperations;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.Set;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
@@ -79,6 +80,26 @@ class CcdiBaseStaffAssetImportServiceImplTest {
|
|||||||
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
assertEquals("320101199001010011", captor.getValue().get(0).getPersonId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importAssetInfoSync_shouldImportWhenOwnerComesFromCurrentWorkbook() {
|
||||||
|
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199001010033", "存款");
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
when(assetInfoMapper.selectOwnerCandidatesByBaseStaffIdCards(List.of("320101199001010033")))
|
||||||
|
.thenReturn(List.of());
|
||||||
|
|
||||||
|
service.importAssetInfoSync(
|
||||||
|
List.of(excel),
|
||||||
|
"task-current-workbook",
|
||||||
|
"tester",
|
||||||
|
Map.of("320101199001010033", Set.of("320101199001010033"))
|
||||||
|
);
|
||||||
|
|
||||||
|
ArgumentCaptor<List<CcdiAssetInfo>> captor = ArgumentCaptor.forClass(List.class);
|
||||||
|
verify(assetInfoMapper).insertBatch(captor.capture());
|
||||||
|
assertEquals("320101199001010033", captor.getValue().get(0).getFamilyId());
|
||||||
|
assertEquals("320101199001010033", captor.getValue().get(0).getPersonId());
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void importAssetInfoAsync_shouldFailWhenFamilyCertificateIsUsed() {
|
void importAssetInfoAsync_shouldFailWhenFamilyCertificateIsUsed() {
|
||||||
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
|
CcdiBaseStaffAssetInfoExcel excel = buildExcel("320101199201010022", "车辆");
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
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;
|
||||||
@@ -7,26 +8,33 @@ 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;
|
||||||
@@ -41,6 +49,9 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private CreditParseClient creditParseClient;
|
private CreditParseClient creditParseClient;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private CreditHtmlStorageService creditHtmlStorageService;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CreditInfoPayloadAssembler assembler;
|
private CreditInfoPayloadAssembler assembler;
|
||||||
|
|
||||||
@@ -54,11 +65,15 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
private CcdiCreditInfoQueryMapper queryMapper;
|
private CcdiCreditInfoQueryMapper queryMapper;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() {
|
void uploadHtmlFiles_shouldStoreCreditObjectWithoutStaffBinding() throws Exception {
|
||||||
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(creditParseClient.parse(anyString(), anyString(), any(File.class)))
|
when(creditHtmlStorageService.save(any()))
|
||||||
|
.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")));
|
||||||
@@ -69,15 +84,20 @@ 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() {
|
void uploadHtmlFiles_shouldRejectOlderReportDate() throws Exception {
|
||||||
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(creditParseClient.parse(anyString(), anyString(), any(File.class)))
|
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"));
|
||||||
|
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"));
|
||||||
@@ -88,7 +108,68 @@ class CcdiCreditInfoServiceImplTest {
|
|||||||
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
|
assertEquals("上传征信日期早于当前已维护最新记录", result.getFailures().get(0).getReason());
|
||||||
}
|
}
|
||||||
|
|
||||||
private CreditParseResponse successResponse(String personId, String personName, String reportTime) {
|
@Test
|
||||||
|
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);
|
||||||
@@ -99,9 +180,20 @@ 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.setStatusCode("0");
|
response.setMessage("成功");
|
||||||
|
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) {
|
||||||
|
|||||||
@@ -1,14 +1,13 @@
|
|||||||
package com.ruoyi.info.collection.service;
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
import com.ruoyi.info.collection.domain.CcdiBizIntermediary;
|
||||||
import com.ruoyi.info.collection.domain.CcdiEnterpriseBaseInfo;
|
|
||||||
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiIntermediaryEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.IntermediaryEnterpriseRelationImportFailureVO;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
import com.ruoyi.info.collection.mapper.CcdiBizIntermediaryMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiEnterpriseBaseInfoMapper;
|
|
||||||
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiIntermediaryEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryEnterpriseRelationImportServiceImpl;
|
import com.ruoyi.info.collection.service.impl.CcdiIntermediaryEnterpriseRelationImportServiceImpl;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
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.ArgumentCaptor;
|
||||||
@@ -23,6 +22,7 @@ import java.util.List;
|
|||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
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;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
@@ -42,7 +42,7 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
private CcdiBizIntermediaryMapper intermediaryMapper;
|
private CcdiBizIntermediaryMapper intermediaryMapper;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private CcdiEnterpriseBaseInfoMapper enterpriseBaseInfoMapper;
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
@@ -58,11 +58,11 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
||||||
prepareFailureRedisMocks();
|
prepareFailureRedisMocks();
|
||||||
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
|
when(intermediaryMapper.selectList(any())).thenReturn(List.of());
|
||||||
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(enterprise("91330100MA27X12345")));
|
|
||||||
|
|
||||||
service.importAsync(List.of(excel), "task-owner-miss", "tester");
|
service.importAsync(List.of(excel), "task-owner-miss", "tester");
|
||||||
|
|
||||||
verify(relationMapper, never()).insertBatch(any());
|
verify(relationMapper, never()).insertBatch(any());
|
||||||
|
verify(enterpriseAutoFillService, never()).ensureExistsBatch(any());
|
||||||
IntermediaryEnterpriseRelationImportFailureVO failure =
|
IntermediaryEnterpriseRelationImportFailureVO failure =
|
||||||
firstFailure("import:intermediary-enterprise-relation:task-owner-miss:failures");
|
firstFailure("import:intermediary-enterprise-relation:task-owner-miss:failures");
|
||||||
assertEquals("320101199001010014", failure.getOwnerPersonId());
|
assertEquals("320101199001010014", failure.getOwnerPersonId());
|
||||||
@@ -70,20 +70,20 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void importEnterpriseRelationAsync_shouldFailWhenEnterpriseDoesNotExist() {
|
void importEnterpriseRelationAsync_shouldAutoFillWhenEnterpriseDoesNotExist() {
|
||||||
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
||||||
prepareFailureRedisMocks();
|
prepareStatusRedisMock();
|
||||||
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
|
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
|
||||||
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of());
|
|
||||||
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
|
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
|
||||||
|
|
||||||
service.importAsync(List.of(excel), "task-ent-miss", "tester");
|
service.importAsync(List.of(excel), "task-ent-miss", "tester");
|
||||||
|
|
||||||
verify(relationMapper, never()).insertBatch(any());
|
ArgumentCaptor<List<CcdiIntermediaryEnterpriseRelation>> relationCaptor = ArgumentCaptor.forClass(List.class);
|
||||||
IntermediaryEnterpriseRelationImportFailureVO failure =
|
verify(relationMapper).insertBatch(relationCaptor.capture());
|
||||||
firstFailure("import:intermediary-enterprise-relation:task-ent-miss:failures");
|
assertEquals(1, relationCaptor.getValue().size());
|
||||||
assertEquals("91330100MA27X12345", failure.getSocialCreditCode());
|
assertEquals("owner-biz", relationCaptor.getValue().get(0).getIntermediaryBizId());
|
||||||
assertTrue(failure.getErrorMessage().contains("机构表"));
|
assertEquals("91330100MA27X12345", relationCaptor.getValue().get(0).getSocialCreditCode());
|
||||||
|
assertIntermediaryAutoFill("91330100MA27X12345");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -96,10 +96,6 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
owner("owner-biz-1", "320101199001010014"),
|
owner("owner-biz-1", "320101199001010014"),
|
||||||
owner("owner-biz-2", "320101199003030035")
|
owner("owner-biz-2", "320101199003030035")
|
||||||
));
|
));
|
||||||
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(
|
|
||||||
enterprise("91330100MA27X12345"),
|
|
||||||
enterprise("91330100MA27X12346")
|
|
||||||
));
|
|
||||||
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of("owner-biz-1|91330100MA27X12345"));
|
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of("owner-biz-1|91330100MA27X12345"));
|
||||||
|
|
||||||
service.importAsync(List.of(duplicateInDb, duplicateInFile1, duplicateInFile2), "task-duplicate", "tester");
|
service.importAsync(List.of(duplicateInDb, duplicateInFile1, duplicateInFile2), "task-duplicate", "tester");
|
||||||
@@ -108,6 +104,7 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
verify(relationMapper).insertBatch(captor.capture());
|
verify(relationMapper).insertBatch(captor.capture());
|
||||||
assertEquals(1, captor.getValue().size());
|
assertEquals(1, captor.getValue().size());
|
||||||
assertEquals("owner-biz-2", captor.getValue().get(0).getIntermediaryBizId());
|
assertEquals("owner-biz-2", captor.getValue().get(0).getIntermediaryBizId());
|
||||||
|
assertIntermediaryAutoFill("91330100MA27X12346");
|
||||||
IntermediaryEnterpriseRelationImportFailureVO failure =
|
IntermediaryEnterpriseRelationImportFailureVO failure =
|
||||||
firstFailure("import:intermediary-enterprise-relation:task-duplicate:failures");
|
firstFailure("import:intermediary-enterprise-relation:task-duplicate:failures");
|
||||||
assertTrue(failure.getErrorMessage().contains("重复") || failure.getErrorMessage().contains("已存在"));
|
assertTrue(failure.getErrorMessage().contains("重复") || failure.getErrorMessage().contains("已存在"));
|
||||||
@@ -118,7 +115,6 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
CcdiIntermediaryEnterpriseRelationExcel excel = buildExcel("320101199001010014", "91330100MA27X12345");
|
||||||
prepareStatusRedisMock();
|
prepareStatusRedisMock();
|
||||||
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
|
when(intermediaryMapper.selectList(any())).thenReturn(List.of(owner("owner-biz", "320101199001010014")));
|
||||||
when(enterpriseBaseInfoMapper.selectList(any())).thenReturn(List.of(enterprise("91330100MA27X12345")));
|
|
||||||
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
|
when(relationMapper.batchExistsByCombinations(any())).thenReturn(List.of());
|
||||||
|
|
||||||
service.importAsync(List.of(excel), "task-success", "tester");
|
service.importAsync(List.of(excel), "task-success", "tester");
|
||||||
@@ -127,6 +123,7 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
verify(relationMapper).insertBatch(captor.capture());
|
verify(relationMapper).insertBatch(captor.capture());
|
||||||
assertEquals(1, captor.getValue().size());
|
assertEquals(1, captor.getValue().size());
|
||||||
assertEquals("owner-biz", captor.getValue().get(0).getIntermediaryBizId());
|
assertEquals("owner-biz", captor.getValue().get(0).getIntermediaryBizId());
|
||||||
|
assertIntermediaryAutoFill("91330100MA27X12345");
|
||||||
verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
|
verify(valueOperations, never()).set(any(), any(), any(Long.class), any(TimeUnit.class));
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -163,10 +160,15 @@ class CcdiIntermediaryEnterpriseRelationImportServiceImplTest {
|
|||||||
return owner;
|
return owner;
|
||||||
}
|
}
|
||||||
|
|
||||||
private CcdiEnterpriseBaseInfo enterprise(String socialCreditCode) {
|
private void assertIntermediaryAutoFill(String socialCreditCode) {
|
||||||
CcdiEnterpriseBaseInfo enterprise = new CcdiEnterpriseBaseInfo();
|
ArgumentCaptor<List<EnterpriseAutoFillService.EnterpriseFillItem>> captor = ArgumentCaptor.forClass(List.class);
|
||||||
enterprise.setSocialCreditCode(socialCreditCode);
|
verify(enterpriseAutoFillService).ensureExistsBatch(captor.capture());
|
||||||
enterprise.setEnterpriseName("机构" + socialCreditCode.substring(socialCreditCode.length() - 2));
|
assertEquals(1, captor.getValue().size());
|
||||||
return enterprise;
|
EnterpriseAutoFillService.EnterpriseFillItem item = captor.getValue().get(0);
|
||||||
|
assertEquals(socialCreditCode, item.socialCreditCode());
|
||||||
|
assertNull(item.enterpriseName());
|
||||||
|
assertEquals("INTERMEDIARY", item.entSource());
|
||||||
|
assertEquals("IMPORT", item.dataSource());
|
||||||
|
assertEquals("tester", item.userName());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,6 +2,8 @@ package com.ruoyi.info.collection.service;
|
|||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
|
||||||
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
import com.baomidou.mybatisplus.core.metadata.TableInfoHelper;
|
||||||
|
import com.ruoyi.common.core.domain.entity.SysUser;
|
||||||
|
import com.ruoyi.common.core.domain.model.LoginUser;
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
|
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
|
||||||
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
|
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
|
||||||
@@ -10,8 +12,10 @@ import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationOptionVO;
|
|||||||
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
|
||||||
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl;
|
import com.ruoyi.info.collection.service.impl.CcdiStaffEnterpriseRelationServiceImpl;
|
||||||
|
import com.ruoyi.info.collection.service.support.EnterpriseAutoFillService;
|
||||||
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
import org.apache.ibatis.builder.MapperBuilderAssistant;
|
||||||
import org.apache.ibatis.session.Configuration;
|
import org.apache.ibatis.session.Configuration;
|
||||||
|
import org.junit.jupiter.api.AfterEach;
|
||||||
import org.junit.jupiter.api.BeforeAll;
|
import org.junit.jupiter.api.BeforeAll;
|
||||||
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;
|
||||||
@@ -20,13 +24,17 @@ 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.data.redis.core.RedisTemplate;
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
|
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
|
||||||
|
import org.springframework.security.core.context.SecurityContextHolder;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Set;
|
||||||
|
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
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.junit.jupiter.api.Assertions.assertThrows;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
|
import static org.mockito.ArgumentMatchers.argThat;
|
||||||
import static org.mockito.ArgumentMatchers.isNull;
|
import static org.mockito.ArgumentMatchers.isNull;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
@@ -55,8 +63,17 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
|
|||||||
@Mock
|
@Mock
|
||||||
private RedisTemplate<String, Object> redisTemplate;
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
|
@Mock
|
||||||
|
private EnterpriseAutoFillService enterpriseAutoFillService;
|
||||||
|
|
||||||
|
@AfterEach
|
||||||
|
void clearSecurityContext() {
|
||||||
|
SecurityContextHolder.clearContext();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void insertRelation_shouldAllowValidFamily() {
|
void insertRelation_shouldAllowValidFamily() {
|
||||||
|
mockLoginUser("tester");
|
||||||
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
|
CcdiStaffEnterpriseRelationAddDTO addDTO = buildAddDto();
|
||||||
CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
|
CcdiStaffFmyRelation familyRelation = new CcdiStaffFmyRelation();
|
||||||
familyRelation.setRelationCertNo(addDTO.getPersonId());
|
familyRelation.setRelationCertNo(addDTO.getPersonId());
|
||||||
@@ -75,6 +92,13 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
|
|||||||
assertEquals(1, captor.getValue().getStatus());
|
assertEquals(1, captor.getValue().getStatus());
|
||||||
assertEquals("MANUAL", captor.getValue().getDataSource());
|
assertEquals("MANUAL", captor.getValue().getDataSource());
|
||||||
assertEquals(1, captor.getValue().getIsEmpFamily());
|
assertEquals(1, captor.getValue().getIsEmpFamily());
|
||||||
|
verify(enterpriseAutoFillService).ensureExists(argThat(item ->
|
||||||
|
"91310000123456789A".equals(item.socialCreditCode())
|
||||||
|
&& "测试企业".equals(item.enterpriseName())
|
||||||
|
&& "EMP_RELATION".equals(item.entSource())
|
||||||
|
&& "MANUAL".equals(item.dataSource())
|
||||||
|
&& "tester".equals(item.userName())
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@@ -153,4 +177,13 @@ class CcdiStaffEnterpriseRelationServiceImplTest {
|
|||||||
assistant.setCurrentNamespace(namespace);
|
assistant.setCurrentNamespace(namespace);
|
||||||
TableInfoHelper.initTableInfo(assistant, entityClass);
|
TableInfoHelper.initTableInfo(assistant, entityClass);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mockLoginUser(String userName) {
|
||||||
|
SysUser user = new SysUser();
|
||||||
|
user.setUserName(userName);
|
||||||
|
LoginUser loginUser = new LoginUser(1L, 1L, user, Set.of());
|
||||||
|
UsernamePasswordAuthenticationToken authentication =
|
||||||
|
new UsernamePasswordAuthenticationToken(loginUser, null, List.of());
|
||||||
|
SecurityContextHolder.getContext().setAuthentication(authentication);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,8 @@
|
|||||||
package com.ruoyi.info.collection.service;
|
package com.ruoyi.info.collection.service;
|
||||||
|
|
||||||
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
import com.ruoyi.info.collection.domain.CcdiStaffRecruitment;
|
||||||
|
import com.ruoyi.info.collection.domain.CcdiStaffRecruitmentWork;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
||||||
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
|
import com.ruoyi.info.collection.domain.vo.RecruitmentImportFailureVO;
|
||||||
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
|
import com.ruoyi.info.collection.mapper.CcdiStaffRecruitmentMapper;
|
||||||
@@ -27,6 +29,7 @@ import static org.mockito.ArgumentMatchers.anyLong;
|
|||||||
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.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
|
|
||||||
@@ -55,7 +58,7 @@ class CcdiStaffRecruitmentImportServiceImplTest {
|
|||||||
void shouldFailWholeWorkGroupWhenExistingHistoryExists() {
|
void shouldFailWholeWorkGroupWhenExistingHistoryExists() {
|
||||||
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
when(redisTemplate.opsForValue()).thenReturn(valueOperations);
|
||||||
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
when(recruitmentMapper.selectBatchIds(any())).thenReturn(List.of(buildRecruitment("RC001")));
|
when(recruitmentMapper.selectList(any())).thenReturn(List.of(buildRecruitment("RC001")));
|
||||||
when(recruitmentWorkMapper.selectCount(any())).thenReturn(1L);
|
when(recruitmentWorkMapper.selectCount(any())).thenReturn(1L);
|
||||||
|
|
||||||
CcdiStaffRecruitmentWorkExcel workRow = new CcdiStaffRecruitmentWorkExcel();
|
CcdiStaffRecruitmentWorkExcel workRow = new CcdiStaffRecruitmentWorkExcel();
|
||||||
@@ -86,13 +89,81 @@ class CcdiStaffRecruitmentImportServiceImplTest {
|
|||||||
assertEquals("招聘记录编号[RC001]已存在历史工作经历,不允许重复导入", failure.getErrorMessage());
|
assertEquals("招聘记录编号[RC001]已存在历史工作经历,不允许重复导入", failure.getErrorMessage());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAllowDuplicateRecruitIdsWhenImportingMainSheet() {
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
when(recruitmentMapper.insert(any(CcdiStaffRecruitment.class))).thenReturn(1);
|
||||||
|
|
||||||
|
CcdiStaffRecruitmentExcel first = buildRecruitmentExcel("RC001", "张三");
|
||||||
|
CcdiStaffRecruitmentExcel second = buildRecruitmentExcel("RC001", "李四");
|
||||||
|
|
||||||
|
service.importRecruitmentAsync(List.of(first, second), Collections.emptyList(), "task-2", "admin");
|
||||||
|
|
||||||
|
verify(recruitmentMapper, times(2)).insert(any(CcdiStaffRecruitment.class));
|
||||||
|
verify(valueOperations, never()).set(eq("import:recruitment:task-2:failures"), any(), anyLong(), any());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void shouldAttachWorkToMatchedRecruitmentWhenRecruitIdIsDuplicated() {
|
||||||
|
when(redisTemplate.opsForHash()).thenReturn(hashOperations);
|
||||||
|
CcdiStaffRecruitment matched = buildRecruitment(10L, "RC001", "张三");
|
||||||
|
CcdiStaffRecruitment other = buildRecruitment(11L, "RC001", "李四");
|
||||||
|
when(recruitmentMapper.selectList(any())).thenReturn(List.of(matched, other));
|
||||||
|
when(recruitmentWorkMapper.selectCount(any())).thenReturn(0L);
|
||||||
|
|
||||||
|
CcdiStaffRecruitmentWorkExcel workRow = buildWorkExcel("RC001", "张三");
|
||||||
|
|
||||||
|
service.importRecruitmentAsync(Collections.emptyList(), List.of(workRow), "task-3", "admin");
|
||||||
|
|
||||||
|
ArgumentCaptor<CcdiStaffRecruitmentWork> workCaptor = ArgumentCaptor.forClass(CcdiStaffRecruitmentWork.class);
|
||||||
|
verify(recruitmentWorkMapper).insert(workCaptor.capture());
|
||||||
|
assertEquals(10L, workCaptor.getValue().getRecruitmentId());
|
||||||
|
assertEquals("RC001", workCaptor.getValue().getRecruitId());
|
||||||
|
}
|
||||||
|
|
||||||
private CcdiStaffRecruitment buildRecruitment(String recruitId) {
|
private CcdiStaffRecruitment buildRecruitment(String recruitId) {
|
||||||
|
return buildRecruitment(1L, recruitId, "张三");
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiStaffRecruitment buildRecruitment(Long id, String recruitId, String candName) {
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
|
recruitment.setId(id);
|
||||||
recruitment.setRecruitId(recruitId);
|
recruitment.setRecruitId(recruitId);
|
||||||
recruitment.setRecruitType("SOCIAL");
|
recruitment.setRecruitType("SOCIAL");
|
||||||
recruitment.setCandName("张三");
|
recruitment.setCandName(candName);
|
||||||
recruitment.setRecruitName("社会招聘项目");
|
recruitment.setRecruitName("社会招聘项目");
|
||||||
recruitment.setPosName("Java工程师");
|
recruitment.setPosName("Java工程师");
|
||||||
return recruitment;
|
return recruitment;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private CcdiStaffRecruitmentExcel buildRecruitmentExcel(String recruitId, String candName) {
|
||||||
|
CcdiStaffRecruitmentExcel excel = new CcdiStaffRecruitmentExcel();
|
||||||
|
excel.setRecruitId(recruitId);
|
||||||
|
excel.setRecruitName("社会招聘项目");
|
||||||
|
excel.setPosName("Java工程师");
|
||||||
|
excel.setPosCategory("技术类");
|
||||||
|
excel.setPosDesc("负责系统开发");
|
||||||
|
excel.setAdmitStatus("录用");
|
||||||
|
excel.setCandName(candName);
|
||||||
|
excel.setRecruitType("SOCIAL");
|
||||||
|
excel.setCandEdu("本科");
|
||||||
|
excel.setCandId(candName.equals("张三") ? "110105199001010010" : "110105199002020026");
|
||||||
|
excel.setCandGrad("202110");
|
||||||
|
excel.setCandSchool("四川大学");
|
||||||
|
excel.setCandMajor("法学");
|
||||||
|
return excel;
|
||||||
|
}
|
||||||
|
|
||||||
|
private CcdiStaffRecruitmentWorkExcel buildWorkExcel(String recruitId, String candName) {
|
||||||
|
CcdiStaffRecruitmentWorkExcel workRow = new CcdiStaffRecruitmentWorkExcel();
|
||||||
|
workRow.setRecruitId(recruitId);
|
||||||
|
workRow.setCandName(candName);
|
||||||
|
workRow.setRecruitName("社会招聘项目");
|
||||||
|
workRow.setPosName("Java工程师");
|
||||||
|
workRow.setSortOrder(1);
|
||||||
|
workRow.setCompanyName("测试科技");
|
||||||
|
workRow.setPositionName("开发工程师");
|
||||||
|
workRow.setJobStartMonth("2022-01");
|
||||||
|
return workRow;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -12,6 +12,7 @@ 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 {
|
||||||
@@ -28,13 +29,18 @@ 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(2, rows.size());
|
assertEquals(3, 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
|
||||||
@@ -51,6 +57,42 @@ 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();
|
||||||
|
|||||||
@@ -2,12 +2,22 @@ package com.ruoyi.info.collection.utils;
|
|||||||
|
|
||||||
import com.ruoyi.common.core.domain.entity.SysDictData;
|
import com.ruoyi.common.core.domain.entity.SysDictData;
|
||||||
import com.ruoyi.common.utils.DictUtils;
|
import com.ruoyi.common.utils.DictUtils;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiAccountInfoExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffAssetInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiEnterpriseBaseInfoExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEnterpriseRelationExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryEntityExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiIntermediaryPersonExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiPurchaseTransactionSupplierExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
|
||||||
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
import com.ruoyi.info.collection.domain.excel.CcdiStaffRecruitmentWorkExcel;
|
||||||
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
|
|
||||||
import org.apache.poi.ss.usermodel.CellStyle;
|
import org.apache.poi.ss.usermodel.CellStyle;
|
||||||
import org.apache.poi.ss.usermodel.DataValidation;
|
import org.apache.poi.ss.usermodel.DataValidation;
|
||||||
import org.apache.poi.ss.usermodel.Row;
|
import org.apache.poi.ss.usermodel.Row;
|
||||||
@@ -99,6 +109,63 @@ class EasyExcelUtilTemplateTest {
|
|||||||
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
Sheet sheet = workbook.getSheetAt(0);
|
Sheet sheet = workbook.getSheetAt(0);
|
||||||
assertTrue(hasValidationOnColumn(sheet, 7), "是否党员列应包含下拉校验");
|
assertTrue(hasValidationOnColumn(sheet, 7), "是否党员列应包含下拉校验");
|
||||||
|
assertHeaderValue(sheet, 0, "姓名*");
|
||||||
|
assertHeaderValue(sheet, 1, "员工ID*");
|
||||||
|
assertHeaderValue(sheet, 2, "所属部门ID*");
|
||||||
|
assertHeaderValue(sheet, 3, "身份证号*");
|
||||||
|
assertHeaderValue(sheet, 4, "电话*");
|
||||||
|
assertHeaderValue(sheet, 7, "是否党员*");
|
||||||
|
assertHeaderValue(sheet, 8, "状态*");
|
||||||
|
assertTextColumn(sheet, 3);
|
||||||
|
assertTextColumn(sheet, 4);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void importTemplateWithDictDropdown_shouldKeepBaseStaffDualSheetColumnWidths() throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
|
||||||
|
try (MockedStatic<DictUtils> mocked = mockStatic(DictUtils.class)) {
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_employee_status"))
|
||||||
|
.thenReturn(List.of(
|
||||||
|
buildDictData("在职", "0"),
|
||||||
|
buildDictData("离职", "1")
|
||||||
|
));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_yes_no_flag"))
|
||||||
|
.thenReturn(List.of(
|
||||||
|
buildDictData("是", "1"),
|
||||||
|
buildDictData("否", "0")
|
||||||
|
));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_asset_status"))
|
||||||
|
.thenReturn(List.of(
|
||||||
|
buildDictData("正常"),
|
||||||
|
buildDictData("冻结"),
|
||||||
|
buildDictData("处置中")
|
||||||
|
));
|
||||||
|
|
||||||
|
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||||
|
response,
|
||||||
|
CcdiBaseStaffExcel.class,
|
||||||
|
"员工信息",
|
||||||
|
CcdiBaseStaffAssetInfoExcel.class,
|
||||||
|
"员工资产信息",
|
||||||
|
"员工信息维护导入模板"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
Sheet staffSheet = workbook.getSheet("员工信息");
|
||||||
|
Sheet assetSheet = workbook.getSheet("员工资产信息");
|
||||||
|
assertNotNull(staffSheet);
|
||||||
|
assertNotNull(assetSheet);
|
||||||
|
|
||||||
|
assertColumnWidthsAtLeast(staffSheet, new int[] {16, 18, 20, 24, 18, 20, 18, 16, 14});
|
||||||
|
assertColumnWidthsAtLeast(assetSheet, new int[] {24, 18, 18, 24, 14, 20, 18, 18, 20, 16, 32});
|
||||||
|
assertHeaderValue(staffSheet, 0, "姓名*");
|
||||||
|
assertHeaderValue(staffSheet, 8, "状态*");
|
||||||
|
assertHeaderValue(assetSheet, 0, "员工身份证号*");
|
||||||
|
assertHeaderValue(assetSheet, 1, "资产大类*");
|
||||||
|
assertHeaderValue(assetSheet, 10, "备注");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,6 +180,11 @@ class EasyExcelUtilTemplateTest {
|
|||||||
buildDictData("未录用"),
|
buildDictData("未录用"),
|
||||||
buildDictData("放弃")
|
buildDictData("放弃")
|
||||||
));
|
));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_recruit_type"))
|
||||||
|
.thenReturn(List.of(
|
||||||
|
buildDictData("社招", "SOCIAL"),
|
||||||
|
buildDictData("校招", "CAMPUS")
|
||||||
|
));
|
||||||
|
|
||||||
EasyExcelUtil.importTemplateWithDictDropdown(
|
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||||
response,
|
response,
|
||||||
@@ -128,6 +200,8 @@ class EasyExcelUtilTemplateTest {
|
|||||||
assertEquals(2, workbook.getNumberOfSheets(), "招聘导入模板应输出双Sheet");
|
assertEquals(2, workbook.getNumberOfSheets(), "招聘导入模板应输出双Sheet");
|
||||||
assertEquals("招聘信息", workbook.getSheetAt(0).getSheetName());
|
assertEquals("招聘信息", workbook.getSheetAt(0).getSheetName());
|
||||||
assertEquals("历史工作经历", workbook.getSheetAt(1).getSheetName());
|
assertEquals("历史工作经历", workbook.getSheetAt(1).getSheetName());
|
||||||
|
assertTrue(hasValidationOnColumn(workbook.getSheetAt(0), 5), "录用情况列应包含下拉校验");
|
||||||
|
assertTrue(hasValidationOnColumn(workbook.getSheetAt(0), 7), "招聘类型列应包含下拉校验");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -155,12 +229,119 @@ class EasyExcelUtilTemplateTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void infoImportTemplates_shouldFormatIdentifierAndContactColumnsAsText() throws Exception {
|
||||||
|
try (MockedStatic<DictUtils> mocked = mockStatic(DictUtils.class)) {
|
||||||
|
mockCommonDicts(mocked);
|
||||||
|
|
||||||
|
assertPlainTemplateTextColumns(CcdiAccountInfoExcel.class, "账户库管理", 1, 3, 7);
|
||||||
|
assertSingleTemplateTextColumns(CcdiAssetInfoExcel.class, "亲属资产信息", 0);
|
||||||
|
assertSingleTemplateTextColumns(CcdiBaseStaffAssetInfoExcel.class, "员工资产信息", 0);
|
||||||
|
assertDualTemplateTextColumns(
|
||||||
|
CcdiBaseStaffExcel.class,
|
||||||
|
"员工信息",
|
||||||
|
new int[] {3, 4},
|
||||||
|
CcdiBaseStaffAssetInfoExcel.class,
|
||||||
|
"员工资产信息",
|
||||||
|
new int[] {0},
|
||||||
|
"员工信息维护导入模板"
|
||||||
|
);
|
||||||
|
assertSingleTemplateTextColumns(CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息", 0, 1);
|
||||||
|
assertSingleTemplateTextColumns(CcdiCustFmyRelationExcel.class, "信贷客户家庭关系", 0, 6, 7, 8);
|
||||||
|
assertSingleTemplateTextColumns(CcdiEnterpriseBaseInfoExcel.class, "实体库管理", 0, 10);
|
||||||
|
assertSingleTemplateTextColumns(CcdiIntermediaryPersonExcel.class, "个人中介信息", 5, 6, 10, 12);
|
||||||
|
assertSingleTemplateTextColumns(CcdiIntermediaryEntityExcel.class, "实体中介信息", 1, 10);
|
||||||
|
assertSingleTemplateTextColumns(CcdiIntermediaryEnterpriseRelationExcel.class, "中介实体关联关系信息", 0, 1);
|
||||||
|
assertDualTemplateTextColumns(
|
||||||
|
CcdiPurchaseTransactionExcel.class,
|
||||||
|
"招投标主信息",
|
||||||
|
new int[] {0, 21, 24},
|
||||||
|
CcdiPurchaseTransactionSupplierExcel.class,
|
||||||
|
"供应商信息",
|
||||||
|
new int[] {0, 2, 4, 5},
|
||||||
|
"招投标信息维护导入模板"
|
||||||
|
);
|
||||||
|
assertSingleTemplateTextColumns(CcdiStaffEnterpriseRelationExcel.class, "员工亲属实体关联", 0, 1);
|
||||||
|
assertSingleTemplateTextColumns(CcdiStaffFmyRelationExcel.class, "员工亲属关系信息", 0, 6, 7, 8);
|
||||||
|
assertDualTemplateTextColumns(
|
||||||
|
CcdiStaffRecruitmentExcel.class,
|
||||||
|
"招聘信息",
|
||||||
|
new int[] {0, 9, 14, 16},
|
||||||
|
CcdiStaffRecruitmentWorkExcel.class,
|
||||||
|
"历史工作经历",
|
||||||
|
new int[] {0},
|
||||||
|
"招聘信息管理导入模板"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void assertTextColumn(Sheet sheet, int columnIndex) {
|
private void assertTextColumn(Sheet sheet, int columnIndex) {
|
||||||
CellStyle style = sheet.getColumnStyle(columnIndex);
|
CellStyle style = sheet.getColumnStyle(columnIndex);
|
||||||
assertNotNull(style, "文本列应设置默认样式");
|
assertNotNull(style, "文本列应设置默认样式");
|
||||||
assertEquals("@", style.getDataFormatString(), "证件号列应使用文本格式");
|
assertEquals("@", style.getDataFormatString(), "证件号列应使用文本格式");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void assertHeaderValue(Sheet sheet, int columnIndex, String expectedValue) {
|
||||||
|
assertEquals(expectedValue, sheet.getRow(0).getCell(columnIndex).getStringCellValue());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertTextColumns(Sheet sheet, int... columnIndexes) {
|
||||||
|
for (int columnIndex : columnIndexes) {
|
||||||
|
assertTextColumn(sheet, columnIndex);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertPlainTemplateTextColumns(Class<?> clazz, String sheetName, int... columnIndexes) throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
EasyExcelUtil.importTemplateExcel(response, clazz, sheetName);
|
||||||
|
|
||||||
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
assertTextColumns(workbook.getSheetAt(0), columnIndexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertSingleTemplateTextColumns(Class<?> clazz, String sheetName, int... columnIndexes) throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
EasyExcelUtil.importTemplateWithDictDropdown(response, clazz, sheetName);
|
||||||
|
|
||||||
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
assertTextColumns(workbook.getSheetAt(0), columnIndexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private <T1, T2> void assertDualTemplateTextColumns(Class<T1> firstClazz,
|
||||||
|
String firstSheetName,
|
||||||
|
int[] firstColumnIndexes,
|
||||||
|
Class<T2> secondClazz,
|
||||||
|
String secondSheetName,
|
||||||
|
int[] secondColumnIndexes,
|
||||||
|
String fileName) throws Exception {
|
||||||
|
MockHttpServletResponse response = new MockHttpServletResponse();
|
||||||
|
EasyExcelUtil.importTemplateWithDictDropdown(
|
||||||
|
response,
|
||||||
|
firstClazz,
|
||||||
|
firstSheetName,
|
||||||
|
secondClazz,
|
||||||
|
secondSheetName,
|
||||||
|
fileName
|
||||||
|
);
|
||||||
|
|
||||||
|
try (Workbook workbook = WorkbookFactory.create(new ByteArrayInputStream(response.getContentAsByteArray()))) {
|
||||||
|
assertTextColumns(workbook.getSheet(firstSheetName), firstColumnIndexes);
|
||||||
|
assertTextColumns(workbook.getSheet(secondSheetName), secondColumnIndexes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void assertColumnWidthsAtLeast(Sheet sheet, int[] expectedWidths) {
|
||||||
|
for (int columnIndex = 0; columnIndex < expectedWidths.length; columnIndex++) {
|
||||||
|
int currentColumnIndex = columnIndex;
|
||||||
|
int expectedWidth = expectedWidths[currentColumnIndex];
|
||||||
|
int expected = expectedWidth * 256;
|
||||||
|
assertTrue(sheet.getColumnWidth(currentColumnIndex) >= expected,
|
||||||
|
() -> sheet.getSheetName() + "第" + (currentColumnIndex + 1) + "列宽度应不小于" + expectedWidth);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private boolean hasValidationOnColumn(Sheet sheet, int columnIndex) {
|
private boolean hasValidationOnColumn(Sheet sheet, int columnIndex) {
|
||||||
for (DataValidation validation : sheet.getDataValidations()) {
|
for (DataValidation validation : sheet.getDataValidations()) {
|
||||||
for (CellRangeAddress address : validation.getRegions().getCellRangeAddresses()) {
|
for (CellRangeAddress address : validation.getRegions().getCellRangeAddresses()) {
|
||||||
@@ -172,6 +353,33 @@ class EasyExcelUtilTemplateTest {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void mockCommonDicts(MockedStatic<DictUtils> mocked) {
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_asset_status"))
|
||||||
|
.thenReturn(List.of(buildDictData("正常")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_employee_status"))
|
||||||
|
.thenReturn(List.of(buildDictData("在职", "1")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_yes_no_flag"))
|
||||||
|
.thenReturn(List.of(buildDictData("是", "1")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_relation_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("配偶")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_indiv_gender"))
|
||||||
|
.thenReturn(List.of(buildDictData("男")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_certificate_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("居民身份证")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_entity_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("有限责任公司")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_enterprise_nature"))
|
||||||
|
.thenReturn(List.of(buildDictData("民营企业")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_person_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("中介")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_person_sub_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("本人")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_admit_status"))
|
||||||
|
.thenReturn(List.of(buildDictData("录用")));
|
||||||
|
mocked.when(() -> DictUtils.getDictCache("ccdi_recruit_type"))
|
||||||
|
.thenReturn(List.of(buildDictData("社招", "SOCIAL")));
|
||||||
|
}
|
||||||
|
|
||||||
private SysDictData buildDictData(String label) {
|
private SysDictData buildDictData(String label) {
|
||||||
return buildDictData(label, label);
|
return buildDictData(label, label);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,9 @@
|
|||||||
package com.ruoyi.lsfx.client;
|
package com.ruoyi.lsfx.client;
|
||||||
|
|
||||||
import com.ruoyi.lsfx.domain.response.CreditParseResponse;
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
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;
|
||||||
@@ -8,7 +11,6 @@ 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;
|
||||||
|
|
||||||
@@ -16,32 +18,191 @@ 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;
|
||||||
|
|
||||||
public CreditParseResponse parse(String model, String hType, File file) {
|
@Value("${credit-parse.api.result-url}")
|
||||||
|
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();
|
||||||
log.info("【征信解析】开始调用: fileName={}, model={}, hType={}", file.getName(), model, hType);
|
String actualModel = StringUtils.isBlank(model) ? defaultModel : model;
|
||||||
|
String serialNum = buildSerialNum();
|
||||||
try {
|
try {
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> initiateParams = buildInitiateParams(serialNum, actualModel, remotePath);
|
||||||
params.put("model", model);
|
CreditParseInvokeResponse initiateResponse = request(creditParseUrl, initiateParams, "发起接口");
|
||||||
params.put("hType", hType);
|
requireSuccessfulInitiateResponse(initiateResponse, "征信解析发起接口");
|
||||||
params.put("file", file);
|
|
||||||
|
|
||||||
CreditParseResponse response = httpUtil.uploadFile(creditParseUrl, params, null, CreditParseResponse.class);
|
CreditParseInvokeResponse response = queryResult(serialNum);
|
||||||
|
|
||||||
long elapsed = System.currentTimeMillis() - startTime;
|
long elapsed = System.currentTimeMillis() - startTime;
|
||||||
log.info("【征信解析】调用完成: statusCode={}, cost={}ms",
|
log.info("【征信解析】调用完成: success={}, code={}, businessStatusCode={}, cost={}ms",
|
||||||
response != null ? response.getStatusCode() : null, elapsed);
|
response != null ? response.getSuccess() : null,
|
||||||
|
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("【征信解析】调用失败: fileName={}, model={}, hType={}, error={}",
|
log.error("【征信解析】调用失败: serialNum={}, model={}, remotePath={}, error={}",
|
||||||
file.getName(), model, hType, e.getMessage(), e);
|
serialNum, actualModel, remotePath, 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);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ import jakarta.annotation.Resource;
|
|||||||
import lombok.extern.slf4j.Slf4j;
|
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 org.springframework.util.StringUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@@ -110,7 +111,15 @@ public class LsfxAnalysisClient {
|
|||||||
* 上传文件
|
* 上传文件
|
||||||
*/
|
*/
|
||||||
public UploadFileResponse uploadFile(Integer groupId, File file) {
|
public UploadFileResponse uploadFile(Integer groupId, File file) {
|
||||||
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, file.getName());
|
return uploadFile(groupId, file, file.getName());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 上传文件
|
||||||
|
*/
|
||||||
|
public UploadFileResponse uploadFile(Integer groupId, File file, String uploadFileName) {
|
||||||
|
String multipartFileName = StringUtils.hasText(uploadFileName) ? uploadFileName : file.getName();
|
||||||
|
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, multipartFileName);
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
try {
|
try {
|
||||||
@@ -118,7 +127,7 @@ public class LsfxAnalysisClient {
|
|||||||
|
|
||||||
Map<String, Object> params = new HashMap<>();
|
Map<String, Object> params = new HashMap<>();
|
||||||
params.put("groupId", groupId);
|
params.put("groupId", groupId);
|
||||||
params.put("files", file);
|
params.put("files", HttpUtil.namedFileResource(file, multipartFileName));
|
||||||
|
|
||||||
Map<String, String> headers = new HashMap<>();
|
Map<String, String> headers = new HashMap<>();
|
||||||
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
|
||||||
|
|||||||
@@ -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.CreditParseResponse;
|
import com.ruoyi.lsfx.domain.response.CreditParseInvokeResponse;
|
||||||
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,13 +14,6 @@ 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
|
||||||
@@ -29,56 +22,27 @@ import java.nio.file.StandardCopyOption;
|
|||||||
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("file") MultipartFile file,
|
public AjaxResult parse(@Parameter(description = "征信HTML远程访问地址") @RequestParam("remotePath") String remotePath,
|
||||||
@Parameter(description = "解析模型,默认LXCUSTALL") @RequestParam(required = false) String model,
|
@Parameter(description = "模型编码,默认LXCUSTALL") @RequestParam(required = false) String model) {
|
||||||
@Parameter(description = "主体类型,默认PERSON") @RequestParam(required = false) String hType) {
|
if (StringUtils.isBlank(remotePath)) {
|
||||||
if (file == null || file.isEmpty()) {
|
return AjaxResult.error("征信HTML远程地址不能为空");
|
||||||
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 {
|
||||||
String suffix = lowerCaseName.endsWith(".htm") ? ".htm" : ".html";
|
CreditParseInvokeResponse response = creditParseClient.parse(actualModel, remotePath);
|
||||||
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) {
|
|
||||||
// 忽略临时文件删除失败,避免影响主流程返回
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,17 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
package com.ruoyi.lsfx.domain.response;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
public class CreditParseInvokeResponse {
|
||||||
|
|
||||||
|
private Boolean success;
|
||||||
|
|
||||||
|
private Integer code;
|
||||||
|
|
||||||
|
private CreditParseInvokeData data;
|
||||||
|
}
|
||||||
@@ -0,0 +1,33 @@
|
|||||||
|
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);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
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
|
||||||
@@ -11,5 +12,6 @@ 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;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -10,6 +10,7 @@ import org.springframework.http.*;
|
|||||||
import org.springframework.stereotype.Component;
|
import org.springframework.stereotype.Component;
|
||||||
import org.springframework.util.LinkedMultiValueMap;
|
import org.springframework.util.LinkedMultiValueMap;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
|
import org.springframework.util.StringUtils;
|
||||||
import org.springframework.web.client.RestClientException;
|
import org.springframework.web.client.RestClientException;
|
||||||
import org.springframework.web.client.RestTemplate;
|
import org.springframework.web.client.RestTemplate;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
@@ -31,6 +32,24 @@ public class HttpUtil {
|
|||||||
@Resource
|
@Resource
|
||||||
private ObjectMapper objectMapper;
|
private ObjectMapper objectMapper;
|
||||||
|
|
||||||
|
public static org.springframework.core.io.Resource namedFileResource(File file, String filename) {
|
||||||
|
return new NamedFileSystemResource(file, filename);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class NamedFileSystemResource extends FileSystemResource {
|
||||||
|
private final String filename;
|
||||||
|
|
||||||
|
NamedFileSystemResource(File file, String filename) {
|
||||||
|
super(file);
|
||||||
|
this.filename = StringUtils.hasText(filename) ? filename : file.getName();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getFilename() {
|
||||||
|
return filename;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 发送GET请求(带查询参数和请求头)
|
* 发送GET请求(带查询参数和请求头)
|
||||||
* @param url 请求URL
|
* @param url 请求URL
|
||||||
@@ -187,6 +206,86 @@ 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
|
||||||
@@ -207,6 +306,8 @@ public class HttpUtil {
|
|||||||
if (value instanceof File) {
|
if (value instanceof File) {
|
||||||
File file = (File) value;
|
File file = (File) value;
|
||||||
body.add(key, new FileSystemResource(file));
|
body.add(key, new FileSystemResource(file));
|
||||||
|
} else if (value instanceof org.springframework.core.io.Resource) {
|
||||||
|
body.add(key, value);
|
||||||
} else {
|
} else {
|
||||||
body.add(key, value);
|
body.add(key, value);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,24 +1,34 @@
|
|||||||
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.CreditParseResponse;
|
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 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.mock.web.MockMultipartFile;
|
import org.springframework.test.util.ReflectionTestUtils;
|
||||||
|
|
||||||
import java.io.File;
|
import java.util.ArrayList;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.util.List;
|
||||||
|
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.mockito.ArgumentMatchers.any;
|
import static org.junit.jupiter.api.Assertions.assertThrows;
|
||||||
|
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)
|
||||||
@@ -31,31 +41,21 @@ class CreditParseControllerTest {
|
|||||||
private CreditParseController controller;
|
private CreditParseController controller;
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_shouldRejectEmptyFile() {
|
void parse_shouldRejectBlankRemotePath() {
|
||||||
AjaxResult result = controller.parse(null, null, null);
|
AjaxResult result = controller.parse(null, null);
|
||||||
assertEquals(500, result.get("code"));
|
assertEquals(500, result.get("code"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void parse_shouldRejectNonHtmlFile() {
|
void shouldUseDefaultModelWhenMissing() {
|
||||||
MockMultipartFile file = new MockMultipartFile(
|
CreditParseInvokeResponse response = new CreditParseInvokeResponse();
|
||||||
"file", "credit.pdf", "application/pdf", "x".getBytes(StandardCharsets.UTF_8)
|
response.setSuccess(true);
|
||||||
);
|
response.setCode(10000);
|
||||||
AjaxResult result = controller.parse(file, null, null);
|
|
||||||
assertEquals(500, result.get("code"));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
String remotePath = "http://127.0.0.1:62318/profile/credit-html/a.html";
|
||||||
void shouldUseDefaultModelAndTypeWhenMissing() {
|
when(client.parse(eq("LXCUSTALL"), eq(remotePath))).thenReturn(response);
|
||||||
MockMultipartFile file = new MockMultipartFile(
|
|
||||||
"file", "credit.html", "text/html", "<html/>".getBytes(StandardCharsets.UTF_8)
|
|
||||||
);
|
|
||||||
CreditParseResponse response = new CreditParseResponse();
|
|
||||||
response.setStatusCode("0");
|
|
||||||
|
|
||||||
when(client.parse(eq("LXCUSTALL"), eq("PERSON"), any(File.class))).thenReturn(response);
|
AjaxResult result = controller.parse(remotePath, null);
|
||||||
|
|
||||||
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,14 +63,230 @@ class CreditParseControllerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
void shouldReturnAjaxErrorWhenClientThrows() {
|
void shouldReturnAjaxErrorWhenClientThrows() {
|
||||||
MockMultipartFile file = new MockMultipartFile(
|
when(client.parse(anyString(), anyString()))
|
||||||
"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(file, null, null);
|
AjaxResult result = controller.parse("http://127.0.0.1:62318/profile/credit-html/a.html", 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;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -49,6 +49,12 @@
|
|||||||
<artifactId>easyexcel</artifactId>
|
<artifactId>easyexcel</artifactId>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
|
||||||
|
<!-- pdf导出工具 -->
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.apache.pdfbox</groupId>
|
||||||
|
<artifactId>pdfbox</artifactId>
|
||||||
|
</dependency>
|
||||||
|
|
||||||
<!-- 测试依赖 -->
|
<!-- 测试依赖 -->
|
||||||
<dependency>
|
<dependency>
|
||||||
<groupId>org.springframework.boot</groupId>
|
<groupId>org.springframework.boot</groupId>
|
||||||
|
|||||||
@@ -9,6 +9,7 @@ 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() {
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -71,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(".xls")
|
if (!lowerFileName.endsWith(".xlsx") && !lowerFileName.endsWith(".csv")
|
||||||
&& !lowerFileName.endsWith(".csv") && !lowerFileName.endsWith(".pdf")) {
|
&& !lowerFileName.endsWith(".pdf")) {
|
||||||
return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, Excel 文件");
|
return AjaxResult.error("文件 " + fileName + " 格式不支持, 仅支持 PDF, CSV, XLSX 文件");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -0,0 +1,78 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
@Operation(summary = "查询资金流图谱主体")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult searchSubjects(CcdiFundGraphQueryDTO queryDTO) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
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:query')")
|
||||||
|
public AjaxResult saveManualEdge(@RequestBody CcdiFundGraphManualEdgeSaveDTO saveDTO) {
|
||||||
|
try {
|
||||||
|
CcdiFundGraphEdgeVO edge = fundGraphService.saveManualEdge(saveDTO, SecurityUtils.getUsername());
|
||||||
|
return AjaxResult.success(edge);
|
||||||
|
} catch (IllegalArgumentException e) {
|
||||||
|
return AjaxResult.error(e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -29,6 +29,7 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
|||||||
import org.springframework.web.bind.annotation.GetMapping;
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
import org.springframework.web.bind.annotation.PostMapping;
|
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.RequestMethod;
|
||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@@ -181,4 +182,14 @@ public class CcdiProjectOverviewController extends BaseController {
|
|||||||
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
public void exportRiskDetails(HttpServletResponse response, Long projectId) {
|
||||||
overviewService.exportRiskDetails(response, projectId);
|
overviewService.exportRiskDetails(response, projectId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 导出结果总览报告
|
||||||
|
*/
|
||||||
|
@RequestMapping(value = "/report/export", method = { RequestMethod.GET, RequestMethod.POST })
|
||||||
|
@Operation(summary = "导出结果总览报告")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public void exportOverviewReport(HttpServletResponse response, Long projectId) {
|
||||||
|
overviewService.exportOverviewReport(response, projectId);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,55 @@
|
|||||||
|
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.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;
|
||||||
|
|
||||||
|
@GetMapping("/search")
|
||||||
|
@Operation(summary = "查询关系图谱主体")
|
||||||
|
@PreAuthorize("@ss.hasPermi('ccdi:project:query')")
|
||||||
|
public AjaxResult searchSubjects(CcdiRelationGraphQueryDTO queryDTO) {
|
||||||
|
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) {
|
||||||
|
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) {
|
||||||
|
CcdiRelationGraphSuspectedEnterpriseVO result = relationGraphService.getSuspectedEnterprises(queryDTO);
|
||||||
|
return AjaxResult.success(result);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -32,7 +32,7 @@ public class CcdiProject implements Serializable {
|
|||||||
/** 配置方式:default-全局默认,custom-自定义 */
|
/** 配置方式:default-全局默认,custom-自定义 */
|
||||||
private String configType;
|
private String configType;
|
||||||
|
|
||||||
/** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中 */
|
/** 项目状态:0-进行中,1-已完成,2-已归档,3-打标中,4-打标失败 */
|
||||||
private String status;
|
private String status;
|
||||||
|
|
||||||
/** 是否归档:0-未归档,1-已归档 */
|
/** 是否归档:0-未归档,1-已归档 */
|
||||||
|
|||||||
@@ -0,0 +1,42 @@
|
|||||||
|
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;
|
||||||
|
}
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user