1 Commits

Author SHA1 Message Date
wkc
4ce4a717db 文件夹整理 2026-02-09 14:32:35 +08:00
658 changed files with 1144 additions and 125469 deletions

View File

@@ -99,20 +99,7 @@
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" status --short)",
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" add \"doc/plans/2025-02-08-intermediary-import-history-cleanup.md\" \"doc/reports/2026-02-08-intermediary-import-history-cleanup-completion.md\")",
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" commit -m \"$\\(cat <<''EOF''\ndocs: 添加中介导入历史清除功能完成报告\n\n- 添加功能设计文档\n- 添加功能完成总结报告\n- 包含代码审查结果和后续优化建议\n\nCo-Authored-By: Claude Sonnet 4.5 <noreply@anthropic.com>\nEOF\n\\)\")",
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" log --oneline -5)",
"Bash([:*)",
"Bash([ -d modules ])",
"Bash([ -d test-data ])",
"Skill(generate-test-data)",
"Bash(python3:*)",
"Skill(mcp-mysql-correct-db)",
"Bash(git diff:*)",
"Bash(git pull:*)",
"Bash(git merge:*)",
"mcp__chrome-devtools-mcp__take_snapshot",
"mcp__chrome-devtools-mcp__fill",
"mcp__chrome-devtools-mcp__click",
"mcp__chrome-devtools-mcp__take_screenshot"
"Bash(git -C \"D:\\\\ccdi\\\\ccdi\" log --oneline -5)"
]
},
"enabledMcpjsonServers": [

4
.gitignore vendored
View File

@@ -56,7 +56,3 @@ test/
######################################################################
# Excel Temporary Files
doc/test-data/**/~$*
######################################################################
# Database Configuration
db_config.conf

View File

@@ -1,18 +1,3 @@
{
"mcpServers": {
"mysql": {
"args": [
"-y",
"@fhuang/mcp-mysql-server"
],
"command": "npx",
"env": {
"MYSQL_DATABASE": "ccdi",
"MYSQL_HOST": "116.62.17.81",
"MYSQL_PASSWORD": "Kfcx@1234",
"MYSQL_PORT": "3306",
"MYSQL_USER": "root"
}
}
}
"mcpServers": {}
}

697
CLAUDE.md
View File

@@ -1,528 +1,327 @@
# CLAUDE.md
## 分析
- 在进行需求分析类型的任务时,自动开启深度思考模式,输入 “think more”、“think a lot”、“think harder” 或 “think longer” 触发更深层的思考
- 在进行需求分析与分解任务时,按照不同的模块分为不同的文件,创建模块名的文件夹并将对应文件保存在文件夹中,然后对模块的功能文件进行继续分解
- 在使用/openspec:proposal时自动开启深度思考模式输入 “think more”、“think a lot”、“think harder” 或 “think longer” 触发更深层的思考
- 在执行/openspec:apply后使用code-simplifier 进行代码精简
- 在分析生成需求文档时每次都需要在doc目录下新建文件夹并以需求内容为命名
## Communication
- 永远使用简体中文进行思考和对话
## Documentation
- 编写 .md 文档时,也要用中文
- 所有生成的文档都放在项目根目录下的doc文件中。
## 数据库规范
- 新建表时,需要加上项目英文名首字母集合
## Coding
### Java Code Style
- 新建模块命名方式为项目英文名首字母集合+主要功能
- 新的功能代码与若依框架自带的代码分离新建模块controller层也要放在新建模块中
- 使用 `@Data` 注解保证代码的简洁
- 尽量使用 MyBatis Plus 进行 CRUD 操作(版本 3.5.10Spring Boot 3 适配版)
- 服务层中的使用@Resource注释,替代@Autowired
- 实体类不继承BaseEntity单独添加审计字段
- 完成后端代码controller层代码生成测试后在项目文件目录下生成API文档
- 接口传参需要使用单独的DTO不可以与entity混用
- 需要单独的VO类不可以与entity混用
- 审计字段通过添加注释的方式实现自动插入
- 简单的crud操作通过mybatis plus的方法实现复杂的操作通过xml中写sql和mapper映射实现
- 控制层所有接口需要正确的添加注释确保在swagger-ui中正确展示。控制层中任何接口发生变动及时同步到doc中的接口文档中
- 控制层分页接口使用mybatis plus page不要使用若依框架的分页
### 前端代码
- 在添加页面和组件后,注意与数据库中菜单表进行联动修改
- 前端组件代码需要组件化,复杂的组件需要进行拆分为单独的文件
## 运行
- 使用mcp:ccdi_intermediary_blacklist进行数据库相关操作
- 不要在命令行中启动后端进行测试
- 测试方式为生成可执行的测试脚本
- 测试脚本在运行完成后需要保存所有接口输出并生成测试用例报告
- /login/test接口可以传入username和password获取token用于测试验证接口的功能。
用于测试的账号username: admin password admin123
- swagger-ui的地址为/swagger-ui/index.html
- 在向doc文件夹添加文件时需要分门别类添加根据
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
## 快速参考
## Project Overview
**启动项目:**
- 后端: `mvn spring-boot:run` 或运行 `ry.bat`
- 前端: `cd ruoyi-ui && npm run dev`
This is a **discipline preliminary check system** built on the **RuoYi (若依) v3.9.1** rapid development framework. It is an enterprise-grade management system using a front-end/back-end separated architecture.
**访问地址:**
- 前端: http://localhost:80
- 后端: http://localhost:8080
- Swagger: http://localhost:8080/swagger-ui/index.html
- Druid 监控: http://localhost:8080/druid/ (ruoyi/123456)
### Technology Stack
**测试账号:**
- 用户名: `admin`
- 密码: `admin123`
**Backend:**
- Spring Boot 3.5.8
- Spring Security + JWT (authentication)
- MyBatis 3.0.5 (ORM)
- MySQL 8.2.0
- Redis (caching)
- Quartz 2.5.2 (scheduled tasks)
- SpringDoc 2.8.14 (API documentation)
- Java 17
**获取 Token:**
```bash
POST http://localhost:8080/login/test?username=admin&password=admin123
```
**Frontend:**
- Vue 2.6.12
- Element UI 2.15.14
- Vuex 3.6.0 (state management)
- Vue Router 3.4.9
- Axios 0.28.1
---
## Common Commands
## 项目概述
**纪检初核系统** - 基于 **若依管理系统 v3.9.1** 构建的企业级前后端分离管理系统,用于员工异常行为风险识别。
### 技术栈版本
| 后端技术 | 版本 | 前端技术 | 版本 |
|-----------------------------|--------|------------|---------|
| Spring Boot | 3.5.8 | Vue.js | 2.6.12 |
| Java | 17 | 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)
### Backend (Maven)
```bash
# 编译项目
# Compile the project
mvn clean compile
# 运行应用 (开发环境)
# Run the application (development)
mvn spring-boot:run
# 打包部署
# Package for deployment
mvn clean package
# Windows 启动
ry.bat
# Linux/Mac 启动
./ry.sh start
# Run using startup scripts
./ry.bat # Windows
./ry.sh start # Linux/Mac
```
### 前端 (npm)
### Frontend (npm)
```bash
cd ruoyi-ui
# 安装依赖 (推荐使用国内镜像)
npm install --registry=https://registry.npmmirror.com
# Install dependencies
npm install
# 开发服务器 (端口 80)
# Development server (runs on port 80 by default)
npm run dev
# 生产构建
# Production build
npm run build:prod
# 预览生产构建
# Staging build
npm run build:stage
# Preview production build
npm run preview
```
### 数据库初始化
### Database Initialization
```bash
# 初始化若依框架基础表
# Main database schema
mysql -u root -p < sql/ry_20250522.sql
# 初始化定时任务表
# Quartz scheduler tables
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`
## Project Architecture
---
## 模块架构
### Module Structure
```
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/ # 代码生成器
├── ruoyi-info-collection/ # 【核心业务模块】信息采集
├── ruoyi-ui/ # 前端 Vue 应用
├── sql/ # 数据库脚本
── bin/ # 启动脚本
└── doc/ # 项目文档
discipline-prelim-check/
├── ruoyi-admin/ # Main application entry point
├── ruoyi-framework/ # Core framework (Security, config, filters)
├── ruoyi-system/ # System management (Users, Roles, Menus, Depts)
├── ruoyi-common/ # Common utilities (annotations, utils, constants)
├── ruoyi-quartz/ # Scheduled task management
├── ruoyi-generator/ # Code generator (CRUD scaffolding)
├── ruoyi-ui/ # Frontend Vue application
├── sql/ # Database scripts
├── bin/ # Startup scripts
── openspec/ # OpenSpec specification workflow
```
### 模块依赖关系
### Backend Architecture: MVC + Modular Design
The backend follows a standard MVC pattern with modular separation:
```
ruoyi-admin (启动模块)
├── ruoyi-framework (核心安全配置)
├── ruoyi-system (系统核心业务)
├── ruoyi-common (共享工具)
├── ruoyi-quartz (定时任务)
├── ruoyi-generator (代码生成)
└── ruoyi-info-collection (信息采集模块)
└── 依赖 ruoyi-common
Controller Layer (ruoyi-admin/web/controller/)
├── common/ # Common controllers (captcha, file upload)
├── monitor/ # Monitoring controllers (cache, server, logs)
├── system/ # System management (users, roles, menus)
└── tool/ # Tools (code generator, swagger)
Service Layer (ruoyi-system/service/)
├── ISysUserService.java
├── ISysRoleService.java
└── ...
Mapper Layer (ruoyi-system/mapper/)
├── SysUserMapper.java
├── SysRoleMapper.java
└── ...
Domain Layer (ruoyi-system/domain/)
├── SysUser.java # Entity
├── vo/ # Value objects
└── ...
```
**添加新业务模块:**
1. 在根目录 `pom.xml``<modules>` 中添加新模块
2. 在新模块的 `pom.xml` 中添加对 `ruoyi-common` 的依赖
3.`ruoyi-admin/pom.xml` 中添加对新模块的依赖
4. 在新模块中按照分层规范创建 controller/service/mapper/domain 包
### ruoyi-info-collection 业务模块 (核心)
自定义业务模块,包含以下核心功能:
| 功能 | Controller | 实体类 |
|----------|---------------------------------------|-----------------------------|
| 员工基础信息 | CcdiBaseStaffController | CcdiBaseStaff |
| 中介黑名单 | CcdiIntermediaryController | CcdiBizIntermediary |
| 员工家庭关系 | CcdiStaffFmyRelationController | CcdiStaffFmyRelation |
| 员工企业关系 | CcdiStaffEnterpriseRelationController | CcdiStaffEnterpriseRelation |
| 信贷客户家庭关系 | CcdiCustFmyRelationController | CcdiCustFmyRelation |
| 信贷客户企业关系 | CcdiCustEnterpriseRelationController | CcdiCustEnterpriseRelation |
| 员工调动记录 | CcdiStaffTransferController | CcdiStaffTransfer |
| 员工招聘记录 | CcdiStaffRecruitmentController | CcdiStaffRecruitment |
| 采购交易 | CcdiPurchaseTransactionController | CcdiPurchaseTransaction |
**分层结构:**
- Controller: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/`
- Service: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/service/`
- Mapper: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/mapper/`
- Domain: `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/domain/`
- dto/: 数据传输对象
- vo/: 视图对象
- excel/: Excel导入导出实体
- XML映射: `ruoyi-info-collection/src/main/resources/mapper/info/collection/`
---
## 后端开发规范
### 通用规范
- **新模块命名**: 项目英文名首字母集合 + 主要功能 (如 `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);
```
---
## 前端开发规范
### 目录结构
### Frontend Architecture: Vue SPA
```
ruoyi-ui/src/
├── api/ # API 请求定义 (与后端 Controller 对应)
├── views/ # 页面组件 (按功能模块组织)
├── ccdiBaseStaff/
│ ├── ccdiIntermediary/
│ └── ...
├── components/ # 可复用组件 (复杂组件需拆分)
├── router/ # 路由配置
── store/ # Vuex 状态管理
├── api/ # API request definitions
├── assets/ # Static resources (images, styles)
├── components/ # Reusable components
├── layout/ # Main layout (Sidebar, Navbar, TagsView)
├── router/ # Vue Router configuration
├── store/ # Vuex state management
├── utils/ # Utility functions
── views/ # Page components organized by feature
│ ├── dashboard/
│ ├── monitor/
│ ├── system/
│ └── tool/
└── permission.js # Permission directives
```
### API 调用示例
### Module Dependencies
```
ruoyi-admin (startup module)
↓ depends on
ruoyi-framework (core security & config)
ruoyi-system (system core business)
ruoyi-common (shared utilities)
ruoyi-quartz (scheduled tasks)
ruoyi-generator (code generation)
```
## Key Development Patterns
### Code Generation Workflow
RuoYi provides a powerful code generator for rapid CRUD development:
1. **Create database table** - Design your table schema
2. **Import table** - Use System Tools → Code Generation → Import
3. **Configure** - Edit table info, generate info (module, function name, etc.)
4. **Generate code** - Download the generated zip
5. **Copy files** - Extract to appropriate directories:
- Backend: `ruoyi-admin/web/controller/`, service, mapper files
- Frontend: `ruoyi-ui/src/views/`, `ruoyi-ui/src/api/`
### Permission System
The permission system uses **Role-Menu-Button** hierarchy:
- **Menus**: Define navigation items and route permissions
- **Roles**: Assign menu permissions to roles
- **Users**: Assign roles to users
- **Data Permissions**: Control data scope (all, custom, department, etc.)
Permission keys in code use format: `system:user:edit`, `system:user:remove`, etc.
### API Response Format
All API responses use `AjaxResult` wrapper:
```java
// Success
AjaxResult.success("操作成功", data);
// Error
AjaxResult.error("操作失败");
// Custom
AjaxResult.put("key", value);
```
### Frontend API Calls
API calls are defined in `ruoyi-ui/src/api/`:
```javascript
import request from '@/utils/request'
export function listStaff(query) {
export function listUser(query) {
return request({
url: '/ccdi/baseStaff/list',
url: '/system/user/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));
export function addUser(data) {
return request({
url: '/system/user',
method: 'post',
data: data
})
}
```
**导入流程:**
1. 前端上传 Excel 文件
2. 后端异步处理,返回 taskId
3. 前端轮询 `/import/status/{taskId}` 获取导入进度
4. 导入完成后,可获取成功/失败数据统计
## OpenSpec Workflow
**导入结果处理:**
- 只返回导入失败的数据(含失败原因)
- 成功数据不返回,减少响应体积
- 支持批量插入,提高性能
This project uses **OpenSpec** for specification-driven development. Always reference `openspec/AGENTS.md` when:
### EasyExcel 字典下拉框
- Planning or proposing new features
- Making breaking changes
- Modifying architecture
- Handling ambiguous requirements
导入模板支持字典下拉框配置,提升数据录入准确性。使用 `DictDropdownWriteHandler` 实现。
### 权限控制
基于 Spring Security + JWT 的角色菜单权限系统:
- 权限格式: `system:user:edit`, `ccdi:staff:list`
- 数据权限: 支持全部、自定义、部门等范围
---
## 测试与验证
### 测试账号
- **用户名**: `admin`
- **密码**: `admin123`
### 登录获取 Token
### Key OpenSpec Commands
```bash
# 登录接口
POST /login/test?username=admin&password=admin123
# List active changes
openspec list
# List all specifications
openspec list --specs
# View details
openspec show [change-id or spec-id]
# Validate changes
openspec validate [change-id] --strict --no-interactive
# Archive completed changes
openspec archive <change-id>
```
### API 文档
### When to Create Proposals
- **Swagger UI**: `/swagger-ui/index.html`
- **API Docs**: `/v3/api-docs`
**Create proposal for:**
- New features or capabilities
- Breaking changes (API, schema)
- Architecture changes
- Performance optimizations that change behavior
### 测试规范
**Skip proposal for:**
- Bug fixes (restoring intended behavior)
- Typos, formatting, comments
- Non-breaking dependency updates
- Configuration changes
- 不在命令行启动后端进行测试
- 生成可执行的测试脚本进行验证
- 测试完成后保存接口输出并生成测试用例报告
## Configuration Notes
### 开发调试技巧
- **Default Admin**: `admin/admin123`
- **Backend Port**: 8080
- **Frontend Dev Port**: 80
- **API Base Path**: Configured in `ruoyi-ui/vue.config.js` proxy
- **Database Config**: `ruoyi-admin/src/main/resources/application.yml`
**使用 Swagger 测试接口:**
1. 访问 `/swagger-ui/index.html`
2. 点击接口展开详情
3. 点击 "Try it out" 进行测试
4. 填写参数后点击 "Execute" 执行
## Important File Locations
**查看 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
### 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 | `ruoyi-info-collection/src/main/java/com/ruoyi/info/collection/controller/` |
| 业务 Mapper XML | `ruoyi-info-collection/src/main/resources/mapper/info/collection/` |
| 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. 检查必填字段是否为空
---
## 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>
```
| Purpose | Location |
|---------|----------|
| Main application entry | [ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java](ruoyi-admin/src/main/java/com/ruoyi/RuoYiApplication.java) |
| Security configuration | [ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java](ruoyi-framework/src/main/java/com/ruoyi/framework/config/SecurityConfig.java) |
| Database config | [ruoyi-admin/src/main/resources/application.yml](ruoyi-admin/src/main/resources/application.yml) |
| MyBatis mappers | [ruoyi-system/src/main/resources/mapper/system/](ruoyi-system/src/main/resources/mapper/system/) |
| Vue router | [ruoyi-ui/src/router/index.js](ruoyi-ui/src/router/index.js) |
| Vuex store | [ruoyi-ui/src/store/](ruoyi-ui/src/store/) |

View File

@@ -1,205 +0,0 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.*;
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
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 com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
* 员工信息Controller
*
* @author ruoyi
* @date 2026-01-28
*/
@Tag(name = "员工信息管理")
@RestController
@RequestMapping("/ccdi/baseStaff")
public class CcdiBaseStaffController extends BaseController {
@Resource
private ICcdiBaseStaffService baseStaffService;
@Resource
private ICcdiBaseStaffImportService importAsyncService;
/**
* 查询员工列表
*/
@Operation(summary = "查询员工列表")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiBaseStaffQueryDTO queryDTO) {
// 使用MyBatis Plus分页
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiBaseStaffVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiBaseStaffVO> result = baseStaffService.selectBaseStaffPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 查询员工下拉列表
*/
@Operation(summary = "查询员工下拉列表")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:list')")
@GetMapping("/options")
public AjaxResult getStaffOptions(@RequestParam(required = false) String query) {
List<CcdiBaseStaffOptionVO> list = baseStaffService.selectStaffOptions(query);
return success(list);
}
/**
* 导出员工列表
*/
@Operation(summary = "导出员工列表")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:export')")
@Log(title = "员工信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiBaseStaffQueryDTO queryDTO) {
List<CcdiBaseStaffExcel> list = baseStaffService.selectBaseStaffListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiBaseStaffExcel.class, "员工信息");
}
/**
* 获取员工详细信息
*/
@Operation(summary = "获取员工详细信息")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:query')")
@GetMapping(value = "/{staffId}")
public AjaxResult getInfo(@PathVariable Long staffId) {
return success(baseStaffService.selectBaseStaffById(staffId));
}
/**
* 新增员工
*/
@Operation(summary = "新增员工")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:add')")
@Log(title = "员工信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiBaseStaffAddDTO addDTO) {
return toAjax(baseStaffService.insertBaseStaff(addDTO));
}
/**
* 修改员工
*/
@Operation(summary = "修改员工")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:edit')")
@Log(title = "员工信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiBaseStaffEditDTO editDTO) {
return toAjax(baseStaffService.updateBaseStaff(editDTO));
}
/**
* 删除员工
*/
@Operation(summary = "删除员工")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:remove')")
@Log(title = "员工信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{staffIds}")
public AjaxResult remove(@PathVariable Long[] staffIds) {
return toAjax(baseStaffService.deleteBaseStaffByIds(staffIds));
}
/**
* 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiBaseStaffExcel.class, "员工信息");
}
/**
* 导入员工信息(异步)
*/
@Operation(summary = "导入员工信息")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')")
@Log(title = "员工信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception {
List<CcdiBaseStaffExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiBaseStaffExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = baseStaffService.importBaseStaff(list, updateSupport);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询员工导入状态")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
try {
ImportStatusVO status = importAsyncService.getImportStatus(taskId);
return success(status);
} catch (Exception e) {
return error(e.getMessage());
}
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@PreAuthorize("@ss.hasPermi('ccdi:baseStaff:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<ImportFailureVO> failures = importAsyncService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
// 检查 fromIndex 是否超出范围
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<ImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}

View File

@@ -1,200 +0,0 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
import com.ruoyi.info.collection.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
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 com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
* 信贷客户实体关联信息Controller
*
* @author ruoyi
* @date 2026-02-12
*/
@Tag(name = "信贷客户实体关联信息管理")
@RestController
@RequestMapping("/ccdi/custEnterpriseRelation")
public class CcdiCustEnterpriseRelationController extends BaseController {
@Resource
private ICcdiCustEnterpriseRelationService relationService;
@Resource
private ICcdiCustEnterpriseRelationImportService relationImportService;
/**
* 查询信贷客户实体关联列表
*/
@Operation(summary = "查询信贷客户实体关联列表")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
// 使用MyBatis Plus分页
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiCustEnterpriseRelationVO> result = relationService.selectRelationPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 导出信贷客户实体关联列表
*/
@Operation(summary = "导出信贷客户实体关联列表")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:export')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiCustEnterpriseRelationQueryDTO queryDTO) {
List<CcdiCustEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息");
}
/**
* 获取信贷客户实体关联详细信息
*/
@Operation(summary = "获取信贷客户实体关联详细信息")
@Parameter(name = "id", description = "主键ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(relationService.selectRelationById(id));
}
/**
* 新增信贷客户实体关联
*/
@Operation(summary = "新增信贷客户实体关联")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:add')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiCustEnterpriseRelationAddDTO addDTO) {
return toAjax(relationService.insertRelation(addDTO));
}
/**
* 修改信贷客户实体关联
*/
@Operation(summary = "修改信贷客户实体关联")
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:edit')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiCustEnterpriseRelationEditDTO editDTO) {
return toAjax(relationService.updateRelation(editDTO));
}
/**
* 删除信贷客户实体关联
*/
@Operation(summary = "删除信贷客户实体关联")
@Parameter(name = "ids", description = "主键ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:remove')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(relationService.deleteRelationByIds(ids));
}
/**
* 下载导入模板
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiCustEnterpriseRelationExcel.class, "信贷客户实体关联信息");
}
/**
* 异步导入信贷客户实体关联
*/
@Operation(summary = "异步导入信贷客户实体关联")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@Log(title = "信贷客户实体关联信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiCustEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiCustEnterpriseRelationExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = relationService.importRelation(list);
// 立即返回
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询导入状态")
@Parameter(name = "taskId", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
ImportStatusVO statusVO = relationImportService.getImportStatus(taskId);
return success(statusVO);
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@Parameter(name = "taskId", description = "任务ID", required = true)
@Parameter(name = "pageNum", description = "页码", required = false)
@Parameter(name = "pageSize", description = "每页条数", required = false)
@PreAuthorize("@ss.hasPermi('ccdi:custEnterpriseRelation:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<CustEnterpriseRelationImportFailureVO> failures = relationImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
// 检查 fromIndex 是否超出范围
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<CustEnterpriseRelationImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}

View File

@@ -1,193 +0,0 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustFmyRelationVO;
import com.ruoyi.info.collection.domain.vo.CustFmyRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.service.ICcdiCustFmyRelationImportService;
import com.ruoyi.info.collection.service.ICcdiCustFmyRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
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 com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
/**
* 信贷客户家庭关系Controller
*
* @author ruoyi
* @date 2026-02-11
*/
@Tag(name = "信贷客户家庭关系管理")
@RestController
@RequestMapping("/ccdi/custFmyRelation")
public class CcdiCustFmyRelationController extends BaseController {
@Resource
private ICcdiCustFmyRelationService relationService;
@Resource
private ICcdiCustFmyRelationImportService relationImportService;
/**
* 查询信贷客户家庭关系列表
*/
@Operation(summary = "查询信贷客户家庭关系列表")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')")
@GetMapping("/list")
public TableDataInfo list(CcdiCustFmyRelationQueryDTO query) {
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiCustFmyRelationVO> page = relationService.selectRelationPage(
query, pageDomain.getPageNum(), pageDomain.getPageSize());
return getDataTable(page.getRecords(), page.getTotal());
}
/**
* 根据ID查询信贷客户家庭关系详情
*/
@Operation(summary = "查询信贷客户家庭关系详情")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')")
@GetMapping("/{id}")
public AjaxResult getInfo(@PathVariable("id") Long id) {
CcdiCustFmyRelationVO relation = relationService.selectRelationById(id);
return success(relation);
}
/**
* 新增信贷客户家庭关系
*/
@Operation(summary = "新增信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:add')")
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiCustFmyRelationAddDTO addDTO) {
return toAjax(relationService.insertRelation(addDTO));
}
/**
* 修改信贷客户家庭关系
*/
@Operation(summary = "修改信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:edit')")
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiCustFmyRelationEditDTO editDTO) {
return toAjax(relationService.updateRelation(editDTO));
}
/**
* 删除信贷客户家庭关系
*/
@Operation(summary = "删除信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:remove')")
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(relationService.deleteRelationByIds(ids));
}
/**
* 导出信贷客户家庭关系
*/
@Operation(summary = "导出信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:export')")
@Log(title = "信贷客户家庭关系", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiCustFmyRelationQueryDTO query) {
relationService.exportRelations(query, response);
}
/**
* 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiCustFmyRelationExcel.class, "信贷客户家庭关系");
}
/**
* 异步导入信贷客户家庭关系
*/
@Operation(summary = "异步导入信贷客户家庭关系")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:import')")
@Log(title = "信贷客户家庭关系", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@RequestParam("file") MultipartFile file) throws IOException {
List<CcdiCustFmyRelationExcel> excels = EasyExcelUtil.importExcel(
file.getInputStream(),
CcdiCustFmyRelationExcel.class
);
if (excels == null || excels.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = relationService.importRelations(excels);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询导入状态")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable("taskId") String taskId) {
ImportStatusVO statusVO = relationImportService.getImportStatus(taskId);
return success(statusVO);
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@PreAuthorize("@ss.hasPermi('ccdi:custFmyRelation:query')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable("taskId") String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<CustFmyRelationImportFailureVO> failures = relationImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
// 检查 fromIndex 是否超出范围
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<CustFmyRelationImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}

View File

@@ -1,201 +0,0 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
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 com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
* 员工实体关系信息Controller
*
* @author ruoyi
* @date 2026-02-09
*/
@Tag(name = "员工实体关系信息管理")
@RestController
@RequestMapping("/ccdi/staffEnterpriseRelation")
public class CcdiStaffEnterpriseRelationController extends BaseController {
@Resource
private ICcdiStaffEnterpriseRelationService relationService;
@Resource
private ICcdiStaffEnterpriseRelationImportService relationImportService;
/**
* 查询员工实体关系列表
*/
@Operation(summary = "查询员工实体关系列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
// 使用MyBatis Plus分页
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiStaffEnterpriseRelationVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiStaffEnterpriseRelationVO> result = relationService.selectRelationPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 导出员工实体关系列表
*/
@Operation(summary = "导出员工实体关系列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:export')")
@Log(title = "员工实体关系信息", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
List<CcdiStaffEnterpriseRelationExcel> list = relationService.selectRelationListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息");
}
/**
* 获取员工实体关系详细信息
*/
@Operation(summary = "获取员工实体关系详细信息")
@Parameter(name = "id", description = "主键ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(relationService.selectRelationById(id));
}
/**
* 新增员工实体关系
*/
@Operation(summary = "新增员工实体关系")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:add')")
@Log(title = "员工实体关系信息", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiStaffEnterpriseRelationAddDTO addDTO) {
return toAjax(relationService.insertRelation(addDTO));
}
/**
* 修改员工实体关系
*/
@Operation(summary = "修改员工实体关系")
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:edit')")
@Log(title = "员工实体关系信息", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiStaffEnterpriseRelationEditDTO editDTO) {
return toAjax(relationService.updateRelation(editDTO));
}
/**
* 删除员工实体关系
*/
@Operation(summary = "删除员工实体关系")
@Parameter(name = "ids", description = "主键ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:remove')")
@Log(title = "员工实体关系信息", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(relationService.deleteRelationByIds(ids));
}
/**
* 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffEnterpriseRelationExcel.class, "员工实体关系信息");
}
/**
* 异步导入员工实体关系
*/
@Operation(summary = "异步导入员工实体关系")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')")
@Log(title = "员工实体关系信息", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffEnterpriseRelationExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffEnterpriseRelationExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = relationService.importRelation(list);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询导入状态")
@Parameter(name = "taskId", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
ImportStatusVO statusVO = relationImportService.getImportStatus(taskId);
return success(statusVO);
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@Parameter(name = "taskId", description = "任务ID", required = true)
@Parameter(name = "pageNum", description = "页码", required = false)
@Parameter(name = "pageSize", description = "每页条数", required = false)
@PreAuthorize("@ss.hasPermi('ccdi:staffEnterpriseRelation:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<StaffEnterpriseRelationImportFailureVO> failures = relationImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
// 检查 fromIndex 是否超出范围
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<StaffEnterpriseRelationImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}

View File

@@ -1,201 +0,0 @@
package com.ruoyi.info.collection.controller;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffTransferExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffTransferVO;
import com.ruoyi.info.collection.domain.vo.ImportResultVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffTransferImportFailureVO;
import com.ruoyi.info.collection.service.ICcdiStaffTransferImportService;
import com.ruoyi.info.collection.service.ICcdiStaffTransferService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.annotation.Log;
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 com.ruoyi.common.enums.BusinessType;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.validation.annotation.Validated;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import java.util.ArrayList;
import java.util.List;
/**
* 员工调动记录Controller
*
* @author ruoyi
* @date 2026-02-10
*/
@Tag(name = "员工调动记录管理")
@RestController
@RequestMapping("/ccdi/staffTransfer")
public class CcdiStaffTransferController extends BaseController {
@Resource
private ICcdiStaffTransferService transferService;
@Resource
private ICcdiStaffTransferImportService transferImportService;
/**
* 查询员工调动记录列表
*/
@Operation(summary = "查询员工调动记录列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:list')")
@GetMapping("/list")
public TableDataInfo list(CcdiStaffTransferQueryDTO queryDTO) {
// 使用MyBatis Plus分页
PageDomain pageDomain = TableSupport.buildPageRequest();
Page<CcdiStaffTransferVO> page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize());
Page<CcdiStaffTransferVO> result = transferService.selectTransferPage(page, queryDTO);
return getDataTable(result.getRecords(), result.getTotal());
}
/**
* 导出员工调动记录列表
*/
@Operation(summary = "导出员工调动记录列表")
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:export')")
@Log(title = "员工调动记录", businessType = BusinessType.EXPORT)
@PostMapping("/export")
public void export(HttpServletResponse response, CcdiStaffTransferQueryDTO queryDTO) {
List<CcdiStaffTransferExcel> list = transferService.selectTransferListForExport(queryDTO);
EasyExcelUtil.exportExcel(response, list, CcdiStaffTransferExcel.class, "员工调动记录信息");
}
/**
* 获取员工调动记录详细信息
*/
@Operation(summary = "获取员工调动记录详细信息")
@Parameter(name = "id", description = "主键ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:query')")
@GetMapping(value = "/{id}")
public AjaxResult getInfo(@PathVariable Long id) {
return success(transferService.selectTransferById(id));
}
/**
* 新增员工调动记录
*/
@Operation(summary = "新增员工调动记录")
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:add')")
@Log(title = "员工调动记录", businessType = BusinessType.INSERT)
@PostMapping
public AjaxResult add(@Validated @RequestBody CcdiStaffTransferAddDTO addDTO) {
return toAjax(transferService.insertTransfer(addDTO));
}
/**
* 修改员工调动记录
*/
@Operation(summary = "修改员工调动记录")
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:edit')")
@Log(title = "员工调动记录", businessType = BusinessType.UPDATE)
@PutMapping
public AjaxResult edit(@Validated @RequestBody CcdiStaffTransferEditDTO editDTO) {
return toAjax(transferService.updateTransfer(editDTO));
}
/**
* 删除员工调动记录
*/
@Operation(summary = "删除员工调动记录")
@Parameter(name = "ids", description = "主键ID数组", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:remove')")
@Log(title = "员工调动记录", businessType = BusinessType.DELETE)
@DeleteMapping("/{ids}")
public AjaxResult remove(@PathVariable Long[] ids) {
return toAjax(transferService.deleteTransferByIds(ids));
}
/**
* 下载带字典下拉框的导入模板
* 使用@DictDropdown注解自动添加下拉框
*/
@Operation(summary = "下载导入模板")
@PostMapping("/importTemplate")
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiStaffTransferExcel.class, "员工调动记录信息");
}
/**
* 异步导入员工调动记录
*/
@Operation(summary = "异步导入员工调动记录")
@Parameter(name = "file", description = "导入文件", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:import')")
@Log(title = "员工调动记录", businessType = BusinessType.IMPORT)
@PostMapping("/importData")
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
List<CcdiStaffTransferExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffTransferExcel.class);
if (list == null || list.isEmpty()) {
return error("至少需要一条数据");
}
// 提交异步任务
String taskId = transferService.importTransfer(list);
// 立即返回,不等待后台任务完成
ImportResultVO result = new ImportResultVO();
result.setTaskId(taskId);
result.setStatus("PROCESSING");
result.setMessage("导入任务已提交,正在后台处理");
return AjaxResult.success("导入任务已提交,正在后台处理", result);
}
/**
* 查询导入状态
*/
@Operation(summary = "查询导入状态")
@Parameter(name = "taskId", description = "任务ID", required = true)
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:import')")
@GetMapping("/importStatus/{taskId}")
public AjaxResult getImportStatus(@PathVariable String taskId) {
ImportStatusVO statusVO = transferImportService.getImportStatus(taskId);
return success(statusVO);
}
/**
* 查询导入失败记录
*/
@Operation(summary = "查询导入失败记录")
@Parameter(name = "taskId", description = "任务ID", required = true)
@Parameter(name = "pageNum", description = "页码", required = false)
@Parameter(name = "pageSize", description = "每页条数", required = false)
@PreAuthorize("@ss.hasPermi('ccdi:staffTransfer:import')")
@GetMapping("/importFailures/{taskId}")
public TableDataInfo getImportFailures(
@PathVariable String taskId,
@RequestParam(defaultValue = "1") Integer pageNum,
@RequestParam(defaultValue = "10") Integer pageSize) {
List<StaffTransferImportFailureVO> failures = transferImportService.getImportFailures(taskId);
// 手动分页
int fromIndex = (pageNum - 1) * pageSize;
int toIndex = Math.min(fromIndex + pageSize, failures.size());
// 检查 fromIndex 是否超出范围
if (fromIndex >= failures.size()) {
return getDataTable(new ArrayList<>(), failures.size());
}
List<StaffTransferImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
return getDataTable(pageData, failures.size());
}
}

View File

@@ -1,60 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工信息对象 ccdi_base_staff
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
@TableName("ccdi_base_staff")
public class CcdiBaseStaff implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
@TableId(type = IdType.INPUT)
private Long staffId;
/** 姓名 */
private String name;
/** 所属部门ID */
private Long deptId;
/** 身份证号 */
private String idCard;
/** 电话 */
private String phone;
/** 入职时间 */
private Date hireDate;
/** 状态 */
private String status;
/** 创建者 */
@TableField(fill = FieldFill.INSERT)
private String createBy;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新者 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updateBy;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
}

View File

@@ -1,93 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户实体关联信息对象 ccdi_cust_enterprise_relation
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@TableName("ccdi_cust_enterprise_relation")
@Schema(description = "信贷客户实体关联信息")
public class CcdiCustEnterpriseRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,109 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import com.baomidou.mybatisplus.annotation.TableName;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系对象 ccdi_cust_fmy_relation
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@TableName("ccdi_cust_fmy_relation")
public class CcdiCustFmyRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
private Long id;
/** 信贷客户身份证号 */
private String personId;
/** 关系类型 */
private String relationType;
/** 关系人姓名 */
private String relationName;
/** 性别M-男F-女O-其他 */
private String gender;
/** 出生日期 */
private Date birthDate;
/** 关系人证件类型 */
private String relationCertType;
/** 关系人证件号码 */
private String relationCertNo;
/** 手机号码1 */
private String mobilePhone1;
/** 手机号码2 */
private String mobilePhone2;
/** 微信名称1 */
private String wechatNo1;
/** 微信名称2 */
private String wechatNo2;
/** 微信名称3 */
private String wechatNo3;
/** 详细联系地址 */
private String contactAddress;
/** 关系详细描述 */
private String relationDesc;
/** 状态0-无效1-有效 */
private Integer status;
/** 生效日期 */
private Date effectiveDate;
/** 失效日期 */
private Date invalidDate;
/** 备注 */
private String remark;
/** 数据来源MANUAL-手工录入IMPORT-批量导入 */
private String dataSource;
/** 是否是员工亲属0-否 */
private Boolean isEmpFamily;
/** 是否是客户亲属1-是 */
private Boolean isCustFamily;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

@@ -1,93 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.*;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工实体关系信息对象 ccdi_staff_enterprise_relation
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@TableName("ccdi_staff_enterprise_relation")
@Schema(description = "员工实体关系信息")
public class CcdiStaffEnterpriseRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,107 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.FieldFill;
import com.baomidou.mybatisplus.annotation.IdType;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.annotation.TableId;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系对象 ccdi_staff_fmy_relation
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
public class CcdiStaffFmyRelation implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
private Long id;
/** 员工身份证号 */
private String personId;
/** 关系类型 */
private String relationType;
/** 关系人姓名 */
private String relationName;
/** 性别M-男F-女O-其他 */
private String gender;
/** 出生日期 */
private Date birthDate;
/** 关系人证件类型 */
private String relationCertType;
/** 关系人证件号码 */
private String relationCertNo;
/** 手机号码1 */
private String mobilePhone1;
/** 手机号码2 */
private String mobilePhone2;
/** 微信名称1 */
private String wechatNo1;
/** 微信名称2 */
private String wechatNo2;
/** 微信名称3 */
private String wechatNo3;
/** 详细联系地址 */
private String contactAddress;
/** 关系详细描述 */
private String relationDesc;
/** 生效日期 */
private Date effectiveDate;
/** 失效日期 */
private Date invalidDate;
/** 状态0-无效1-有效 */
private Integer status;
/** 备注 */
private String remark;
/** 数据来源MANUAL-手工录入IMPORT-导入 */
private String dataSource;
/** 是否是员工亲属0-否1-是 */
private Boolean isEmpFamily;
/** 是否是客户亲属0-否1-是 */
private Boolean isCustFamily;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

@@ -1,84 +0,0 @@
package com.ruoyi.info.collection.domain;
import com.baomidou.mybatisplus.annotation.*;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录对象 ccdi_staff_transfer
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@TableName("ccdi_staff_transfer")
public class CcdiStaffTransfer implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@TableId(type = IdType.AUTO)
private Long id;
/** 员工ID,关联ccdi_base_staff.staff_id */
private Long staffId;
/** 调动类型 */
private String transferType;
/** 调动子类型 */
private String transferSubType;
/** 调动前部门ID */
private Long deptIdBefore;
/** 调动前部门 */
private String deptNameBefore;
/** 调动前职级 */
private String gradeBefore;
/** 调动前岗位 */
private String positionBefore;
/** 调动前薪酬等级 */
private String salaryLevelBefore;
/** 调动后部门ID */
private Long deptIdAfter;
/** 调动后部门 */
private String deptNameAfter;
/** 调动后职级 */
private String gradeAfter;
/** 调动后岗位 */
private String positionAfter;
/** 调动后薪酬等级 */
private String salaryLevelAfter;
/** 调动日期 */
private Date transferDate;
/** 创建时间 */
@TableField(fill = FieldFill.INSERT)
private Date createTime;
/** 更新时间 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private Date updateTime;
/** 创建人 */
@TableField(fill = FieldFill.INSERT)
private String createdBy;
/** 更新人 */
@TableField(fill = FieldFill.INSERT_UPDATE)
private String updatedBy;
}

View File

@@ -1,54 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工信息新增 DTO
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
public class CcdiBaseStaffAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 姓名 */
@NotBlank(message = "姓名不能为空")
@Size(max = 100, message = "姓名长度不能超过100个字符")
private String name;
/** 员工ID */
@NotNull(message = "员工ID不能为空")
private Long staffId;
/** 所属部门ID */
@NotNull(message = "所属部门不能为空")
private Long deptId;
/** 身份证号 */
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确")
private String idCard;
/** 电话 */
@NotBlank(message = "电话不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "电话格式不正确")
private String phone;
/** 入职时间 */
private Date hireDate;
/** 状态 */
@NotBlank(message = "状态不能为空")
private String status;
}

View File

@@ -1,52 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工信息编辑 DTO
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
public class CcdiBaseStaffEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
@NotNull(message = "员工ID不能为空")
private Long staffId;
/** 姓名 */
@Size(max = 100, message = "姓名长度不能超过100个字符")
private String name;
/** 所属部门ID */
@NotNull(message = "所属部门不能为空")
private Long deptId;
/** 身份证号 */
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确")
private String idCard;
/** 电话 */
@NotBlank(message = "电话不能为空")
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "电话格式不正确")
private String phone;
/** 入职时间 */
private Date hireDate;
/** 状态 */
private String status;
}

View File

@@ -1,34 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工信息查询 DTO
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
public class CcdiBaseStaffQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 姓名(模糊查询) */
private String name;
/** 员工ID(精确查询) */
private Long staffId;
/** 所属部门ID */
private Long deptId;
/** 身份证号(模糊查询) */
private String idCard;
/** 状态 */
private String status;
}

View File

@@ -1,55 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息新增DTO
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息新增")
public class CcdiCustEnterpriseRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确")
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
}

View File

@@ -1,56 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息编辑DTO
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息编辑")
public class CcdiCustEnterpriseRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 身份证号(不可修改) */
@Schema(description = "身份证号(不可修改)")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码(不可修改) */
@Schema(description = "统一社会信用代码(不可修改)")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
}

View File

@@ -1,37 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息查询DTO
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息查询条件")
public class CcdiCustEnterpriseRelationQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
}

View File

@@ -1,119 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系新增DTO
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "信贷客户家庭关系新增")
public class CcdiCustFmyRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 信贷客户身份证号 */
@NotBlank(message = "信贷客户身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "信贷客户身份证号格式不正确")
@Schema(description = "信贷客户身份证号")
private String personId;
/** 关系类型 */
@NotBlank(message = "关系类型不能为空")
@Size(max = 50, message = "关系类型长度不能超过50个字符")
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@NotBlank(message = "关系人姓名不能为空")
@Size(max = 100, message = "关系人姓名长度不能超过100个字符")
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O")
@Schema(description = "性别M-男F-女O-其他")
private String gender;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@NotBlank(message = "关系人证件类型不能为空")
@Size(max = 50, message = "关系人证件类型长度不能超过50个字符")
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件号码 */
@NotBlank(message = "关系人证件号码不能为空")
@Size(max = 100, message = "关系人证件号码长度不能超过100个字符")
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确")
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确")
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Size(max = 50, message = "微信名称1长度不能超过50个字符")
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Size(max = 50, message = "微信名称2长度不能超过50个字符")
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Size(max = 50, message = "微信名称3长度不能超过50个字符")
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Size(max = 500, message = "详细联系地址长度不能超过500个字符")
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Size(max = 500, message = "关系详细描述长度不能超过500个字符")
@Schema(description = "关系详细描述")
private String relationDesc;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 备注 */
@Schema(description = "备注")
private String remark;
}

View File

@@ -1,125 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系编辑DTO
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "信贷客户家庭关系编辑")
public class CcdiCustFmyRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 信贷客户身份证号 */
@NotBlank(message = "信贷客户身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "信贷客户身份证号格式不正确")
@Schema(description = "信贷客户身份证号")
private String personId;
/** 关系类型 */
@NotBlank(message = "关系类型不能为空")
@Size(max = 50, message = "关系类型长度不能超过50个字符")
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@NotBlank(message = "关系人姓名不能为空")
@Size(max = 100, message = "关系人姓名长度不能超过100个字符")
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O")
@Schema(description = "性别M-男F-女O-其他")
private String gender;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@NotBlank(message = "关系人证件类型不能为空")
@Size(max = 50, message = "关系人证件类型长度不能超过50个字符")
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件号码 */
@NotBlank(message = "关系人证件号码不能为空")
@Size(max = 100, message = "关系人证件号码长度不能超过100个字符")
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确")
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确")
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Size(max = 50, message = "微信名称1长度不能超过50个字符")
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Size(max = 50, message = "微信名称2长度不能超过50个字符")
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Size(max = 50, message = "微信名称3长度不能超过50个字符")
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Size(max = 500, message = "详细联系地址长度不能超过500个字符")
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Size(max = 500, message = "关系详细描述长度不能超过500个字符")
@Schema(description = "关系详细描述")
private String relationDesc;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 备注 */
@Schema(description = "备注")
private String remark;
}

View File

@@ -1,53 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系查询DTO
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "信贷客户家庭关系查询")
public class CcdiCustFmyRelationQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 信贷客户身份证号 */
@Schema(description = "信贷客户身份证号")
private String personId;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 生效日期开始 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期开始")
private Date effectiveDateStart;
/** 生效日期结束 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期结束")
private Date effectiveDateEnd;
}

View File

@@ -1,76 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工实体关系信息新增DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息新增")
public class CcdiStaffEnterpriseRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@NotBlank(message = "身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确")
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@NotBlank(message = "统一社会信用代码不能为空")
@Pattern(regexp = "^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$", message = "统一社会信用代码格式不正确")
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Size(max = 50, message = "数据来源长度不能超过50个字符")
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
}

View File

@@ -1,77 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工实体关系信息编辑DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息编辑")
public class CcdiStaffEnterpriseRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号(不可修改)")
private String personId;
/** 关联人在企业的职务 */
@Size(max = 100, message = "关联人在企业的职务长度不能超过100个字符")
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码(不可修改)")
private String socialCreditCode;
/** 企业名称 */
@NotBlank(message = "企业名称不能为空")
@Size(max = 200, message = "企业名称长度不能超过200个字符")
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Size(max = 50, message = "数据来源长度不能超过50个字符")
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
}

View File

@@ -1,37 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工实体关系信息查询DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息查询条件")
public class CcdiStaffEnterpriseRelationQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
}

View File

@@ -1,119 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系新增DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工亲属关系新增")
public class CcdiStaffFmyRelationAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工身份证号 */
@NotBlank(message = "员工身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "员工身份证号格式不正确")
@Schema(description = "员工身份证号")
private String personId;
/** 关系类型 */
@NotBlank(message = "关系类型不能为空")
@Size(max = 50, message = "关系类型长度不能超过50个字符")
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@NotBlank(message = "关系人姓名不能为空")
@Size(max = 100, message = "关系人姓名长度不能超过100个字符")
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O")
@Schema(description = "性别M-男F-女O-其他")
private String gender;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@NotBlank(message = "关系人证件类型不能为空")
@Size(max = 50, message = "关系人证件类型长度不能超过50个字符")
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件号码 */
@NotBlank(message = "关系人证件号码不能为空")
@Size(max = 100, message = "关系人证件号码长度不能超过100个字符")
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确")
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确")
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Size(max = 50, message = "微信名称1长度不能超过50个字符")
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Size(max = 50, message = "微信名称2长度不能超过50个字符")
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Size(max = 50, message = "微信名称3长度不能超过50个字符")
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Size(max = 500, message = "详细联系地址长度不能超过500个字符")
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Size(max = 500, message = "关系详细描述长度不能超过500个字符")
@Schema(description = "关系详细描述")
private String relationDesc;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 备注 */
@Schema(description = "备注")
private String remark;
}

View File

@@ -1,125 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Pattern;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系编辑DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工亲属关系编辑")
public class CcdiStaffFmyRelationEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 员工身份证号 */
@NotBlank(message = "员工身份证号不能为空")
@Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$", message = "员工身份证号格式不正确")
@Schema(description = "员工身份证号")
private String personId;
/** 关系类型 */
@NotBlank(message = "关系类型不能为空")
@Size(max = 50, message = "关系类型长度不能超过50个字符")
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@NotBlank(message = "关系人姓名不能为空")
@Size(max = 100, message = "关系人姓名长度不能超过100个字符")
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Pattern(regexp = "^[MFO]$", message = "性别只能是M、F或O")
@Schema(description = "性别M-男F-女O-其他")
private String gender;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@NotBlank(message = "关系人证件类型不能为空")
@Size(max = 50, message = "关系人证件类型长度不能超过50个字符")
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件号码 */
@NotBlank(message = "关系人证件号码不能为空")
@Size(max = 100, message = "关系人证件号码长度不能超过100个字符")
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码1格式不正确")
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Pattern(regexp = "^1[3-9]\\d{9}$", message = "手机号码2格式不正确")
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Size(max = 50, message = "微信名称1长度不能超过50个字符")
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Size(max = 50, message = "微信名称2长度不能超过50个字符")
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Size(max = 50, message = "微信名称3长度不能超过50个字符")
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Size(max = 500, message = "详细联系地址长度不能超过500个字符")
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Size(max = 500, message = "关系详细描述长度不能超过500个字符")
@Schema(description = "关系详细描述")
private String relationDesc;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 备注 */
@Schema(description = "备注")
private String remark;
}

View File

@@ -1,57 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系查询DTO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工亲属关系查询")
public class CcdiStaffFmyRelationQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工身份证号 */
@Schema(description = "员工身份证号")
private String personId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String personName;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 生效日期开始 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期开始")
private Date effectiveDateStart;
/** 生效日期结束 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期结束")
private Date effectiveDateEnd;
}

View File

@@ -1,98 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotBlank;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录新增DTO
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@Schema(description = "员工调动记录新增")
public class CcdiStaffTransferAddDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
@NotNull(message = "员工ID不能为空")
@Schema(description = "员工ID")
private Long staffId;
/** 调动类型 */
@NotBlank(message = "调动类型不能为空")
@Size(max = 50, message = "调动类型长度不能超过50个字符")
@Schema(description = "调动类型")
private String transferType;
/** 调动子类型 */
@Size(max = 100, message = "调动子类型长度不能超过100个字符")
@Schema(description = "调动子类型")
private String transferSubType;
/** 调动前部门ID */
@NotNull(message = "调动前部门ID不能为空")
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/** 调动前部门 */
@Size(max = 200, message = "调动前部门长度不能超过200个字符")
@Schema(description = "调动前部门")
private String deptNameBefore;
/** 调动前职级 */
@Size(max = 50, message = "调动前职级长度不能超过50个字符")
@Schema(description = "调动前职级")
private String gradeBefore;
/** 调动前岗位 */
@Size(max = 100, message = "调动前岗位长度不能超过100个字符")
@Schema(description = "调动前岗位")
private String positionBefore;
/** 调动前薪酬等级 */
@Size(max = 50, message = "调动前薪酬等级长度不能超过50个字符")
@Schema(description = "调动前薪酬等级")
private String salaryLevelBefore;
/** 调动后部门ID */
@NotNull(message = "调动后部门ID不能为空")
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/** 调动后部门 */
@Size(max = 200, message = "调动后部门长度不能超过200个字符")
@Schema(description = "调动后部门")
private String deptNameAfter;
/** 调动后职级 */
@Size(max = 50, message = "调动后职级长度不能超过50个字符")
@Schema(description = "调动后职级")
private String gradeAfter;
/** 调动后岗位 */
@Size(max = 100, message = "调动后岗位长度不能超过100个字符")
@Schema(description = "调动后岗位")
private String positionAfter;
/** 调动后薪酬等级 */
@Size(max = 50, message = "调动后薪酬等级长度不能超过50个字符")
@Schema(description = "调动后薪酬等级")
private String salaryLevelAfter;
/** 调动日期 */
@NotNull(message = "调动日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期")
private Date transferDate;
}

View File

@@ -1,99 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import jakarta.validation.constraints.NotNull;
import jakarta.validation.constraints.Size;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录修改DTO
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@Schema(description = "员工调动记录修改")
public class CcdiStaffTransferEditDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@NotNull(message = "主键ID不能为空")
@Schema(description = "主键ID")
private Long id;
/** 员工ID */
@NotNull(message = "员工ID不能为空")
@Schema(description = "员工ID")
private Long staffId;
/** 调动类型 */
@Size(max = 50, message = "调动类型长度不能超过50个字符")
@Schema(description = "调动类型")
private String transferType;
/** 调动子类型 */
@Size(max = 100, message = "调动子类型长度不能超过100个字符")
@Schema(description = "调动子类型")
private String transferSubType;
/** 调动前部门ID */
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/** 调动前部门 */
@Size(max = 200, message = "调动前部门长度不能超过200个字符")
@Schema(description = "调动前部门")
private String deptNameBefore;
/** 调动前职级 */
@Size(max = 50, message = "调动前职级长度不能超过50个字符")
@Schema(description = "调动前职级")
private String gradeBefore;
/** 调动前岗位 */
@Size(max = 100, message = "调动前岗位长度不能超过100个字符")
@Schema(description = "调动前岗位")
private String positionBefore;
/** 调动前薪酬等级 */
@Size(max = 50, message = "调动前薪酬等级长度不能超过50个字符")
@Schema(description = "调动前薪酬等级")
private String salaryLevelBefore;
/** 调动后部门ID */
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/** 调动后部门 */
@Size(max = 200, message = "调动后部门长度不能超过200个字符")
@Schema(description = "调动后部门")
private String deptNameAfter;
/** 调动后职级 */
@Size(max = 50, message = "调动后职级长度不能超过50个字符")
@Schema(description = "调动后职级")
private String gradeAfter;
/** 调动后岗位 */
@Size(max = 100, message = "调动后岗位长度不能超过100个字符")
@Schema(description = "调动后岗位")
private String positionAfter;
/** 调动后薪酬等级 */
@Size(max = 50, message = "调动后薪酬等级长度不能超过50个字符")
@Schema(description = "调动后薪酬等级")
private String salaryLevelAfter;
/** 调动日期 */
@NotNull(message = "调动日期不能为空")
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期")
private Date transferDate;
}

View File

@@ -1,57 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录查询DTO
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@Schema(description = "员工调动记录查询")
public class CcdiStaffTransferQueryDTO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID(模糊查询) */
@Schema(description = "员工ID")
private Long staffId;
/** 员工姓名(模糊查询) */
@Schema(description = "员工姓名")
private String staffName;
/** 调动类型(精确查询) */
@Schema(description = "调动类型")
private String transferType;
/** 调动子类型 */
@Schema(description = "调动子类型")
private String transferSubType;
/** 调动前部门ID */
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/** 调动后部门ID */
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/** 调动日期开始 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期开始")
private Date transferDateStart;
/** 调动日期结束 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期结束")
private Date transferDateEnd;
}

View File

@@ -1,90 +0,0 @@
package com.ruoyi.info.collection.domain.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录唯一键DTO
* 用于唯一性校验员工ID + 调动前部门ID + 调动后部门ID + 调动日期
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "员工调动记录唯一键")
public class TransferUniqueKey implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/**
* 员工ID
*/
@Schema(description = "员工ID")
private Long staffId;
/**
* 调动前部门ID
*/
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/**
* 调动后部门ID
*/
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/**
* 调动日期
*/
@Schema(description = "调动日期")
private Date transferDate;
/**
* 生成唯一标识字符串
* 格式: staffId_deptIdBefore_deptIdAfter_transferDate的时间戳
*
* @return 唯一标识字符串
*/
public String toUniqueString() {
return staffId + "_" +
deptIdBefore + "_" +
deptIdAfter + "_" +
(transferDate != null ? transferDate.getTime() : 0);
}
/**
* 从AddDTO构建唯一键
*
* @param addDTO 新增DTO
* @return 唯一键
*/
public static TransferUniqueKey from(CcdiStaffTransferAddDTO addDTO) {
TransferUniqueKey key = new TransferUniqueKey();
key.setStaffId(addDTO.getStaffId());
key.setDeptIdBefore(addDTO.getDeptIdBefore());
key.setDeptIdAfter(addDTO.getDeptIdAfter());
key.setTransferDate(addDTO.getTransferDate());
return key;
}
/**
* 从EditDTO构建唯一键
*
* @param editDTO 编辑DTO
* @return 唯一键
*/
public static TransferUniqueKey from(CcdiStaffTransferEditDTO editDTO) {
TransferUniqueKey key = new TransferUniqueKey();
key.setStaffId(editDTO.getStaffId());
key.setDeptIdBefore(editDTO.getDeptIdBefore());
key.setDeptIdAfter(editDTO.getDeptIdAfter());
key.setTransferDate(editDTO.getTransferDate());
return key;
}
}

View File

@@ -1,66 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工信息Excel导入导出对象
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
public class CcdiBaseStaffExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 姓名 */
@ExcelProperty(value = "姓名", index = 0)
@ColumnWidth(15)
@Required
private String name;
/** 员工ID */
@ExcelProperty(value = "员工ID", index = 1)
@ColumnWidth(15)
@Required
private Long staffId;
/** 所属部门ID */
@ExcelProperty(value = "所属部门ID", index = 2)
@ColumnWidth(15)
@Required
private Long deptId;
/** 身份证号 */
@ExcelProperty(value = "身份证号", index = 3)
@ColumnWidth(20)
@Required
private String idCard;
/** 电话 */
@ExcelProperty(value = "电话", index = 4)
@ColumnWidth(15)
@Required
private String phone;
/** 入职时间 */
@ExcelProperty(value = "入职时间", index = 5)
@ColumnWidth(15)
private Date hireDate;
/** 状态 */
@ExcelProperty(value = "状态", index = 6)
@ColumnWidth(10)
@DictDropdown(dictType = "ccdi_employee_status")
@Required
private String status;
}

View File

@@ -1,57 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.Required;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息Excel导入导出对象")
public class CcdiCustEnterpriseRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@ExcelProperty(value = "身份证号", index = 0)
@ColumnWidth(20)
@Required
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@ExcelProperty(value = "统一社会信用代码", index = 1)
@ColumnWidth(25)
@Required
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@ExcelProperty(value = "企业名称", index = 2)
@ColumnWidth(30)
@Required
@Schema(description = "企业名称")
private String enterpriseName;
/** 关联人在企业的职务 */
@ExcelProperty(value = "关联人在企业的职务", index = 3)
@ColumnWidth(25)
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 补充说明 */
@ExcelProperty(value = "补充说明", index = 4)
@ColumnWidth(40)
@Schema(description = "补充说明")
private String remark;
}

View File

@@ -1,117 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
public class CcdiCustFmyRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 信贷客户身份证号 */
@ExcelProperty(value = "信贷客户身份证号*", index = 0)
@ColumnWidth(20)
@Required
private String personId;
/** 关系类型 */
@ExcelProperty(value = "关系类型*", index = 1)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_relation_type")
@Required
private String relationType;
/** 关系人姓名 */
@ExcelProperty(value = "关系人姓名*", index = 2)
@ColumnWidth(15)
@Required
private String relationName;
/** 性别 */
@ExcelProperty(value = "性别", index = 3)
@ColumnWidth(10)
@DictDropdown(dictType = "ccdi_indiv_gender")
private String gender;
/** 出生日期 */
@ExcelProperty(value = "出生日期", index = 4)
@ColumnWidth(15)
private Date birthDate;
/** 关系人证件类型 */
@ExcelProperty(value = "关系人证件类型*", index = 5)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_certificate_type")
@Required
private String relationCertType;
/** 关系人证件号码 */
@ExcelProperty(value = "关系人证件号码*", index = 6)
@ColumnWidth(20)
@Required
private String relationCertNo;
/** 手机号码1 */
@ExcelProperty(value = "手机号码1", index = 7)
@ColumnWidth(15)
private String mobilePhone1;
/** 手机号码2 */
@ExcelProperty(value = "手机号码2", index = 8)
@ColumnWidth(15)
private String mobilePhone2;
/** 微信名称1 */
@ExcelProperty(value = "微信名称1", index = 9)
@ColumnWidth(15)
private String wechatNo1;
/** 微信名称2 */
@ExcelProperty(value = "微信名称2", index = 10)
@ColumnWidth(15)
private String wechatNo2;
/** 微信名称3 */
@ExcelProperty(value = "微信名称3", index = 11)
@ColumnWidth(15)
private String wechatNo3;
/** 详细联系地址 */
@ExcelProperty(value = "详细联系地址", index = 12)
@ColumnWidth(30)
private String contactAddress;
/** 关系详细描述 */
@ExcelProperty(value = "关系详细描述", index = 13)
@ColumnWidth(30)
private String relationDesc;
/** 生效日期 */
@ExcelProperty(value = "生效日期", index = 14)
@ColumnWidth(15)
private Date effectiveDate;
/** 失效日期 */
@ExcelProperty(value = "失效日期", index = 15)
@ColumnWidth(15)
private Date invalidDate;
/** 备注 */
@ExcelProperty(value = "备注", index = 16)
@ColumnWidth(30)
private String remark;
}

View File

@@ -1,57 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.Required;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工实体关系信息Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息Excel导入导出对象")
public class CcdiStaffEnterpriseRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@ExcelProperty(value = "身份证号", index = 0)
@ColumnWidth(20)
@Required
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@ExcelProperty(value = "统一社会信用代码", index = 1)
@ColumnWidth(25)
@Required
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@ExcelProperty(value = "企业名称", index = 2)
@ColumnWidth(30)
@Required
@Schema(description = "企业名称")
private String enterpriseName;
/** 关联人在企业的职务 */
@ExcelProperty(value = "关联人在企业的职务", index = 3)
@ColumnWidth(25)
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 补充说明 */
@ExcelProperty(value = "补充说明", index = 4)
@ColumnWidth(40)
@Schema(description = "补充说明")
private String remark;
}

View File

@@ -1,117 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
public class CcdiStaffFmyRelationExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工身份证号 */
@ExcelProperty(value = "员工身份证号*", index = 0)
@ColumnWidth(20)
@Required
private String personId;
/** 关系类型 */
@ExcelProperty(value = "关系类型*", index = 1)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_relation_type")
@Required
private String relationType;
/** 关系人姓名 */
@ExcelProperty(value = "关系人姓名*", index = 2)
@ColumnWidth(15)
@Required
private String relationName;
/** 性别 */
@ExcelProperty(value = "性别", index = 3)
@ColumnWidth(10)
@DictDropdown(dictType = "ccdi_indiv_gender")
private String gender;
/** 出生日期 */
@ExcelProperty(value = "出生日期", index = 4)
@ColumnWidth(15)
private Date birthDate;
/** 关系人证件类型 */
@ExcelProperty(value = "关系人证件类型*", index = 5)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_certificate_type")
@Required
private String relationCertType;
/** 关系人证件号码 */
@ExcelProperty(value = "关系人证件号码*", index = 6)
@ColumnWidth(20)
@Required
private String relationCertNo;
/** 手机号码1 */
@ExcelProperty(value = "手机号码1", index = 7)
@ColumnWidth(15)
private String mobilePhone1;
/** 手机号码2 */
@ExcelProperty(value = "手机号码2", index = 8)
@ColumnWidth(15)
private String mobilePhone2;
/** 微信名称1 */
@ExcelProperty(value = "微信名称1", index = 9)
@ColumnWidth(15)
private String wechatNo1;
/** 微信名称2 */
@ExcelProperty(value = "微信名称2", index = 10)
@ColumnWidth(15)
private String wechatNo2;
/** 微信名称3 */
@ExcelProperty(value = "微信名称3", index = 11)
@ColumnWidth(15)
private String wechatNo3;
/** 详细联系地址 */
@ExcelProperty(value = "详细联系地址", index = 12)
@ColumnWidth(30)
private String contactAddress;
/** 关系详细描述 */
@ExcelProperty(value = "关系详细描述", index = 13)
@ColumnWidth(30)
private String relationDesc;
/** 生效日期 */
@ExcelProperty(value = "生效日期", index = 14)
@ColumnWidth(15)
private Date effectiveDate;
/** 失效日期 */
@ExcelProperty(value = "失效日期", index = 15)
@ColumnWidth(15)
private Date invalidDate;
/** 备注 */
@ExcelProperty(value = "备注", index = 16)
@ColumnWidth(30)
private String remark;
}

View File

@@ -1,90 +0,0 @@
package com.ruoyi.info.collection.domain.excel;
import com.alibaba.excel.annotation.ExcelProperty;
import com.alibaba.excel.annotation.write.style.ColumnWidth;
import com.ruoyi.common.annotation.DictDropdown;
import com.ruoyi.common.annotation.Required;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录Excel导入导出对象
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
public class CcdiStaffTransferExcel implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
@ExcelProperty(value = "员工ID*", index = 0)
@ColumnWidth(15)
@Required
private Long staffId;
/** 调动类型 */
@ExcelProperty(value = "调动类型*", index = 1)
@ColumnWidth(15)
@DictDropdown(dictType = "ccdi_transfer_type")
@Required
private String transferType;
/** 调动子类型 */
@ExcelProperty(value = "调动子类型", index = 2)
@ColumnWidth(15)
private String transferSubType;
/** 调动前部门ID */
@ExcelProperty(value = "调动前部门ID*", index = 3)
@ColumnWidth(15)
@Required
private Long deptIdBefore;
/** 调动前职级 */
@ExcelProperty(value = "调动前职级", index = 4)
@ColumnWidth(15)
private String gradeBefore;
/** 调动前岗位 */
@ExcelProperty(value = "调动前岗位", index = 5)
@ColumnWidth(15)
private String positionBefore;
/** 调动前薪酬等级 */
@ExcelProperty(value = "调动前薪酬等级", index = 6)
@ColumnWidth(15)
private String salaryLevelBefore;
/** 调动后部门ID */
@ExcelProperty(value = "调动后部门ID*", index = 7)
@ColumnWidth(15)
@Required
private Long deptIdAfter;
/** 调动后职级 */
@ExcelProperty(value = "调动后职级", index = 8)
@ColumnWidth(15)
private String gradeAfter;
/** 调动后岗位 */
@ExcelProperty(value = "调动后岗位", index = 9)
@ColumnWidth(15)
private String positionAfter;
/** 调动后薪酬等级 */
@ExcelProperty(value = "调动后薪酬等级", index = 10)
@ColumnWidth(15)
private String salaryLevelAfter;
/** 调动日期 */
@ExcelProperty(value = "调动日期*", index = 11)
@ColumnWidth(15)
@Required
private Date transferDate;
}

View File

@@ -1,33 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import lombok.Data;
/**
* 员工选项VO用于下拉选择框
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
public class CcdiBaseStaffOptionVO {
/**
* 员工ID
*/
private Long staffId;
/**
* 员工姓名
*/
private String name;
/**
* 部门ID
*/
private Long deptId;
/**
* 部门名称
*/
private String deptName;
}

View File

@@ -1,59 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工信息 VO
*
* @author ruoyi
* @date 2026-01-28
*/
@Data
public class CcdiBaseStaffVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
private Long staffId;
/** 姓名 */
private String name;
/** 所属部门ID */
private Long deptId;
/** 所属部门名称 */
private String deptName;
/** 身份证号 */
private String idCard;
/** 电话 */
private String phone;
/** 入职时间 */
private Date hireDate;
/** 状态 */
private String status;
/** 状态描述 */
private String statusDesc;
/** 创建时间 */
private Date createTime;
/** 创建者 */
private String createBy;
/** 更新时间 */
private Date updateTime;
/** 更新者 */
private String updateBy;
}

View File

@@ -1,89 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户实体关联信息VO
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息")
public class CcdiCustEnterpriseRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,148 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 信贷客户家庭关系VO
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "信贷客户家庭关系")
public class CcdiCustFmyRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 信贷客户身份证号 */
@Schema(description = "信贷客户身份证号")
private String personId;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系类型名称 */
@Schema(description = "关系类型名称")
private String relationTypeName;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Schema(description = "性别M-男F-女O-其他")
private String gender;
/** 性别名称 */
@Schema(description = "性别名称")
private String genderName;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件类型名称 */
@Schema(description = "关系人证件类型名称")
private String relationCertTypeName;
/** 关系人证件号码 */
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Schema(description = "关系详细描述")
private String relationDesc;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 状态 */
@Schema(description = "状态0-无效1-有效")
private Integer status;
/** 状态名称 */
@Schema(description = "状态名称")
private String statusName;
/** 备注 */
@Schema(description = "备注")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源MANUAL-手工录入IMPORT-批量导入")
private String dataSource;
/** 是否是员工亲属 */
@Schema(description = "是否是员工亲属0-否")
private Boolean isEmpFamily;
/** 是否是客户亲属 */
@Schema(description = "是否是客户亲属1-是")
private Boolean isCustFamily;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,93 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工实体关系信息VO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息")
public class CcdiStaffEnterpriseRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String personName;
/** 关联人在企业的职务 */
@Schema(description = "关联人在企业的职务")
private String relationPersonPost;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 状态0-无效 1-有效) */
@Schema(description = "状态0-无效 1-有效)")
private Integer status;
/** 补充说明 */
@Schema(description = "补充说明")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否为员工0-否 1-是) */
@Schema(description = "是否为员工0-否 1-是)")
private Integer isEmployee;
/** 是否为员工家属0-否 1-是) */
@Schema(description = "是否为员工家属0-否 1-是)")
private Integer isEmpFamily;
/** 是否为客户0-否 1-是) */
@Schema(description = "是否为客户0-否 1-是)")
private Integer isCustomer;
/** 是否为客户家属0-否 1-是) */
@Schema(description = "是否为客户家属0-否 1-是)")
private Integer isCustFamily;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,144 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工亲属关系VO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工亲属关系")
public class CcdiStaffFmyRelationVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 员工身份证号 */
@Schema(description = "员工身份证号")
private String personId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String personName;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Schema(description = "性别")
private String gender;
/** 性别名称 */
@Schema(description = "性别名称")
private String genderName;
/** 出生日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "出生日期")
private Date birthDate;
/** 关系人证件类型 */
@Schema(description = "关系人证件类型")
private String relationCertType;
/** 关系人证件号码 */
@Schema(description = "关系人证件号码")
private String relationCertNo;
/** 手机号码1 */
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 手机号码2 */
@Schema(description = "手机号码2")
private String mobilePhone2;
/** 微信名称1 */
@Schema(description = "微信名称1")
private String wechatNo1;
/** 微信名称2 */
@Schema(description = "微信名称2")
private String wechatNo2;
/** 微信名称3 */
@Schema(description = "微信名称3")
private String wechatNo3;
/** 详细联系地址 */
@Schema(description = "详细联系地址")
private String contactAddress;
/** 关系详细描述 */
@Schema(description = "关系详细描述")
private String relationDesc;
/** 生效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "生效日期")
private Date effectiveDate;
/** 失效日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "失效日期")
private Date invalidDate;
/** 状态 */
@Schema(description = "状态")
private Integer status;
/** 状态名称 */
@Schema(description = "状态名称")
private String statusName;
/** 备注 */
@Schema(description = "备注")
private String remark;
/** 数据来源 */
@Schema(description = "数据来源")
private String dataSource;
/** 是否是员工亲属 */
@Schema(description = "是否是员工亲属")
private Boolean isEmpFamily;
/** 是否是客户亲属 */
@Schema(description = "是否是客户亲属")
private Boolean isCustFamily;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,106 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录VO
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@Schema(description = "员工调动记录")
public class CcdiStaffTransferVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 主键ID */
@Schema(description = "主键ID")
private Long id;
/** 员工ID */
@Schema(description = "员工ID")
private Long staffId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String staffName;
/** 调动类型 */
@Schema(description = "调动类型")
private String transferType;
/** 调动子类型 */
@Schema(description = "调动子类型")
private String transferSubType;
/** 调动前部门ID */
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/** 调动前部门 */
@Schema(description = "调动前部门")
private String deptNameBefore;
/** 调动前职级 */
@Schema(description = "调动前职级")
private String gradeBefore;
/** 调动前岗位 */
@Schema(description = "调动前岗位")
private String positionBefore;
/** 调动前薪酬等级 */
@Schema(description = "调动前薪酬等级")
private String salaryLevelBefore;
/** 调动后部门ID */
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/** 调动后部门 */
@Schema(description = "调动后部门")
private String deptNameAfter;
/** 调动后职级 */
@Schema(description = "调动后职级")
private String gradeAfter;
/** 调动后岗位 */
@Schema(description = "调动后岗位")
private String positionAfter;
/** 调动后薪酬等级 */
@Schema(description = "调动后薪酬等级")
private String salaryLevelAfter;
/** 调动日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期")
private Date transferDate;
/** 创建时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "创建时间")
private Date createTime;
/** 更新时间 */
@JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss")
@Schema(description = "更新时间")
private Date updateTime;
/** 创建人 */
@Schema(description = "创建人")
private String createdBy;
/** 更新人 */
@Schema(description = "更新人")
private String updatedBy;
}

View File

@@ -1,37 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户实体关联信息导入失败记录VO
*
* @author ruoyi
* @date 2026-02-12
*/
@Data
@Schema(description = "信贷客户实体关联信息导入失败记录")
public class CustEnterpriseRelationImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 错误信息 */
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 信贷客户家庭关系导入失败VO
*
* @author ruoyi
* @date 2026-02-11
*/
@Data
@Schema(description = "信贷客户家庭关系导入失败记录")
public class CustFmyRelationImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 行号 */
@Schema(description = "行号")
private Integer rowNum;
/** 信贷客户身份证号 */
@Schema(description = "信贷客户身份证号")
private String personId;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 错误消息 */
@Schema(description = "错误消息")
private String errorMessage;
}

View File

@@ -1,37 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
/**
* 员工实体关系信息导入失败记录VO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工实体关系信息导入失败记录")
public class StaffEnterpriseRelationImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 身份证号 */
@Schema(description = "身份证号")
private String personId;
/** 统一社会信用代码 */
@Schema(description = "统一社会信用代码")
private String socialCreditCode;
/** 企业名称 */
@Schema(description = "企业名称")
private String enterpriseName;
/** 错误信息 */
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -1,51 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
/**
* 员工亲属关系信息导入失败记录VO
*
* @author ruoyi
* @date 2026-02-09
*/
@Data
@Schema(description = "员工亲属关系信息导入失败记录")
public class StaffFmyRelationImportFailureVO {
/** 员工身份证号 */
@Schema(description = "员工身份证号")
private String personId;
/** 关系类型 */
@Schema(description = "关系类型")
private String relationType;
/** 关系人姓名 */
@Schema(description = "关系人姓名")
private String relationName;
/** 性别 */
@Schema(description = "性别")
private String gender;
/** 证件类型 */
@Schema(description = "证件类型")
private String relationCertType;
/** 证件号码 */
@Schema(description = "证件号码")
private String relationCertNo;
/** 手机号码1 */
@Schema(description = "手机号码1")
private String mobilePhone1;
/** 状态 */
@Schema(description = "状态")
private Integer status;
/** 错误信息 */
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -1,88 +0,0 @@
package com.ruoyi.info.collection.domain.vo;
import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import java.io.Serial;
import java.io.Serializable;
import java.util.Date;
/**
* 员工调动记录导入失败记录VO
*
* @author ruoyi
* @date 2026-02-10
*/
@Data
@Schema(description = "员工调动记录导入失败记录")
public class StaffTransferImportFailureVO implements Serializable {
@Serial
private static final long serialVersionUID = 1L;
/** 员工ID */
@Schema(description = "员工ID")
private Long staffId;
/** 员工姓名 */
@Schema(description = "员工姓名")
private String staffName;
/** 调动前部门ID */
@Schema(description = "调动前部门ID")
private Long deptIdBefore;
/** 调动后部门ID */
@Schema(description = "调动后部门ID")
private Long deptIdAfter;
/** 调动类型 */
@Schema(description = "调动类型")
private String transferType;
/** 调动子类型 */
@Schema(description = "调动子类型")
private String transferSubType;
/** 调动前部门 */
@Schema(description = "调动前部门")
private String deptNameBefore;
/** 调动前职级 */
@Schema(description = "调动前职级")
private String gradeBefore;
/** 调动前岗位 */
@Schema(description = "调动前岗位")
private String positionBefore;
/** 调动前薪酬等级 */
@Schema(description = "调动前薪酬等级")
private String salaryLevelBefore;
/** 调动后部门 */
@Schema(description = "调动后部门")
private String deptNameAfter;
/** 调动后职级 */
@Schema(description = "调动后职级")
private String gradeAfter;
/** 调动后岗位 */
@Schema(description = "调动后岗位")
private String positionAfter;
/** 调动后薪酬等级 */
@Schema(description = "调动后薪酬等级")
private String salaryLevelAfter;
/** 调动日期 */
@JsonFormat(pattern = "yyyy-MM-dd")
@Schema(description = "调动日期")
private Date transferDate;
/** 错误信息 */
@Schema(description = "错误信息")
private String errorMessage;
}

View File

@@ -1,138 +0,0 @@
package com.ruoyi.info.collection.enums;
import org.apache.commons.lang3.StringUtils;
/**
* 性别枚举
* 用于性别中文标签与英文代码之间的转换
*
* @author ruoyi
* @date 2026-02-10
*/
public enum GenderEnum {
/**
* 男
*/
MALE("", "M"),
/**
* 女
*/
FEMALE("", "F"),
/**
* 其他
*/
OTHER("其他", "O");
private final String chineseLabel;
private final String englishCode;
GenderEnum(String chineseLabel, String englishCode) {
this.chineseLabel = chineseLabel;
this.englishCode = englishCode;
}
public String getChineseLabel() {
return chineseLabel;
}
public String getEnglishCode() {
return englishCode;
}
/**
* 根据中文标签获取枚举
*
* @param chineseLabel 中文标签(男、女、其他)
* @return 对应的枚举值
* @throws IllegalArgumentException 如果标签无效
*/
public static GenderEnum fromChinese(String chineseLabel) {
if (StringUtils.isEmpty(chineseLabel)) {
throw new IllegalArgumentException("性别标签不能为空");
}
for (GenderEnum gender : values()) {
if (gender.chineseLabel.equals(chineseLabel)) {
return gender;
}
}
throw new IllegalArgumentException("无效的性别标签: " + chineseLabel);
}
/**
* 根据英文代码获取枚举
*
* @param englishCode 英文代码M、F、O
* @return 对应的枚举值
* @throws IllegalArgumentException 如果代码无效
*/
public static GenderEnum fromEnglish(String englishCode) {
if (StringUtils.isEmpty(englishCode)) {
throw new IllegalArgumentException("性别代码不能为空");
}
for (GenderEnum gender : values()) {
if (gender.englishCode.equals(englishCode)) {
return gender;
}
}
throw new IllegalArgumentException("无效的性别代码: " + englishCode);
}
/**
* 验证性别值是否有效
* 支持中文标签男、女、其他或英文代码M、F、O
*
* @param value 待验证的值
* @return true-有效false-无效
*/
public static boolean isValid(String value) {
if (StringUtils.isEmpty(value)) {
return false;
}
for (GenderEnum gender : values()) {
if (gender.chineseLabel.equals(value) || gender.englishCode.equals(value)) {
return true;
}
}
return false;
}
/**
* 标准化性别值
* 输入中文标签或英文代码,统一返回英文代码
*
* @param input 输入值(中文或英文)
* @return 英文代码M、F、O
* @throws IllegalArgumentException 如果输入值无效
*/
public static String normalize(String input) {
if (StringUtils.isEmpty(input)) {
throw new IllegalArgumentException("性别值不能为空");
}
// 先尝试按中文匹配
for (GenderEnum gender : values()) {
if (gender.chineseLabel.equals(input)) {
return gender.englishCode;
}
}
// 再尝试按英文匹配(大写)
String upperInput = input.toUpperCase().trim();
for (GenderEnum gender : values()) {
if (gender.englishCode.equals(upperInput)) {
return gender.englishCode;
}
}
throw new IllegalArgumentException("无效的性别值: " + input + ",有效值为:男、女、其他 或 M、F、O");
}
}

View File

@@ -1,49 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 员工信息 数据层
*
* @author ruoyi
* @date 2026-01-28
*/
public interface CcdiBaseStaffMapper extends BaseMapper<CcdiBaseStaff> {
/**
* 分页查询员工列表(包含部门名称)
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工VO分页结果
*/
Page<CcdiBaseStaffVO> selectBaseStaffPageWithDept(@Param("page") Page<CcdiBaseStaffVO> page,
@Param("query") CcdiBaseStaffQueryDTO queryDTO);
int insertOrUpdateBatch(@Param("list") List<CcdiBaseStaff> list);
/**
* 批量插入员工信息
*
* @param list 员工信息列表
* @return 影响行数
*/
int insertBatch(@Param("list") List<CcdiBaseStaff> list);
/**
* 查询员工选项(用于下拉选择框)
* <p>支持按员工ID或姓名模糊搜索只返回在职员工</p>
*
* @param query 搜索关键词员工ID或姓名可为空
* @return 员工选项列表最多返回100条
*/
List<CcdiBaseStaffOptionVO> selectStaffOptions(@Param("query") String query);
}

View File

@@ -1,67 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
/**
* 信贷客户实体关联信息 数据层
*
* @author ruoyi
* @date 2026-02-12
*/
@Mapper
public interface CcdiCustEnterpriseRelationMapper extends BaseMapper<CcdiCustEnterpriseRelation> {
/**
* 分页查询信贷客户实体关联列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 信贷客户实体关联VO分页结果
*/
Page<CcdiCustEnterpriseRelationVO> selectRelationPage(@Param("page") Page<CcdiCustEnterpriseRelationVO> page,
@Param("query") CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联详情
*
* @param id 主键ID
* @return 信贷客户实体关联VO
*/
CcdiCustEnterpriseRelationVO selectRelationById(@Param("id") Long id);
/**
* 判断身份证号和统一社会信用代码的组合是否已存在
*
* @param personId 身份证号
* @param socialCreditCode 统一社会信用代码
* @return 存在返回true否则返回false
*/
boolean existsByPersonIdAndSocialCreditCode(@Param("personId") String personId,
@Param("socialCreditCode") String socialCreditCode);
/**
* 批量查询已存在的person_id + social_credit_code组合
* 优化导入性能,一次性查询所有组合
*
* @param combinations 组合列表,格式为 ["personId1|socialCreditCode1", "personId2|socialCreditCode2", ...]
* @return 已存在的组合集合
*/
Set<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
/**
* 批量插入信贷客户实体关联数据
*
* @param list 信贷客户实体关联列表
* @return 插入行数
*/
int insertBatch(@Param("list") List<CcdiCustEnterpriseRelation> list);
}

View File

@@ -1,74 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiCustFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiCustFmyRelationVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 信贷客户家庭关系Mapper接口
*
* @author ruoyi
* @date 2026-02-11
*/
public interface CcdiCustFmyRelationMapper extends BaseMapper<CcdiCustFmyRelation> {
/**
* 分页查询信贷客户家庭关系
*
* @param page 分页对象
* @param query 查询条件
* @return 信贷客户家庭关系VO列表
*/
Page<CcdiCustFmyRelationVO> selectRelationPage(Page<CcdiCustFmyRelationVO> page,
@Param("query") CcdiCustFmyRelationQueryDTO query);
/**
* 根据ID查询信贷客户家庭关系详情
*
* @param id 主键ID
* @return 信贷客户家庭关系VO
*/
CcdiCustFmyRelationVO selectRelationById(@Param("id") Long id);
/**
* 查询已存在的关系记录(用于导入校验)
*
* @param personId 信贷客户身份证号
* @param relationType 关系类型
* @param relationCertNo 关系人证件号码
* @return 已存在的关系记录
*/
CcdiCustFmyRelation selectExistingRelations(@Param("personId") String personId,
@Param("relationType") String relationType,
@Param("relationCertNo") String relationCertNo);
/**
* 批量插入信贷客户家庭关系
*
* @param relations 信贷客户家庭关系列表
* @return 插入条数
*/
int insertBatch(@Param("relations") List<CcdiCustFmyRelation> relations);
/**
* 根据证件号码查询关系数量
*
* @param relationCertNo 关系人证件号码
* @return 关系数量
*/
int countByCertNo(@Param("relationCertNo") String relationCertNo);
/**
* 批量查询已存在的关系组合(性能优化)
* 一次性查询所有 person_id + relation_type + relation_cert_no 组合
*
* @param combinations 组合列表,格式为 "personId|relationType|relationCertNo"
* @return 已存在的组合列表
*/
List<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
}

View File

@@ -1,67 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import java.util.List;
import java.util.Set;
/**
* 员工实体关系信息 数据层
*
* @author ruoyi
* @date 2026-02-09
*/
@Mapper
public interface CcdiStaffEnterpriseRelationMapper extends BaseMapper<CcdiStaffEnterpriseRelation> {
/**
* 分页查询员工实体关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工实体关系VO分页结果
*/
Page<CcdiStaffEnterpriseRelationVO> selectRelationPage(@Param("page") Page<CcdiStaffEnterpriseRelationVO> page,
@Param("query") CcdiStaffEnterpriseRelationQueryDTO queryDTO);
/**
* 查询员工实体关系详情
*
* @param id 主键ID
* @return 员工实体关系VO
*/
CcdiStaffEnterpriseRelationVO selectRelationById(@Param("id") Long id);
/**
* 判断身份证号和统一社会信用代码的组合是否已存在
*
* @param personId 身份证号
* @param socialCreditCode 统一社会信用代码
* @return 存在返回true否则返回false
*/
boolean existsByPersonIdAndSocialCreditCode(@Param("personId") String personId,
@Param("socialCreditCode") String socialCreditCode);
/**
* 批量查询已存在的person_id + social_credit_code组合
* 优化导入性能,一次性查询所有组合
*
* @param combinations 组合列表,格式为 ["personId1|socialCreditCode1", "personId2|socialCreditCode2", ...]
* @return 已存在的组合集合
*/
Set<String> batchExistsByCombinations(@Param("combinations") List<String> combinations);
/**
* 批量插入员工实体关系数据
*
* @param list 员工实体关系列表
* @return 插入行数
*/
int insertBatch(@Param("list") List<CcdiStaffEnterpriseRelation> list);
}

View File

@@ -1,63 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 员工亲属关系 数据层
*
* @author ruoyi
* @date 2026-02-09
*/
public interface CcdiStaffFmyRelationMapper extends BaseMapper<CcdiStaffFmyRelation> {
/**
* 分页查询员工亲属关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工亲属关系VO分页结果
*/
Page<CcdiStaffFmyRelationVO> selectRelationPage(@Param("page") Page<CcdiStaffFmyRelationVO> page,
@Param("query") CcdiStaffFmyRelationQueryDTO queryDTO);
/**
* 查询员工亲属关系详情
*
* @param id 主键ID
* @return 员工亲属关系VO
*/
CcdiStaffFmyRelationVO selectRelationById(@Param("id") Long id);
/**
* 查询员工亲属关系列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工亲属关系VO列表
*/
List<CcdiStaffFmyRelationVO> selectRelationListForExport(@Param("query") CcdiStaffFmyRelationQueryDTO queryDTO);
/**
* 批量插入员工亲属关系数据
*
* @param list 员工亲属关系列表
* @return 插入行数
*/
int insertBatch(@Param("list") List<CcdiStaffFmyRelation> list);
/**
* 批量查询已存在的员工亲属关系(用于导入唯一性校验)
*
* @param personIds 员工身份证号列表
* @param relationCertNos 关系人证件号码列表
* @return 已存在的记录列表
*/
List<CcdiStaffFmyRelation> selectExistingRelations(@Param("personIds") List<String> personIds,
@Param("relationCertNos") List<String> relationCertNos);
}

View File

@@ -1,72 +0,0 @@
package com.ruoyi.info.collection.mapper;
import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffTransfer;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferQueryDTO;
import com.ruoyi.info.collection.domain.dto.TransferUniqueKey;
import com.ruoyi.info.collection.domain.vo.CcdiStaffTransferVO;
import org.apache.ibatis.annotations.Param;
import java.util.List;
/**
* 员工调动记录 数据层
*
* @author ruoyi
* @date 2026-02-10
*/
public interface CcdiStaffTransferMapper extends BaseMapper<CcdiStaffTransfer> {
/**
* 分页查询员工调动记录列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工调动记录VO分页结果
*/
Page<CcdiStaffTransferVO> selectTransferPage(@Param("page") Page<CcdiStaffTransferVO> page,
@Param("query") CcdiStaffTransferQueryDTO queryDTO);
/**
* 查询员工调动记录详情
*
* @param id 主键ID
* @return 员工调动记录VO
*/
CcdiStaffTransferVO selectTransferById(@Param("id") Long id);
/**
* 查询员工调动记录列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工调动记录VO列表
*/
List<CcdiStaffTransferVO> selectTransferListForExport(@Param("query") CcdiStaffTransferQueryDTO queryDTO);
/**
* 批量插入员工调动记录数据
*
* @param list 员工调动记录列表
* @return 插入行数
*/
int insertBatch(@Param("list") List<CcdiStaffTransfer> list);
/**
* 查询单条记录是否存在根据唯一键员工ID + 调动前部门ID + 调动后部门ID + 调动日期)
*
* @param key 唯一键
* @return 存在的记录不存在返回null
*/
CcdiStaffTransfer checkExists(@Param("key") TransferUniqueKey key);
/**
* 查询单条记录是否存在排除指定ID
*
* @param key 唯一键
* @param excludeId 排除的记录ID
* @return 存在的记录不存在返回null
*/
CcdiStaffTransfer checkExistsExcludeId(@Param("key") TransferUniqueKey key,
@Param("excludeId") Long excludeId);
}

View File

@@ -1,38 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import java.util.List;
/**
* @Author: wkc
* @CreateTime: 2026-02-06
*/
public interface ICcdiBaseStaffImportService {
/**
* 异步导入员工数据
*
* @param excelList Excel数据列表
* @param isUpdateSupport 是否更新已存在的数据
*/
void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<ImportFailureVO> getImportFailures(String taskId);
}

View File

@@ -1,96 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
import java.util.List;
/**
* 员工信息 服务层
*
* @author ruoyi
* @date 2026-01-28
*/
public interface ICcdiBaseStaffService {
/**
* 查询员工列表
*
* @param queryDTO 查询条件
* @return 员工VO集合
*/
List<CcdiBaseStaffVO> selectBaseStaffList(CcdiBaseStaffQueryDTO queryDTO);
/**
* 分页查询员工列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工VO分页结果
*/
Page<CcdiBaseStaffVO> selectBaseStaffPage(Page<CcdiBaseStaffVO> page, CcdiBaseStaffQueryDTO queryDTO);
/**
* 查询员工列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工Excel实体集合
*/
List<CcdiBaseStaffExcel> selectBaseStaffListForExport(CcdiBaseStaffQueryDTO queryDTO);
/**
* 查询员工详情
*
* @param staffId 员工ID
* @return 员工VO
*/
CcdiBaseStaffVO selectBaseStaffById(Long staffId);
/**
* 新增员工
*
* @param addDTO 新增DTO
* @return 结果
*/
int insertBaseStaff(CcdiBaseStaffAddDTO addDTO);
/**
* 修改员工
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateBaseStaff(CcdiBaseStaffEditDTO editDTO);
/**
* 批量删除员工
*
* @param staffIds 需要删除的员工ID
* @return 结果
*/
int deleteBaseStaffByIds(Long[] staffIds);
/**
* 导入员工数据
*
* @param excelList Excel实体列表
* @param isUpdateSupport 是否更新支持
* @return 结果
*/
String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport);
/**
* 查询员工下拉列表
* 支持按员工ID或姓名模糊搜索只返回在职员工
*
* @param query 搜索关键词(员工ID或姓名)
* @return 员工选项列表
*/
List<CcdiBaseStaffOptionVO> selectStaffOptions(String query);
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import java.util.List;
/**
* 信贷客户实体关联信息异步导入服务层
*
* @author ruoyi
* @date 2026-02-12
*/
public interface ICcdiCustEnterpriseRelationImportService {
/**
* 异步导入信贷客户实体关联数据
*
* @param excelList Excel数据列表
* @param taskId 任务ID
* @param userName 用户名
*/
void importRelationAsync(List<CcdiCustEnterpriseRelationExcel> excelList, String taskId, String userName);
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<CustEnterpriseRelationImportFailureVO> getImportFailures(String taskId);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
}

View File

@@ -1,84 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
import java.util.List;
/**
* 信贷客户实体关联信息 服务层
*
* @author ruoyi
* @date 2026-02-12
*/
public interface ICcdiCustEnterpriseRelationService {
/**
* 查询信贷客户实体关联列表
*
* @param queryDTO 查询条件
* @return 信贷客户实体关联VO集合
*/
List<CcdiCustEnterpriseRelationVO> selectRelationList(CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 分页查询信贷客户实体关联列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 信贷客户实体关联VO分页结果
*/
Page<CcdiCustEnterpriseRelationVO> selectRelationPage(Page<CcdiCustEnterpriseRelationVO> page, CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联列表(用于导出)
*
* @param queryDTO 查询条件
* @return 信贷客户实体关联Excel实体集合
*/
List<CcdiCustEnterpriseRelationExcel> selectRelationListForExport(CcdiCustEnterpriseRelationQueryDTO queryDTO);
/**
* 查询信贷客户实体关联详情
*
* @param id 主键ID
* @return 信贷客户实体关联VO
*/
CcdiCustEnterpriseRelationVO selectRelationById(Long id);
/**
* 新增信贷客户实体关联
*
* @param addDTO 新增DTO
* @return 结果
*/
int insertRelation(CcdiCustEnterpriseRelationAddDTO addDTO);
/**
* 修改信贷客户实体关联
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateRelation(CcdiCustEnterpriseRelationEditDTO editDTO);
/**
* 批量删除信贷客户实体关联
*
* @param ids 需要删除的主键ID
* @return 结果
*/
int deleteRelationByIds(Long[] ids);
/**
* 导入信贷客户实体关联数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
String importRelation(List<CcdiCustEnterpriseRelationExcel> excelList);
}

View File

@@ -1,50 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CustFmyRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import java.util.List;
/**
* 信贷客户家庭关系导入Service接口
*
* @author ruoyi
* @date 2026-02-11
*/
public interface ICcdiCustFmyRelationImportService {
/**
* 异步导入信贷客户家庭关系
*
* @param excels Excel数据列表
* @param taskId 任务ID
* @param userName 用户名
*/
void importRelationsAsync(List<CcdiCustFmyRelationExcel> excels, String taskId, String userName);
/**
* 校验单条数据
*
* @param excel Excel数据
* @param rowNum 行号
* @return 错误消息,为null表示校验通过
*/
String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum);
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<CustFmyRelationImportFailureVO> getImportFailures(String taskId);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
}

View File

@@ -1,95 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustFmyRelationVO;
import com.ruoyi.info.collection.domain.vo.CustFmyRelationImportFailureVO;
import jakarta.servlet.http.HttpServletResponse;
import java.util.List;
/**
* 信贷客户家庭关系Service接口
*
* @author ruoyi
* @date 2026-02-11
*/
public interface ICcdiCustFmyRelationService {
/**
* 分页查询信贷客户家庭关系
*
* @param query 查询条件
* @param pageNum 页码
* @param pageSize 每页条数
* @return 分页结果
*/
Page<CcdiCustFmyRelationVO> selectRelationPage(CcdiCustFmyRelationQueryDTO query,
Integer pageNum, Integer pageSize);
/**
* 根据ID查询信贷客户家庭关系详情
*
* @param id 主键ID
* @return 信贷客户家庭关系VO
*/
CcdiCustFmyRelationVO selectRelationById(Long id);
/**
* 新增信贷客户家庭关系
*
* @param addDTO 新增DTO
* @return 是否成功
*/
boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO);
/**
* 修改信贷客户家庭关系
*
* @param editDTO 编辑DTO
* @return 是否成功
*/
boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO);
/**
* 删除信贷客户家庭关系
*
* @param ids 主键ID数组
* @return 是否成功
*/
boolean deleteRelationByIds(Long[] ids);
/**
* 导出信贷客户家庭关系
*
* @param query 查询条件
* @param response HTTP响应
*/
void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response);
/**
* 生成导入模板
*
* @param response HTTP响应
*/
void importTemplate(HttpServletResponse response);
/**
* 批量导入信贷客户家庭关系
*
* @param excels Excel数据列表
* @return 导入任务ID
*/
String importRelations(List<CcdiCustFmyRelationExcel> excels);
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<CustFmyRelationImportFailureVO> getImportFailures(String taskId);
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
import java.util.List;
/**
* 员工实体关系信息异步导入服务层
*
* @author ruoyi
* @date 2026-02-09
*/
public interface ICcdiStaffEnterpriseRelationImportService {
/**
* 异步导入员工实体关系数据
*
* @param excelList Excel数据列表
* @param taskId 任务ID
* @param userName 当前用户名
*/
void importRelationAsync(List<CcdiStaffEnterpriseRelationExcel> excelList, String taskId, String userName);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<StaffEnterpriseRelationImportFailureVO> getImportFailures(String taskId);
}

View File

@@ -1,84 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import java.util.List;
/**
* 员工实体关系信息 服务层
*
* @author ruoyi
* @date 2026-02-09
*/
public interface ICcdiStaffEnterpriseRelationService {
/**
* 查询员工实体关系列表
*
* @param queryDTO 查询条件
* @return 员工实体关系VO集合
*/
List<CcdiStaffEnterpriseRelationVO> selectRelationList(CcdiStaffEnterpriseRelationQueryDTO queryDTO);
/**
* 分页查询员工实体关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工实体关系VO分页结果
*/
Page<CcdiStaffEnterpriseRelationVO> selectRelationPage(Page<CcdiStaffEnterpriseRelationVO> page, CcdiStaffEnterpriseRelationQueryDTO queryDTO);
/**
* 查询员工实体关系列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工实体关系Excel实体集合
*/
List<CcdiStaffEnterpriseRelationExcel> selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO);
/**
* 查询员工实体关系详情
*
* @param id 主键ID
* @return 员工实体关系VO
*/
CcdiStaffEnterpriseRelationVO selectRelationById(Long id);
/**
* 新增员工实体关系
*
* @param addDTO 新增DTO
* @return 结果
*/
int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO);
/**
* 修改员工实体关系
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateRelation(CcdiStaffEnterpriseRelationEditDTO editDTO);
/**
* 批量删除员工实体关系
*
* @param ids 需要删除的主键ID
* @return 结果
*/
int deleteRelationByIds(Long[] ids);
/**
* 导入员工实体关系数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
String importRelation(List<CcdiStaffEnterpriseRelationExcel> excelList);
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
import java.util.List;
/**
* 员工亲属关系异步导入 服务层
*
* @author ruoyi
* @date 2026-02-09
*/
public interface ICcdiStaffFmyRelationImportService {
/**
* 异步导入员工亲属关系数据
*
* @param excelList Excel实体列表
* @param taskId 任务ID
* @param userName 用户名
*/
void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName);
/**
* 查询导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<StaffFmyRelationImportFailureVO> getImportFailures(String taskId);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
}

View File

@@ -1,84 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
import java.util.List;
/**
* 员工亲属关系 服务层
*
* @author ruoyi
* @date 2026-02-09
*/
public interface ICcdiStaffFmyRelationService {
/**
* 查询员工亲属关系列表
*
* @param queryDTO 查询条件
* @return 员工亲属关系VO集合
*/
List<CcdiStaffFmyRelationVO> selectRelationList(CcdiStaffFmyRelationQueryDTO queryDTO);
/**
* 分页查询员工亲属关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工亲属关系VO分页结果
*/
Page<CcdiStaffFmyRelationVO> selectRelationPage(Page<CcdiStaffFmyRelationVO> page, CcdiStaffFmyRelationQueryDTO queryDTO);
/**
* 查询员工亲属关系详情
*
* @param id 主键ID
* @return 员工亲属关系VO
*/
CcdiStaffFmyRelationVO selectRelationById(Long id);
/**
* 新增员工亲属关系
*
* @param addDTO 新增DTO
* @return 结果
*/
int insertRelation(CcdiStaffFmyRelationAddDTO addDTO);
/**
* 修改员工亲属关系
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateRelation(CcdiStaffFmyRelationEditDTO editDTO);
/**
* 批量删除员工亲属关系
*
* @param ids 需要删除的主键ID
* @return 结果
*/
int deleteRelationByIds(Long[] ids);
/**
* 查询员工亲属关系列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工亲属关系Excel实体集合
*/
List<CcdiStaffFmyRelationExcel> selectRelationListForExport(CcdiStaffFmyRelationQueryDTO queryDTO);
/**
* 导入员工亲属关系数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
String importRelation(List<CcdiStaffFmyRelationExcel> excelList);
}

View File

@@ -1,41 +0,0 @@
package com.ruoyi.info.collection.service;
import com.ruoyi.info.collection.domain.excel.CcdiStaffTransferExcel;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffTransferImportFailureVO;
import java.util.List;
/**
* 员工调动记录异步导入 服务层
*
* @author ruoyi
* @date 2026-02-10
*/
public interface ICcdiStaffTransferImportService {
/**
* 异步导入员工调动记录数据
*
* @param excelList Excel实体列表
* @param taskId 任务ID
* @param userName 用户名
*/
void importTransferAsync(List<CcdiStaffTransferExcel> excelList, String taskId, String userName);
/**
* 查询导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
List<StaffTransferImportFailureVO> getImportFailures(String taskId);
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
ImportStatusVO getImportStatus(String taskId);
}

View File

@@ -1,101 +0,0 @@
package com.ruoyi.info.collection.service;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffTransferExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffTransferVO;
import com.ruoyi.common.exception.ServiceException;
import java.util.List;
/**
* 员工调动记录 服务层
*
* @author ruoyi
* @date 2026-02-10
*/
public interface ICcdiStaffTransferService {
/**
* 查询员工调动记录列表
*
* @param queryDTO 查询条件
* @return 员工调动记录VO集合
*/
List<CcdiStaffTransferVO> selectTransferList(CcdiStaffTransferQueryDTO queryDTO);
/**
* 分页查询员工调动记录列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工调动记录VO分页结果
*/
Page<CcdiStaffTransferVO> selectTransferPage(Page<CcdiStaffTransferVO> page, CcdiStaffTransferQueryDTO queryDTO);
/**
* 查询员工调动记录详情
*
* @param id 主键ID
* @return 员工调动记录VO
*/
CcdiStaffTransferVO selectTransferById(Long id);
/**
* 新增员工调动记录
*
* @param addDTO 新增DTO
* @return 结果
*/
int insertTransfer(CcdiStaffTransferAddDTO addDTO);
/**
* 修改员工调动记录
*
* @param editDTO 编辑DTO
* @return 结果
*/
int updateTransfer(CcdiStaffTransferEditDTO editDTO);
/**
* 批量删除员工调动记录
*
* @param ids 需要删除的主键ID
* @return 结果
*/
int deleteTransferByIds(Long[] ids);
/**
* 查询员工调动记录列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工调动记录Excel实体集合
*/
List<CcdiStaffTransferExcel> selectTransferListForExport(CcdiStaffTransferQueryDTO queryDTO);
/**
* 导入员工调动记录数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
String importTransfer(List<CcdiStaffTransferExcel> excelList);
/**
* 新增时校验唯一性
*
* @param addDTO 新增DTO
* @throws ServiceException 如果记录已存在
*/
void checkUniqueForAdd(CcdiStaffTransferAddDTO addDTO);
/**
* 编辑时校验唯一性
*
* @param editDTO 编辑DTO
* @throws ServiceException 如果记录已存在
*/
void checkUniqueForEdit(CcdiStaffTransferEditDTO editDTO);
}

View File

@@ -1,360 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.ImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.IdCardUtil;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* @Author: wkc
* @CreateTime: 2026-02-06
*/
@Service
@EnableAsync
public class CcdiBaseStaffImportServiceImpl implements ICcdiBaseStaffImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiBaseStaffImportServiceImpl.class);
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
public void importBaseStaffAsync(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport, String taskId) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工基础信息", excelList.size(), "系统");
List<CcdiBaseStaff> newRecords = new ArrayList<>();
List<CcdiBaseStaff> updateRecords = new ArrayList<>();
List<ImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的员工ID和身份证号
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工ID", excelList.size());
Set<Long> existingIds = getExistingStaffIds(excelList);
Set<String> existingIdCards = getExistingIdCards(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工ID", existingIds.size());
ImportLogUtils.logBatchQueryComplete(log, taskId, "身份证号", existingIdCards.size());
// 用于跟踪Excel文件内已处理的主键
Set<Long> processedStaffIds = new HashSet<>();
Set<String> processedIdCards = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiBaseStaffExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiBaseStaffAddDTO addDTO = new CcdiBaseStaffAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据(支持更新模式)
validateStaffData(addDTO, isUpdateSupport, existingIds, existingIdCards);
CcdiBaseStaff staff = new CcdiBaseStaff();
BeanUtils.copyProperties(excel, staff);
// 统一检查Excel内重复更新和新增两个分支都需要检查
if (processedStaffIds.contains(excel.getStaffId())) {
throw new RuntimeException(String.format("员工ID[%d]在导入文件中重复,已跳过此条记录", excel.getStaffId()));
}
if (StringUtils.isNotEmpty(excel.getIdCard()) &&
processedIdCards.contains(excel.getIdCard())) {
throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard()));
}
// 检查员工ID是否在数据库中已存在
if (existingIds.contains(excel.getStaffId())) {
// 员工ID已存在于数据库
if (!isUpdateSupport) {
throw new RuntimeException("员工ID已存在且未启用更新支持");
}
// 通过检查,添加到更新列表
updateRecords.add(staff);
} else {
// 员工ID不存在,添加到新增列表
newRecords.add(staff);
}
// 统一标记为已处理(只有成功添加到列表后才会执行到这里)
if (excel.getStaffId() != null) {
processedStaffIds.add(excel.getStaffId());
}
if (StringUtils.isNotEmpty(excel.getIdCard())) {
processedIdCards.add(excel.getIdCard());
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size() + updateRecords.size(), failures.size());
} catch (Exception e) {
ImportFailureVO failure = new ImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("员工ID=%s, 姓名=%s, 身份证号=%s",
excel.getStaffId(), excel.getName(), excel.getIdCard());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 批量更新已有数据(先删除再插入)
if (!updateRecords.isEmpty() && isUpdateSupport) {
ImportLogUtils.logBatchOperationStart(log, taskId, "更新",
(updateRecords.size() + 499) / 500, 500);
baseStaffMapper.insertOrUpdateBatch(updateRecords);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:baseStaff:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size() + updateRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus("baseStaff", taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "员工基础信息",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
@Override
public List<ImportFailureVO> getImportFailures(String taskId) {
String key = "import:baseStaff:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), ImportFailureVO.class);
}
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:baseStaff:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskType, String taskId, String status, ImportResult result) {
String key = "import:baseStaff:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
/**
* 批量查询已存在的员工ID
*/
private Set<Long> getExistingStaffIds(List<CcdiBaseStaffExcel> excelList) {
List<Long> staffIds = excelList.stream()
.map(CcdiBaseStaffExcel::getStaffId)
.filter(Objects::nonNull)
.collect(Collectors.toList());
if (staffIds.isEmpty()) {
return Collections.emptySet();
}
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectBatchIds(staffIds);
return existingStaff.stream()
.map(CcdiBaseStaff::getStaffId)
.collect(Collectors.toSet());
}
/**
* 批量查询数据库中已存在的身份证号
* @param excelList Excel数据列表
* @return 已存在的身份证号集合
*/
private Set<String> getExistingIdCards(List<CcdiBaseStaffExcel> excelList) {
List<String> idCards = excelList.stream()
.map(CcdiBaseStaffExcel::getIdCard)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toList());
if (idCards.isEmpty()) {
return Collections.emptySet();
}
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.in(CcdiBaseStaff::getIdCard, idCards);
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
return existingStaff.stream()
.map(CcdiBaseStaff::getIdCard)
.collect(Collectors.toSet());
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiBaseStaff> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiBaseStaff> subList = list.subList(i, end);
baseStaffMapper.insertBatch(subList);
}
}
/**
* 验证员工数据
*
* @param addDTO 新增DTO
* @param isUpdateSupport 是否支持更新
* @param existingIds 已存在的员工ID集合(导入场景使用,传null表示单条新增)
* @param existingIdCards 已存在的身份证号集合(导入场景使用,传null表示单条新增)
*/
public void validateStaffData(CcdiBaseStaffAddDTO addDTO, Boolean isUpdateSupport, Set<Long> existingIds, Set<String> existingIdCards) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getName())) {
throw new RuntimeException("姓名不能为空");
}
if (addDTO.getStaffId() == null) {
throw new RuntimeException("员工ID不能为空");
}
if (addDTO.getDeptId() == null) {
throw new RuntimeException("所属部门不能为空");
}
if (StringUtils.isEmpty(addDTO.getIdCard())) {
throw new RuntimeException("身份证号不能为空");
}
if (StringUtils.isEmpty(addDTO.getPhone())) {
throw new RuntimeException("电话不能为空");
}
if (StringUtils.isEmpty(addDTO.getStatus())) {
throw new RuntimeException("状态不能为空");
}
// 验证身份证号格式
String idCardError = IdCardUtil.getErrorMessage(addDTO.getIdCard());
if (idCardError != null) {
throw new RuntimeException(idCardError);
}
// 单条新增场景:检查员工ID和身份证号唯一性
if (existingIds == null) {
// 检查员工ID唯一性
if (baseStaffMapper.selectById(addDTO.getStaffId()) != null) {
throw new RuntimeException("该员工ID已存在");
}
// 检查身份证号唯一性
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBaseStaff::getIdCard, addDTO.getIdCard());
if (baseStaffMapper.selectCount(wrapper) > 0) {
throw new RuntimeException("该身份证号已存在");
}
} else {
// 导入场景:如果员工ID不存在,才检查身份证号唯一性
if (!existingIds.contains(addDTO.getStaffId())) {
// 使用批量查询的结果检查身份证号唯一性
if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) {
throw new RuntimeException("该身份证号已存在");
}
}
}
// 验证状态
if (!"0".equals(addDTO.getStatus()) && !"1".equals(addDTO.getStatus())) {
throw new RuntimeException("状态只能填写'在职'或'离职'");
}
}
}

View File

@@ -1,249 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiBaseStaffQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiBaseStaffExcel;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO;
import com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO;
import com.ruoyi.info.collection.enums.EmployeeStatus;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.service.ICcdiBaseStaffImportService;
import com.ruoyi.info.collection.service.ICcdiBaseStaffService;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
/**
* 员工信息 服务层处理
*
* @author ruoyi
* @date 2026-01-28
*/
@Service
public class CcdiBaseStaffServiceImpl implements ICcdiBaseStaffService {
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Resource
private ICcdiBaseStaffImportService importAsyncService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 查询员工列表
*
* @param queryDTO 查询条件
* @return 员工VO集合
*/
@Override
public List<CcdiBaseStaffVO> selectBaseStaffList(CcdiBaseStaffQueryDTO queryDTO) {
LambdaQueryWrapper<CcdiBaseStaff> wrapper = buildQueryWrapper(queryDTO);
List<CcdiBaseStaff> list = baseStaffMapper.selectList(wrapper);
List<CcdiBaseStaffVO> voList = new ArrayList<>();
for (CcdiBaseStaff staff : list) {
voList.add(convertToVO(staff));
}
return voList;
}
/**
* 分页查询员工列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工VO分页结果
*/
@Override
public Page<CcdiBaseStaffVO> selectBaseStaffPage(Page<CcdiBaseStaffVO> page, CcdiBaseStaffQueryDTO queryDTO) {
// 使用关联查询获取部门名称
Page<CcdiBaseStaffVO> voPage = new Page<>(page.getCurrent(), page.getSize());
Page<CcdiBaseStaffVO> resultPage = baseStaffMapper.selectBaseStaffPageWithDept(voPage, queryDTO);
// 设置状态描述
resultPage.getRecords().forEach(vo ->
vo.setStatusDesc(EmployeeStatus.getDescByCode(vo.getStatus()))
);
return resultPage;
}
/**
* 查询员工列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工Excel实体集合
*/
@Override
public List<CcdiBaseStaffExcel> selectBaseStaffListForExport(CcdiBaseStaffQueryDTO queryDTO) {
LambdaQueryWrapper<CcdiBaseStaff> wrapper = buildQueryWrapper(queryDTO);
List<CcdiBaseStaff> list = baseStaffMapper.selectList(wrapper);
return list.stream().map(staff -> {
CcdiBaseStaffExcel excel = new CcdiBaseStaffExcel();
BeanUtils.copyProperties(staff, excel);
return excel;
}).toList();
}
/**
* 查询员工详情
*
* @param staffId 员工ID
* @return 员工VO
*/
@Override
public CcdiBaseStaffVO selectBaseStaffById(Long staffId) {
CcdiBaseStaff staff = baseStaffMapper.selectById(staffId);
return convertToVO(staff);
}
/**
* 新增员工
*
* @param addDTO 新增DTO
* @return 结果
*/
@Override
@Transactional
public int insertBaseStaff(CcdiBaseStaffAddDTO addDTO) {
// 检查员工ID唯一性
if (baseStaffMapper.selectById(addDTO.getStaffId()) != null) {
throw new RuntimeException("该员工ID已存在");
}
// 检查身份证号唯一性
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBaseStaff::getIdCard, addDTO.getIdCard());
if (baseStaffMapper.selectCount(wrapper) > 0) {
throw new RuntimeException("该身份证号已存在");
}
CcdiBaseStaff staff = new CcdiBaseStaff();
BeanUtils.copyProperties(addDTO, staff);
int result = baseStaffMapper.insert(staff);
return result;
}
/**
* 修改员工
*
* @param editDTO 编辑DTO
* @return 结果
*/
@Override
@Transactional
public int updateBaseStaff(CcdiBaseStaffEditDTO editDTO) {
// 检查身份证号唯一性(排除自己)
if (StringUtils.isNotEmpty(editDTO.getIdCard())) {
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.eq(CcdiBaseStaff::getIdCard, editDTO.getIdCard())
.ne(CcdiBaseStaff::getStaffId, editDTO.getStaffId());
if (baseStaffMapper.selectCount(wrapper) > 0) {
throw new RuntimeException("该身份证号已存在");
}
}
CcdiBaseStaff staff = new CcdiBaseStaff();
BeanUtils.copyProperties(editDTO, staff);
int result = baseStaffMapper.updateById(staff);
return result;
}
/**
* 批量删除员工
*
* @param staffIds 需要删除的员工ID
* @return 结果
*/
@Override
@Transactional
public int deleteBaseStaffByIds(Long[] staffIds) {
return baseStaffMapper.deleteBatchIds(List.of(staffIds));
}
/**
* 导入员工数据
*
* @param excelList Excel实体列表
* @param isUpdateSupport 是否更新支持
* @return 结果
*/
@Override
@Transactional
public String importBaseStaff(List<CcdiBaseStaffExcel> excelList, Boolean isUpdateSupport) {
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 初始化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, isUpdateSupport, taskId);
return taskId;
}
/**
* 查询员工下拉列表
* 支持按员工ID或姓名模糊搜索只返回在职员工
*
* @param query 搜索关键词(员工ID或姓名)
* @return 员工选项列表
*/
@Override
public List<CcdiBaseStaffOptionVO> selectStaffOptions(String query) {
return baseStaffMapper.selectStaffOptions(query);
}
/**
* 构建查询条件
*/
private LambdaQueryWrapper<CcdiBaseStaff> buildQueryWrapper(CcdiBaseStaffQueryDTO queryDTO) {
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.like(StringUtils.isNotEmpty(queryDTO.getName()), CcdiBaseStaff::getName, queryDTO.getName())
.eq(queryDTO.getStaffId() != null, CcdiBaseStaff::getStaffId, queryDTO.getStaffId())
.eq(queryDTO.getDeptId() != null, CcdiBaseStaff::getDeptId, queryDTO.getDeptId())
.like(StringUtils.isNotEmpty(queryDTO.getIdCard()), CcdiBaseStaff::getIdCard, queryDTO.getIdCard())
.eq(StringUtils.isNotEmpty(queryDTO.getStatus()), CcdiBaseStaff::getStatus, queryDTO.getStatus())
.orderByDesc(CcdiBaseStaff::getCreateTime);
return wrapper;
}
/**
* 转换为VO对象
*/
public CcdiBaseStaffVO convertToVO(CcdiBaseStaff staff) {
if (staff == null) {
return null;
}
CcdiBaseStaffVO vo = new CcdiBaseStaffVO();
BeanUtils.copyProperties(staff, vo);
vo.setStatusDesc(EmployeeStatus.getDescByCode(staff.getStatus()));
return vo;
}
}

View File

@@ -1,306 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CustEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 信贷客户实体关联信息异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-12
*/
@Service
@EnableAsync
public class CcdiCustEnterpriseRelationImportServiceImpl implements ICcdiCustEnterpriseRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiCustEnterpriseRelationImportServiceImpl.class);
@Resource
private CcdiCustEnterpriseRelationMapper relationMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional
public void importRelationAsync(List<CcdiCustEnterpriseRelationExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "信贷客户实体关联", excelList.size(), userName);
List<CcdiCustEnterpriseRelation> newRecords = new ArrayList<>();
List<CustEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
// 【关键差异】不需要验证身份证号是否存在
// 员工实体关系导入会验证身份证号是否存在于员工表,信贷客户实体关联不需要此验证
// 批量查询已存在的person_id + social_credit_code组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的客户企业关系组合", excelList.size());
Set<String> existingCombinations = getExistingCombinations(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "客户企业关系组合", existingCombinations.size());
// 用于跟踪Excel文件内已处理的组合
Set<String> processedCombinations = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiCustEnterpriseRelationExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiCustEnterpriseRelationAddDTO addDTO = new CcdiCustEnterpriseRelationAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据(不验证身份证号是否存在)
validateRelationData(addDTO);
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
CcdiCustEnterpriseRelation relation = new CcdiCustEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
if (existingCombinations.contains(combination)) {
// 组合已存在,直接报错
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合已存在,请勿重复导入",
excel.getPersonId(), excel.getSocialCreditCode()));
} else if (processedCombinations.contains(combination)) {
// Excel文件内部重复
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合在导入文件中重复,已跳过此条记录",
excel.getPersonId(), excel.getSocialCreditCode()));
} else {
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
// 设置默认值
relation.setStatus(1);
// 信贷客户实体关联的身份标识
relation.setIsEmployee(0);
relation.setIsEmpFamily(0);
relation.setIsCustomer(0);
relation.setIsCustFamily(1); // 信贷客户关联人标识为1
relation.setDataSource("IMPORT");
newRecords.add(relation);
processedCombinations.add(combination); // 标记为已处理
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
CustEnterpriseRelationImportFailureVO failure = new CustEnterpriseRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:custEnterpriseRelation:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "信贷客户实体关联",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
@Override
public List<CustEnterpriseRelationImportFailureVO> getImportFailures(String taskId) {
String key = "import:custEnterpriseRelation:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), CustEnterpriseRelationImportFailureVO.class);
}
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:custEnterpriseRelation:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:custEnterpriseRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
/**
* 批量查询已存在的person_id + social_credit_code组合
* 性能优化一次性查询所有组合避免N+1查询问题
*
* @param excelList Excel导入数据列表
* @return 已存在的组合集合
*/
private Set<String> getExistingCombinations(List<CcdiCustEnterpriseRelationExcel> excelList) {
// 提取所有的person_id和social_credit_code组合
List<String> combinations = excelList.stream()
.map(excel -> excel.getPersonId() + "|" + excel.getSocialCreditCode())
.filter(Objects::nonNull)
.distinct() // 去重
.collect(Collectors.toList());
if (combinations.isEmpty()) {
return Collections.emptySet();
}
// 一次性查询所有已存在的组合
// 优化前循环调用existsByPersonIdAndSocialCreditCodeN次数据库查询
// 优化后批量查询1次数据库查询
return new HashSet<>(relationMapper.batchExistsByCombinations(combinations));
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiCustEnterpriseRelation> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiCustEnterpriseRelation> subList = list.subList(i, end);
relationMapper.insertBatch(subList);
}
}
/**
* 验证信贷客户实体关联数据
* 【关键差异】不验证身份证号是否存在于员工表
*
* @param addDTO 新增DTO
*/
private void validateRelationData(CcdiCustEnterpriseRelationAddDTO addDTO) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPersonId())) {
throw new RuntimeException("身份证号不能为空");
}
if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空");
}
if (StringUtils.isEmpty(addDTO.getEnterpriseName())) {
throw new RuntimeException("企业名称不能为空");
}
// 验证身份证号格式18位
if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) {
throw new RuntimeException("身份证号格式不正确必须为18位有效身份证号");
}
// 验证统一社会信用代码格式18位
if (!addDTO.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("统一社会信用代码格式不正确必须为18位有效统一社会信用代码");
}
// 验证字段长度
if (StringUtils.isNotEmpty(addDTO.getRelationPersonPost()) && addDTO.getRelationPersonPost().length() > 100) {
throw new RuntimeException("关联人在企业的职务长度不能超过100个字符");
}
if (addDTO.getEnterpriseName().length() > 200) {
throw new RuntimeException("企业名称长度不能超过200个字符");
}
// 【注意】不验证身份证号是否存在于员工表
}
}

View File

@@ -1,228 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiCustEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO;
import com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiCustEnterpriseRelationService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 信贷客户实体关联信息 服务层处理
*
* @author ruoyi
* @date 2026-02-12
*/
@Service
public class CcdiCustEnterpriseRelationServiceImpl implements ICcdiCustEnterpriseRelationService {
@Resource
private CcdiCustEnterpriseRelationMapper relationMapper;
@Resource
private ICcdiCustEnterpriseRelationImportService relationImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 查询信贷客户实体关联列表
*
* @param queryDTO 查询条件
* @return 信贷客户实体关联VO集合
*/
@Override
public java.util.List<CcdiCustEnterpriseRelationVO> selectRelationList(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiCustEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords();
}
/**
* 分页查询信贷客户实体关联列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 信贷客户实体关联VO分页结果
*/
@Override
public Page<CcdiCustEnterpriseRelationVO> selectRelationPage(Page<CcdiCustEnterpriseRelationVO> page, CcdiCustEnterpriseRelationQueryDTO queryDTO) {
return relationMapper.selectRelationPage(page, queryDTO);
}
/**
* 查询信贷客户实体关联列表(用于导出)
*
* @param queryDTO 查询条件
* @return 信贷客户实体关联Excel实体集合
*/
@Override
public java.util.List<CcdiCustEnterpriseRelationExcel> selectRelationListForExport(CcdiCustEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiCustEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiCustEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords().stream().map(vo -> {
CcdiCustEnterpriseRelationExcel excel = new CcdiCustEnterpriseRelationExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}).collect(Collectors.toList());
}
/**
* 查询信贷客户实体关联详情
*
* @param id 主键ID
* @return 信贷客户实体关联VO
*/
@Override
public CcdiCustEnterpriseRelationVO selectRelationById(Long id) {
return relationMapper.selectRelationById(id);
}
/**
* 新增信贷客户实体关联
*
* @param addDTO 新增DTO
* @return 结果
*/
@Override
@Transactional
public int insertRelation(CcdiCustEnterpriseRelationAddDTO addDTO) {
// 检查身份证号+统一社会信用代码唯一性
if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) {
throw new RuntimeException("该身份证号和统一社会信用代码组合已存在");
}
CcdiCustEnterpriseRelation relation = new CcdiCustEnterpriseRelation();
BeanUtils.copyProperties(addDTO, relation);
// 设置默认值
// 新增时强制设置状态为有效
relation.setStatus(1);
// 信贷客户实体关联的身份标识默认值
if (relation.getIsEmployee() == null) {
relation.setIsEmployee(0);
}
if (relation.getIsEmpFamily() == null) {
relation.setIsEmpFamily(0);
}
if (relation.getIsCustomer() == null) {
relation.setIsCustomer(0);
}
if (relation.getIsCustFamily() == null) {
relation.setIsCustFamily(1); // 信贷客户关联人标识为1
}
if (StringUtils.isEmpty(relation.getDataSource())) {
relation.setDataSource("MANUAL");
}
int result = relationMapper.insert(relation);
return result;
}
/**
* 修改信贷客户实体关联
*
* @param editDTO 编辑DTO
* @return 结果
*/
@Override
@Transactional
public int updateRelation(CcdiCustEnterpriseRelationEditDTO editDTO) {
// 使用LambdaUpdateWrapper只更新非null字段保护系统字段不被覆盖
LambdaUpdateWrapper<CcdiCustEnterpriseRelation> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(CcdiCustEnterpriseRelation::getId, editDTO.getId());
// 只更新前端可编辑的字段
updateWrapper.set(editDTO.getRelationPersonPost() != null, CcdiCustEnterpriseRelation::getRelationPersonPost, editDTO.getRelationPersonPost());
updateWrapper.set(editDTO.getEnterpriseName() != null, CcdiCustEnterpriseRelation::getEnterpriseName, editDTO.getEnterpriseName());
updateWrapper.set(editDTO.getStatus() != null, CcdiCustEnterpriseRelation::getStatus, editDTO.getStatus());
updateWrapper.set(editDTO.getRemark() != null, CcdiCustEnterpriseRelation::getRemark, editDTO.getRemark());
// 注意:以下字段不可修改
// - personId身份证号业务主键
// - socialCreditCode统一社会信用代码业务主键
// - dataSource数据来源系统字段
// - isEmployee是否为员工系统字段
// - isEmpFamily是否为员工家属系统字段
// - isCustomer是否为客户系统字段
// - isCustFamily是否为客户家属系统字段
int result = relationMapper.update(null, updateWrapper);
return result;
}
/**
* 批量删除信贷客户实体关联
*
* @param ids 需要删除的主键ID
* @return 结果
*/
@Override
@Transactional
public int deleteRelationByIds(Long[] ids) {
return relationMapper.deleteBatchIds(java.util.List.of(ids));
}
/**
* 导入信贷客户实体关联数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importRelation(java.util.List<CcdiCustEnterpriseRelationExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
String statusKey = "import:custEnterpriseRelation:" + 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);
return taskId;
}
}

View File

@@ -1,302 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.ruoyi.info.collection.domain.CcdiCustFmyRelation;
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CustFmyRelationImportFailureVO;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.mapper.CcdiCustFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiCustFmyRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 信贷客户家庭关系异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-11
*/
@Service
@EnableAsync
public class CcdiCustFmyRelationImportServiceImpl implements ICcdiCustFmyRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiCustFmyRelationImportServiceImpl.class);
@Resource
private CcdiCustFmyRelationMapper mapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
@Async
@Transactional(rollbackFor = Exception.class)
public void importRelationsAsync(List<CcdiCustFmyRelationExcel> excels, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "信贷客户家庭关系", excels.size(), userName);
List<CcdiCustFmyRelation> newRecords = new ArrayList<>();
List<CustFmyRelationImportFailureVO> failures = new ArrayList<>();
// 批量查询已存在的 person_id + relation_type + relation_cert_no 组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的客户家庭关系组合", excels.size());
Set<String> existingCombinations = getExistingCombinations(excels);
ImportLogUtils.logBatchQueryComplete(log, taskId, "客户家庭关系组合", existingCombinations.size());
// 用于跟踪Excel文件内已处理的组合
Set<String> processedCombinations = new HashSet<>();
// 分类数据
for (int i = 0; i < excels.size(); i++) {
CcdiCustFmyRelationExcel excel = excels.get(i);
try {
// 验证数据
validateExcelRow(excel);
String combination = excel.getPersonId() + "|" + excel.getRelationType() + "|" + excel.getRelationCertNo();
CcdiCustFmyRelation relation = new CcdiCustFmyRelation();
BeanUtils.copyProperties(excel, relation);
if (existingCombinations.contains(combination)) {
// 组合已存在,直接报错
throw new RuntimeException(String.format(
"信贷客户身份证号[%s]、关系类型[%s]和关系人证件号码[%s]的组合已存在,请勿重复导入",
excel.getPersonId(), excel.getRelationType(), excel.getRelationCertNo()));
} else if (processedCombinations.contains(combination)) {
// Excel文件内部重复
throw new RuntimeException(String.format(
"信贷客户身份证号[%s]、关系类型[%s]和关系人证件号码[%s]的组合在导入文件中重复,已跳过此条记录",
excel.getPersonId(), excel.getRelationType(), excel.getRelationCertNo()));
} else {
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
// 设置默认值
relation.setStatus(1); // 默认有效状态
relation.setIsEmpFamily(false);
relation.setIsCustFamily(true);
relation.setDataSource("IMPORT");
newRecords.add(relation);
processedCombinations.add(combination); // 标记为已处理
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excels.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
CustFmyRelationImportFailureVO failure = new CustFmyRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("信贷客户身份证号=%s, 关系类型=%s, 关系人姓名=%s, 关系人证件号码=%s",
excel.getPersonId(), excel.getRelationType(), excel.getRelationName(), excel.getRelationCertNo());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:custFmyRelation:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excels.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "信贷客户家庭关系",
excels.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 批量查询已存在的 person_id + relation_type + relation_cert_no 组合
* 性能优化:一次性查询所有组合,避免N+1查询问题
*
* @param excels Excel导入数据列表
* @return 已存在的组合集合
*/
private Set<String> getExistingCombinations(List<CcdiCustFmyRelationExcel> excels) {
// 提取所有的 person_id + relation_type + relation_cert_no 组合
List<String> combinations = excels.stream()
.map(excel -> excel.getPersonId() + "|" + excel.getRelationType() + "|" + excel.getRelationCertNo())
.filter(Objects::nonNull)
.distinct() // 去重
.collect(Collectors.toList());
if (combinations.isEmpty()) {
return Collections.emptySet();
}
// 一次性查询所有已存在的组合
// 优化前:循环调用selectExistingRelations,N次数据库查询
// 优化后:批量查询,1次数据库查询
return new HashSet<>(mapper.batchExistsByCombinations(combinations));
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiCustFmyRelation> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiCustFmyRelation> subList = list.subList(i, end);
mapper.insertBatch(subList);
}
}
/**
* 验证Excel行数据
*
* @param excel Excel数据
*/
private void validateExcelRow(CcdiCustFmyRelationExcel excel) {
// 验证必填字段
if (StringUtils.isEmpty(excel.getPersonId())) {
throw new RuntimeException("信贷客户身份证号不能为空");
}
if (StringUtils.isEmpty(excel.getRelationType())) {
throw new RuntimeException("关系类型不能为空");
}
if (StringUtils.isEmpty(excel.getRelationName())) {
throw new RuntimeException("关系人姓名不能为空");
}
if (StringUtils.isEmpty(excel.getRelationCertType())) {
throw new RuntimeException("关系人证件类型不能为空");
}
if (StringUtils.isEmpty(excel.getRelationCertNo())) {
throw new RuntimeException("关系人证件号码不能为空");
}
// 验证身份证号格式(18位)
if (!excel.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) {
throw new RuntimeException("信贷客户身份证号格式不正确,必须为18位有效身份证号");
}
// 验证字段长度
if (excel.getRelationName().length() > 50) {
throw new RuntimeException("关系人姓名长度不能超过50个字符");
}
if (excel.getRelationType().length() > 20) {
throw new RuntimeException("关系类型长度不能超过20个字符");
}
if (excel.getRelationCertNo().length() > 50) {
throw new RuntimeException("关系人证件号码长度不能超过50个字符");
}
if (StringUtils.isNotEmpty(excel.getRelationDesc()) && excel.getRelationDesc().length() > 500) {
throw new RuntimeException("关系描述长度不能超过500个字符");
}
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:custFmyRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
@Override
public List<CustFmyRelationImportFailureVO> getImportFailures(String taskId) {
String key = "import:custFmyRelation:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), CustFmyRelationImportFailureVO.class);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:custFmyRelation:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 验证Excel行数据(兼容旧接口)
*/
@Override
public String validateExcelRow(CcdiCustFmyRelationExcel excel, Integer rowNum) {
try {
validateExcelRow(excel);
return null; // 校验通过
} catch (Exception e) {
return e.getMessage();
}
}
}

View File

@@ -1,153 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiCustFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiCustFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiCustFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiCustFmyRelationVO;
import com.ruoyi.info.collection.domain.vo.CustFmyRelationImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiCustFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiCustFmyRelationImportService;
import com.ruoyi.info.collection.service.ICcdiCustFmyRelationService;
import com.ruoyi.info.collection.utils.EasyExcelUtil;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
/**
* 信贷客户家庭关系Service实现
*
* @author ruoyi
* @date 2026-02-11
*/
@Service
public class CcdiCustFmyRelationServiceImpl implements ICcdiCustFmyRelationService {
@Resource
private CcdiCustFmyRelationMapper mapper;
@Resource
private ICcdiCustFmyRelationImportService importService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Override
public Page<CcdiCustFmyRelationVO> selectRelationPage(CcdiCustFmyRelationQueryDTO query,
Integer pageNum, Integer pageSize) {
Page<CcdiCustFmyRelationVO> page = new Page<>(pageNum, pageSize);
return mapper.selectRelationPage(page, query);
}
@Override
public CcdiCustFmyRelationVO selectRelationById(Long id) {
return mapper.selectRelationById(id);
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean insertRelation(CcdiCustFmyRelationAddDTO addDTO) {
CcdiCustFmyRelation relation = new CcdiCustFmyRelation();
BeanUtils.copyProperties(addDTO, relation);
// 关键设置:客户家庭关系
relation.setIsEmpFamily(false);
relation.setIsCustFamily(true);
relation.setStatus(1);
relation.setDataSource("MANUAL");
return mapper.insert(relation) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean updateRelation(CcdiCustFmyRelationEditDTO editDTO) {
CcdiCustFmyRelation relation = new CcdiCustFmyRelation();
BeanUtils.copyProperties(editDTO, relation);
return mapper.updateById(relation) > 0;
}
@Override
@Transactional(rollbackFor = Exception.class)
public boolean deleteRelationByIds(Long[] ids) {
return mapper.deleteBatchIds(List.of(ids)) > 0;
}
@Override
public void exportRelations(CcdiCustFmyRelationQueryDTO query, HttpServletResponse response) {
// 查询所有符合条件的数据(不分页)
Page<CcdiCustFmyRelationVO> page = new Page<>(1, 10000);
Page<CcdiCustFmyRelationVO> result = mapper.selectRelationPage(page, query);
List<CcdiCustFmyRelationExcel> excels = result.getRecords().stream()
.map(this::convertToExcel)
.toList();
// 使用EasyExcelUtil导出
EasyExcelUtil.exportExcel(response, excels, CcdiCustFmyRelationExcel.class, "信贷客户家庭关系");
}
@Override
public void importTemplate(HttpServletResponse response) {
EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiCustFmyRelationExcel.class, "信贷客户家庭关系");
}
@Override
public String importRelations(List<CcdiCustFmyRelationExcel> excels) {
if (StringUtils.isNull(excels) || excels.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
String statusKey = "import:custFmyRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("taskId", taskId);
statusData.put("status", "PROCESSING");
statusData.put("totalCount", excels.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);
// 调用异步导入服务
importService.importRelationsAsync(excels, taskId, userName);
return taskId;
}
@Override
public List<CustFmyRelationImportFailureVO> getImportFailures(String taskId) {
return importService.getImportFailures(taskId);
}
private CcdiCustFmyRelationExcel convertToExcel(CcdiCustFmyRelationVO vo) {
CcdiCustFmyRelationExcel excel = new CcdiCustFmyRelationExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}
}

View File

@@ -1,334 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffEnterpriseRelationImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工实体关系信息异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-09
*/
@Service
@EnableAsync
public class CcdiStaffEnterpriseRelationImportServiceImpl implements ICcdiStaffEnterpriseRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiStaffEnterpriseRelationImportServiceImpl.class);
@Resource
private CcdiStaffEnterpriseRelationMapper relationMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Override
@Async
@Transactional
public void importRelationAsync(List<CcdiStaffEnterpriseRelationExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工实体关系", excelList.size(), userName);
List<CcdiStaffEnterpriseRelation> newRecords = new ArrayList<>();
List<StaffEnterpriseRelationImportFailureVO> failures = new ArrayList<>();
// 批量验证员工身份证号是否存在
Set<String> excelPersonIds = excelList.stream()
.map(CcdiStaffEnterpriseRelationExcel::getPersonId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toSet());
Set<String> existingPersonIds = new HashSet<>();
if (!excelPersonIds.isEmpty()) {
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size());
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.select(CcdiBaseStaff::getIdCard)
.in(CcdiBaseStaff::getIdCard, excelPersonIds);
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
existingPersonIds = existingStaff.stream()
.map(CcdiBaseStaff::getIdCard)
.collect(Collectors.toSet());
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size());
}
// 批量查询已存在的person_id + social_credit_code组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的员工企业关系组合", excelList.size());
Set<String> existingCombinations = getExistingCombinations(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工企业关系组合", existingCombinations.size());
// 用于跟踪Excel文件内已处理的组合
Set<String> processedCombinations = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiStaffEnterpriseRelationExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiStaffEnterpriseRelationAddDTO addDTO = new CcdiStaffEnterpriseRelationAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据
validateRelationData(addDTO);
// 身份证号存在性检查(在基本验证之后)
if (!existingPersonIds.contains(excel.getPersonId())) {
throw new RuntimeException(String.format(
"第%d行: 身份证号[%s]不存在于员工信息表中,请先添加员工信息",
i + 1, excel.getPersonId()));
}
String combination = excel.getPersonId() + "|" + excel.getSocialCreditCode();
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
BeanUtils.copyProperties(excel, relation);
if (existingCombinations.contains(combination)) {
// 组合已存在,直接报错
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合已存在,请勿重复导入",
excel.getPersonId(), excel.getSocialCreditCode()));
} else if (processedCombinations.contains(combination)) {
// Excel文件内部重复
throw new RuntimeException(String.format("身份证号[%s]和统一社会信用代码[%s]的组合在导入文件中重复,已跳过此条记录",
excel.getPersonId(), excel.getSocialCreditCode()));
} else {
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
// 设置默认值
relation.setStatus(1);
relation.setIsEmployee(0);
relation.setIsEmpFamily(1);
relation.setIsCustomer(0);
relation.setIsCustFamily(0);
relation.setDataSource("IMPORT");
newRecords.add(relation);
processedCombinations.add(combination); // 标记为已处理
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
StaffEnterpriseRelationImportFailureVO failure = new StaffEnterpriseRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("身份证号=%s, 统一社会信用代码=%s, 企业名称=%s",
excel.getPersonId(), excel.getSocialCreditCode(), excel.getEnterpriseName());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:staffEnterpriseRelation:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "员工实体关系",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
@Override
public List<StaffEnterpriseRelationImportFailureVO> getImportFailures(String taskId) {
String key = "import:staffEnterpriseRelation:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), StaffEnterpriseRelationImportFailureVO.class);
}
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:staffEnterpriseRelation:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:staffEnterpriseRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
/**
* 批量查询已存在的person_id + social_credit_code组合
* 性能优化一次性查询所有组合避免N+1查询问题
*
* @param excelList Excel导入数据列表
* @return 已存在的组合集合
*/
private Set<String> getExistingCombinations(List<CcdiStaffEnterpriseRelationExcel> excelList) {
// 提取所有的person_id和social_credit_code组合
List<String> combinations = excelList.stream()
.map(excel -> excel.getPersonId() + "|" + excel.getSocialCreditCode())
.filter(Objects::nonNull)
.distinct() // 去重
.collect(Collectors.toList());
if (combinations.isEmpty()) {
return Collections.emptySet();
}
// 一次性查询所有已存在的组合
// 优化前循环调用existsByPersonIdAndSocialCreditCodeN次数据库查询
// 优化后批量查询1次数据库查询
return new HashSet<>(relationMapper.batchExistsByCombinations(combinations));
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiStaffEnterpriseRelation> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiStaffEnterpriseRelation> subList = list.subList(i, end);
relationMapper.insertBatch(subList);
}
}
/**
* 验证员工实体关系数据
*
* @param addDTO 新增DTO
*/
private void validateRelationData(CcdiStaffEnterpriseRelationAddDTO addDTO) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPersonId())) {
throw new RuntimeException("身份证号不能为空");
}
if (StringUtils.isEmpty(addDTO.getSocialCreditCode())) {
throw new RuntimeException("统一社会信用代码不能为空");
}
if (StringUtils.isEmpty(addDTO.getEnterpriseName())) {
throw new RuntimeException("企业名称不能为空");
}
// 验证身份证号格式18位
if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$")) {
throw new RuntimeException("身份证号格式不正确必须为18位有效身份证号");
}
// 验证统一社会信用代码格式18位
if (!addDTO.getSocialCreditCode().matches("^[0-9A-HJ-NPQRTUWXY]{2}\\d{6}[0-9A-HJ-NPQRTUWXY]{10}$")) {
throw new RuntimeException("统一社会信用代码格式不正确必须为18位有效统一社会信用代码");
}
// 验证字段长度
if (StringUtils.isNotEmpty(addDTO.getRelationPersonPost()) && addDTO.getRelationPersonPost().length() > 100) {
throw new RuntimeException("关联人在企业的职务长度不能超过100个字符");
}
if (addDTO.getEnterpriseName().length() > 200) {
throw new RuntimeException("企业名称长度不能超过200个字符");
}
}
}

View File

@@ -1,227 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.core.conditions.update.LambdaUpdateWrapper;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffEnterpriseRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffEnterpriseRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffEnterpriseRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO;
import com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffEnterpriseRelationService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工实体关系信息 服务层处理
*
* @author ruoyi
* @date 2026-02-09
*/
@Service
public class CcdiStaffEnterpriseRelationServiceImpl implements ICcdiStaffEnterpriseRelationService {
@Resource
private CcdiStaffEnterpriseRelationMapper relationMapper;
@Resource
private ICcdiStaffEnterpriseRelationImportService relationImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 查询员工实体关系列表
*
* @param queryDTO 查询条件
* @return 员工实体关系VO集合
*/
@Override
public java.util.List<CcdiStaffEnterpriseRelationVO> selectRelationList(CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiStaffEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiStaffEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords();
}
/**
* 分页查询员工实体关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工实体关系VO分页结果
*/
@Override
public Page<CcdiStaffEnterpriseRelationVO> selectRelationPage(Page<CcdiStaffEnterpriseRelationVO> page, CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
return relationMapper.selectRelationPage(page, queryDTO);
}
/**
* 查询员工实体关系列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工实体关系Excel实体集合
*/
@Override
public java.util.List<CcdiStaffEnterpriseRelationExcel> selectRelationListForExport(CcdiStaffEnterpriseRelationQueryDTO queryDTO) {
Page<CcdiStaffEnterpriseRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiStaffEnterpriseRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords().stream().map(vo -> {
CcdiStaffEnterpriseRelationExcel excel = new CcdiStaffEnterpriseRelationExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}).collect(Collectors.toList());
}
/**
* 查询员工实体关系详情
*
* @param id 主键ID
* @return 员工实体关系VO
*/
@Override
public CcdiStaffEnterpriseRelationVO selectRelationById(Long id) {
return relationMapper.selectRelationById(id);
}
/**
* 新增员工实体关系
*
* @param addDTO 新增DTO
* @return 结果
*/
@Override
@Transactional
public int insertRelation(CcdiStaffEnterpriseRelationAddDTO addDTO) {
// 检查身份证号+统一社会信用代码唯一性
if (relationMapper.existsByPersonIdAndSocialCreditCode(addDTO.getPersonId(), addDTO.getSocialCreditCode())) {
throw new RuntimeException("该身份证号和统一社会信用代码组合已存在");
}
CcdiStaffEnterpriseRelation relation = new CcdiStaffEnterpriseRelation();
BeanUtils.copyProperties(addDTO, relation);
// 设置默认值
// 新增时强制设置状态为有效
relation.setStatus(1);
if (relation.getIsEmployee() == null) {
relation.setIsEmployee(0);
}
if (relation.getIsEmpFamily() == null) {
relation.setIsEmpFamily(1);
}
if (relation.getIsCustomer() == null) {
relation.setIsCustomer(0);
}
if (relation.getIsCustFamily() == null) {
relation.setIsCustFamily(0);
}
if (StringUtils.isEmpty(relation.getDataSource())) {
relation.setDataSource("MANUAL");
}
int result = relationMapper.insert(relation);
return result;
}
/**
* 修改员工实体关系
*
* @param editDTO 编辑DTO
* @return 结果
*/
@Override
@Transactional
public int updateRelation(CcdiStaffEnterpriseRelationEditDTO editDTO) {
// 使用LambdaUpdateWrapper只更新非null字段保护系统字段不被覆盖
LambdaUpdateWrapper<CcdiStaffEnterpriseRelation> updateWrapper = new LambdaUpdateWrapper<>();
updateWrapper.eq(CcdiStaffEnterpriseRelation::getId, editDTO.getId());
// 只更新前端可编辑的字段
updateWrapper.set(editDTO.getRelationPersonPost() != null, CcdiStaffEnterpriseRelation::getRelationPersonPost, editDTO.getRelationPersonPost());
updateWrapper.set(editDTO.getEnterpriseName() != null, CcdiStaffEnterpriseRelation::getEnterpriseName, editDTO.getEnterpriseName());
updateWrapper.set(editDTO.getStatus() != null, CcdiStaffEnterpriseRelation::getStatus, editDTO.getStatus());
updateWrapper.set(editDTO.getRemark() != null, CcdiStaffEnterpriseRelation::getRemark, editDTO.getRemark());
// 注意:以下字段不可修改
// - personId身份证号业务主键
// - socialCreditCode统一社会信用代码业务主键
// - dataSource数据来源系统字段
// - isEmployee是否为员工系统字段
// - isEmpFamily是否为员工家属系统字段
// - isCustomer是否为客户系统字段
// - isCustFamily是否为客户家属系统字段
int result = relationMapper.update(null, updateWrapper);
return result;
}
/**
* 批量删除员工实体关系
*
* @param ids 需要删除的主键ID
* @return 结果
*/
@Override
@Transactional
public int deleteRelationByIds(Long[] ids) {
return relationMapper.deleteBatchIds(java.util.List.of(ids));
}
/**
* 导入员工实体关系数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importRelation(java.util.List<CcdiStaffEnterpriseRelationExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
String statusKey = "import:staffEnterpriseRelation:" + 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);
return taskId;
}
}

View File

@@ -1,342 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffFmyRelationImportFailureVO;
import com.ruoyi.info.collection.enums.GenderEnum;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工亲属关系异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-09
*/
@Service
@EnableAsync
public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService {
private static final Logger log = LoggerFactory.getLogger(CcdiStaffFmyRelationImportServiceImpl.class);
@Resource
private CcdiStaffFmyRelationMapper relationMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Override
@Async
@Transactional
public void importRelationAsync(List<CcdiStaffFmyRelationExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工亲属关系", excelList.size(), userName);
List<CcdiStaffFmyRelation> newRecords = new ArrayList<>();
List<StaffFmyRelationImportFailureVO> failures = new ArrayList<>();
// 批量验证员工身份证号是否存在
Set<String> excelPersonIds = excelList.stream()
.map(CcdiStaffFmyRelationExcel::getPersonId)
.filter(StringUtils::isNotEmpty)
.collect(Collectors.toSet());
Set<String> existingPersonIds = new HashSet<>();
if (!excelPersonIds.isEmpty()) {
ImportLogUtils.logBatchQueryStart(log, taskId, "员工身份证号", excelPersonIds.size());
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.select(CcdiBaseStaff::getIdCard)
.in(CcdiBaseStaff::getIdCard, excelPersonIds);
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
existingPersonIds = existingStaff.stream()
.map(CcdiBaseStaff::getIdCard)
.collect(Collectors.toSet());
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工身份证号", existingPersonIds.size());
}
// ========== 第一步:批量唯一性校验 ==========
// 1. 提取Excel中所有的relationCertNopersonId已在前面提取
Set<String> excelRelationCertNos = new HashSet<>();
for (CcdiStaffFmyRelationExcel excel : excelList) {
if (StringUtils.isNotEmpty(excel.getRelationCertNo())) {
excelRelationCertNos.add(excel.getRelationCertNo());
}
}
// 2. 批量查询数据库中已存在的记录
Set<String> existingKeys = new HashSet<>();
if (!excelRelationCertNos.isEmpty()) {
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的亲属关系", excelList.size());
List<CcdiStaffFmyRelation> existingRecords = relationMapper.selectExistingRelations(
new ArrayList<>(excelPersonIds),
new ArrayList<>(excelRelationCertNos)
);
// 3. 构建已存在记录的唯一键集合personId + relationCertNo
for (CcdiStaffFmyRelation existing : existingRecords) {
String key = existing.getPersonId() + "|" + existing.getRelationCertNo();
existingKeys.add(key);
}
ImportLogUtils.logBatchQueryComplete(log, taskId, "亲属关系", existingKeys.size());
}
// ========== 第二步:处理数据 ==========
// 用于跟踪Excel文件内已处理的唯一键
Set<String> processedKeys = new HashSet<>();
for (int i = 0; i < excelList.size(); i++) {
CcdiStaffFmyRelationExcel excel = excelList.get(i);
try {
// 转换为AddDTO进行验证
CcdiStaffFmyRelationAddDTO addDTO = new CcdiStaffFmyRelationAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据
validateRelationData(addDTO);
// 身份证号存在性检查(在基本验证之后)
if (!existingPersonIds.contains(excel.getPersonId())) {
throw new RuntimeException(String.format(
"第%d行: 身份证号[%s]不存在于员工信息表中,请先添加员工信息",
i + 1, excel.getPersonId()));
}
// 生成唯一键
String uniqueKey = excel.getPersonId() + "|" + excel.getRelationCertNo();
// 检查是否在文件内重复
if (processedKeys.contains(uniqueKey)) {
throw new RuntimeException(String.format("员工[%s]的关系人[%s]在导入文件中重复", excel.getPersonId(), excel.getRelationCertNo()));
}
// 检查是否在数据库中已存在(批量校验的结果)
if (existingKeys.contains(uniqueKey)) {
throw new RuntimeException(String.format("员工[%s]的关系人[%s]已存在", excel.getPersonId(), excel.getRelationCertNo()));
}
// 从已验证和转换过的addDTO创建relation对象
CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation();
BeanUtils.copyProperties(addDTO, relation);
relation.setCreatedBy(userName);
relation.setUpdatedBy(userName);
relation.setIsEmpFamily(true);
relation.setIsCustFamily(false);
relation.setDataSource("IMPORT");
// 显式设置状态为有效status=1
relation.setStatus(1);
newRecords.add(relation);
processedKeys.add(uniqueKey);
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("员工身份证号=%s, 关系人=%s(%s)",
excel.getPersonId(), excel.getRelationName(), excel.getRelationCertNo());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:staffFmyRelation:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "员工亲属关系",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 获取导入失败记录
*
* @param taskId 任务ID
* @return 失败记录列表
*/
@Override
public List<StaffFmyRelationImportFailureVO> getImportFailures(String taskId) {
String key = "import:staffFmyRelation:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), StaffFmyRelationImportFailureVO.class);
}
/**
* 查询导入状态
*
* @param taskId 任务ID
* @return 导入状态信息
*/
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:staffFmyRelation:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:staffFmyRelation:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiStaffFmyRelation> list, int batchSize) {
// 使用真正的批量插入,分批次执行以提高性能
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiStaffFmyRelation> subList = list.subList(i, end);
relationMapper.insertBatch(subList);
}
}
/**
* 验证员工亲属关系数据
*
* @param addDTO 新增DTO
*/
private void validateRelationData(CcdiStaffFmyRelationAddDTO addDTO) {
// 验证必填字段
if (StringUtils.isEmpty(addDTO.getPersonId())) {
throw new RuntimeException("员工身份证号不能为空");
}
if (StringUtils.isEmpty(addDTO.getRelationType())) {
throw new RuntimeException("关系类型不能为空");
}
if (StringUtils.isEmpty(addDTO.getRelationName())) {
throw new RuntimeException("关系人姓名不能为空");
}
if (StringUtils.isEmpty(addDTO.getRelationCertType())) {
throw new RuntimeException("关系人证件类型不能为空");
}
if (StringUtils.isEmpty(addDTO.getRelationCertNo())) {
throw new RuntimeException("关系人证件号码不能为空");
}
// 验证身份证号格式
if (!addDTO.getPersonId().matches("^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[\\dXx]$")) {
throw new RuntimeException("员工身份证号格式不正确");
}
// 验证手机号格式(如果提供)
if (StringUtils.isNotEmpty(addDTO.getMobilePhone1()) && !addDTO.getMobilePhone1().matches("^1[3-9]\\d{9}$")) {
throw new RuntimeException("手机号码1格式不正确");
}
if (StringUtils.isNotEmpty(addDTO.getMobilePhone2()) && !addDTO.getMobilePhone2().matches("^1[3-9]\\d{9}$")) {
throw new RuntimeException("手机号码2格式不正确");
}
// 验证性别值(如果提供)
if (StringUtils.isNotEmpty(addDTO.getGender())) {
try {
// 使用GenderEnum进行标准化支持中文男、女、其他和英文M、F、O
String normalizedGender = GenderEnum.normalize(addDTO.getGender());
addDTO.setGender(normalizedGender);
} catch (IllegalArgumentException e) {
throw new RuntimeException("性别只能是:男、女、其他 或 M、F、O");
}
}
}
}

View File

@@ -1,187 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffFmyRelation;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffFmyRelationQueryDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffFmyRelationExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO;
import com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationImportService;
import com.ruoyi.info.collection.service.ICcdiStaffFmyRelationService;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工亲属关系 服务层处理
*
* @author ruoyi
* @date 2026-02-09
*/
@Service
public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationService {
@Resource
private CcdiStaffFmyRelationMapper relationMapper;
@Resource
private ICcdiStaffFmyRelationImportService relationImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
/**
* 查询员工亲属关系列表
*
* @param queryDTO 查询条件
* @return 员工亲属关系VO集合
*/
@Override
public java.util.List<CcdiStaffFmyRelationVO> selectRelationList(CcdiStaffFmyRelationQueryDTO queryDTO) {
Page<CcdiStaffFmyRelationVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiStaffFmyRelationVO> resultPage = relationMapper.selectRelationPage(page, queryDTO);
return resultPage.getRecords();
}
/**
* 分页查询员工亲属关系列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工亲属关系VO分页结果
*/
@Override
public Page<CcdiStaffFmyRelationVO> selectRelationPage(Page<CcdiStaffFmyRelationVO> page, CcdiStaffFmyRelationQueryDTO queryDTO) {
return relationMapper.selectRelationPage(page, queryDTO);
}
/**
* 查询员工亲属关系列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工亲属关系Excel实体集合
*/
@Override
public java.util.List<CcdiStaffFmyRelationExcel> selectRelationListForExport(CcdiStaffFmyRelationQueryDTO queryDTO) {
return relationMapper.selectRelationListForExport(queryDTO).stream().map(vo -> {
CcdiStaffFmyRelationExcel excel = new CcdiStaffFmyRelationExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}).collect(Collectors.toList());
}
/**
* 查询员工亲属关系详情
*
* @param id 主键ID
* @return 员工亲属关系VO
*/
@Override
public CcdiStaffFmyRelationVO selectRelationById(Long id) {
return relationMapper.selectRelationById(id);
}
/**
* 新增员工亲属关系
*
* @param addDTO 新增DTO
* @return 结果
*/
@Override
@Transactional
public int insertRelation(CcdiStaffFmyRelationAddDTO addDTO) {
CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation();
BeanUtils.copyProperties(addDTO, relation);
// 设置默认值
relation.setIsEmpFamily(true);
relation.setIsCustFamily(false);
relation.setDataSource("MANUAL");
if (relation.getStatus() == null) {
relation.setStatus(1);
}
int result = relationMapper.insert(relation);
return result;
}
/**
* 修改员工亲属关系
*
* @param editDTO 编辑DTO
* @return 结果
*/
@Override
@Transactional
public int updateRelation(CcdiStaffFmyRelationEditDTO editDTO) {
CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation();
BeanUtils.copyProperties(editDTO, relation);
int result = relationMapper.updateById(relation);
return result;
}
/**
* 批量删除员工亲属关系
*
* @param ids 需要删除的主键ID
* @return 结果
*/
@Override
@Transactional
public int deleteRelationByIds(Long[] ids) {
return relationMapper.deleteBatchIds(java.util.List.of(ids));
}
/**
* 导入员工亲属关系数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importRelation(java.util.List<CcdiStaffFmyRelationExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
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);
return taskId;
}
}

View File

@@ -1,380 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.alibaba.fastjson2.JSON;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.info.collection.domain.CcdiBaseStaff;
import com.ruoyi.info.collection.domain.CcdiStaffTransfer;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferAddDTO;
import com.ruoyi.info.collection.domain.excel.CcdiStaffTransferExcel;
import com.ruoyi.info.collection.domain.vo.ImportResult;
import com.ruoyi.info.collection.domain.vo.ImportStatusVO;
import com.ruoyi.info.collection.domain.vo.StaffTransferImportFailureVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffTransferMapper;
import com.ruoyi.info.collection.service.ICcdiStaffTransferImportService;
import com.ruoyi.info.collection.utils.ImportLogUtils;
import com.ruoyi.common.core.domain.entity.SysDept;
import com.ruoyi.common.utils.DictUtils;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.system.mapper.SysDeptMapper;
import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.EnableAsync;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工调动记录异步导入服务层处理
*
* @author ruoyi
* @date 2026-02-10
*/
@Slf4j
@Service
@EnableAsync
public class CcdiStaffTransferImportServiceImpl implements ICcdiStaffTransferImportService {
@Resource
private CcdiStaffTransferMapper transferMapper;
@Resource
private SysDeptMapper deptMapper;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private CcdiBaseStaffMapper baseStaffMapper;
@Override
@Async
@Transactional
public void importTransferAsync(List<CcdiStaffTransferExcel> excelList, String taskId, String userName) {
long startTime = System.currentTimeMillis();
// 记录导入开始
ImportLogUtils.logImportStart(log, taskId, "员工调动记录", excelList.size(), userName);
List<CcdiStaffTransfer> newRecords = new ArrayList<>();
List<StaffTransferImportFailureVO> failures = new ArrayList<>();
// 批量查询员工ID存在性
Set<Long> excelStaffIds = excelList.stream()
.map(CcdiStaffTransferExcel::getStaffId)
.filter(Objects::nonNull)
.collect(Collectors.toSet());
Set<Long> existingStaffIds = new HashSet<>();
if (!excelStaffIds.isEmpty()) {
ImportLogUtils.logBatchQueryStart(log, taskId, "员工ID", excelStaffIds.size());
LambdaQueryWrapper<CcdiBaseStaff> wrapper = new LambdaQueryWrapper<>();
wrapper.select(CcdiBaseStaff::getStaffId)
.in(CcdiBaseStaff::getStaffId, excelStaffIds);
List<CcdiBaseStaff> existingStaff = baseStaffMapper.selectList(wrapper);
existingStaffIds = existingStaff.stream()
.map(CcdiBaseStaff::getStaffId)
.collect(Collectors.toSet());
ImportLogUtils.logBatchQueryComplete(log, taskId, "员工ID", existingStaffIds.size());
}
// 批量查询已存在的唯一键组合
ImportLogUtils.logBatchQueryStart(log, taskId, "已存在的调动记录", excelList.size());
Set<String> existingKeys = getExistingTransferKeys(excelList);
ImportLogUtils.logBatchQueryComplete(log, taskId, "调动记录", existingKeys.size());
// 用于检测Excel内部的重复键
Set<String> excelProcessedKeys = new HashSet<>();
// 分类数据
for (int i = 0; i < excelList.size(); i++) {
CcdiStaffTransferExcel excel = excelList.get(i);
try {
// 员工ID存在性检查
if (excel.getStaffId() != null && !existingStaffIds.contains(excel.getStaffId())) {
throw new RuntimeException(String.format(
"第%d行: 员工ID %s 不存在",
i + 1, excel.getStaffId()));
}
// 转换为AddDTO进行验证
CcdiStaffTransferAddDTO addDTO = new CcdiStaffTransferAddDTO();
BeanUtils.copyProperties(excel, addDTO);
// 验证数据
validateTransferData(addDTO);
// 生成唯一键
String uniqueKey = buildUniqueKey(addDTO.getStaffId(), addDTO.getDeptIdBefore(),
addDTO.getDeptIdAfter(), addDTO.getTransferDate());
if (existingKeys.contains(uniqueKey)) {
// 数据库中已存在
throw new RuntimeException(String.format(
"第%d行: 该员工在%s的调动记录已存在从%s调往%s",
i + 1,
addDTO.getTransferDate(),
addDTO.getDeptNameBefore(),
addDTO.getDeptNameAfter()
));
} else if (excelProcessedKeys.contains(uniqueKey)) {
// Excel内部重复
throw new RuntimeException(String.format(
"该记录与Excel第%d行重复",
excelProcessedKeys.size() + 1
));
} else {
CcdiStaffTransfer transfer = new CcdiStaffTransfer();
// 从addDTO复制因为validateTransferData已经补全了部门名称
BeanUtils.copyProperties(addDTO, transfer);
transfer.setCreatedBy(userName);
transfer.setUpdatedBy(userName);
newRecords.add(transfer);
excelProcessedKeys.add(uniqueKey);
}
// 记录进度
ImportLogUtils.logProgress(log, taskId, i + 1, excelList.size(),
newRecords.size(), failures.size());
} catch (Exception e) {
StaffTransferImportFailureVO failure = new StaffTransferImportFailureVO();
BeanUtils.copyProperties(excel, failure);
failure.setErrorMessage(e.getMessage());
failures.add(failure);
// 记录验证失败日志
String keyData = String.format("员工ID=%s, 调动类型=%s, 调动日期=%s, 调动前部门ID=%s, 调动后部门ID=%s",
excel.getStaffId(), excel.getTransferType(), excel.getTransferDate(),
excel.getDeptIdBefore(), excel.getDeptIdAfter());
ImportLogUtils.logValidationError(log, taskId, i + 1, e.getMessage(), keyData);
}
}
// 批量插入新数据
if (!newRecords.isEmpty()) {
ImportLogUtils.logBatchOperationStart(log, taskId, "插入",
(newRecords.size() + 499) / 500, 500);
saveBatch(newRecords, 500);
}
// 保存失败记录到Redis
if (!failures.isEmpty()) {
try {
String failuresKey = "import:staffTransfer:" + taskId + ":failures";
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
ImportLogUtils.logRedisOperation(log, taskId, "保存失败记录", failures.size());
} catch (Exception e) {
ImportLogUtils.logRedisError(log, taskId, "保存失败记录", e);
}
}
ImportResult result = new ImportResult();
result.setTotalCount(excelList.size());
result.setSuccessCount(newRecords.size());
result.setFailureCount(failures.size());
// 更新最终状态
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
updateImportStatus(taskId, finalStatus, result);
// 记录导入完成
long duration = System.currentTimeMillis() - startTime;
ImportLogUtils.logImportComplete(log, taskId, "员工调动记录",
excelList.size(), result.getSuccessCount(), result.getFailureCount(), duration);
}
/**
* 批量查询已存在的调动记录唯一键
*
* @param excelList Excel数据列表
* @return 已存在的唯一键集合
*/
private Set<String> getExistingTransferKeys(List<CcdiStaffTransferExcel> excelList) {
// 提取所有有效的唯一键
Set<String> allKeys = excelList.stream()
.filter(excel -> excel.getStaffId() != null
&& excel.getDeptIdBefore() != null
&& excel.getDeptIdAfter() != null
&& excel.getTransferDate() != null)
.map(excel -> buildUniqueKey(excel.getStaffId(), excel.getDeptIdBefore(),
excel.getDeptIdAfter(), excel.getTransferDate()))
.collect(Collectors.toSet());
if (allKeys.isEmpty()) {
return Collections.emptySet();
}
// 查询数据库中已存在的记录
LambdaQueryWrapper<CcdiStaffTransfer> wrapper = new LambdaQueryWrapper<>();
wrapper.select(CcdiStaffTransfer::getStaffId,
CcdiStaffTransfer::getDeptIdBefore,
CcdiStaffTransfer::getDeptIdAfter,
CcdiStaffTransfer::getTransferDate);
List<CcdiStaffTransfer> existingTransfers = transferMapper.selectList(wrapper);
// 构建已存在的唯一键集合
return existingTransfers.stream()
.map(t -> buildUniqueKey(t.getStaffId(), t.getDeptIdBefore(),
t.getDeptIdAfter(), t.getTransferDate()))
.collect(Collectors.toSet());
}
/**
* 构建唯一键
*
* @param staffId 员工ID
* @param deptIdBefore 调动前部门ID
* @param deptIdAfter 调动后部门ID
* @param transferDate 调动日期
* @return 唯一键字符串
*/
private String buildUniqueKey(Long staffId, Long deptIdBefore, Long deptIdAfter, Date transferDate) {
String dateStr = new java.text.SimpleDateFormat("yyyy-MM-dd").format(transferDate);
return staffId + "_" + deptIdBefore + "_" + deptIdAfter + "_" + dateStr;
}
/**
* 验证员工调动记录数据
*
* @param addDTO 新增DTO
*/
private void validateTransferData(CcdiStaffTransferAddDTO addDTO) {
// 验证必填字段
if (addDTO.getStaffId() == null) {
throw new RuntimeException("员工ID不能为空");
}
if (StringUtils.isEmpty(addDTO.getTransferType())) {
throw new RuntimeException("调动类型不能为空");
}
if (addDTO.getTransferDate() == null) {
throw new RuntimeException("调动日期不能为空");
}
// 验证调动前部门ID
if (addDTO.getDeptIdBefore() == null) {
throw new RuntimeException("调动前部门ID不能为空");
}
// 验证调动后部门ID
if (addDTO.getDeptIdAfter() == null) {
throw new RuntimeException("调动后部门ID不能为空");
}
// 将调动类型从中文转换为码值
String transferTypeCode = DictUtils.getDictValue("ccdi_transfer_type", addDTO.getTransferType());
if (StringUtils.isEmpty(transferTypeCode)) {
throw new RuntimeException("调动类型[" + addDTO.getTransferType() + "]无效,请检查字典数据");
}
addDTO.setTransferType(transferTypeCode);
// 验证部门ID是否存在并获取部门名称
String deptNameBefore = getDeptNameById(addDTO.getDeptIdBefore());
addDTO.setDeptNameBefore(deptNameBefore);
String deptNameAfter = getDeptNameById(addDTO.getDeptIdAfter());
addDTO.setDeptNameAfter(deptNameAfter);
}
/**
* 根据部门ID查询部门名称
*
* @param deptId 部门ID
* @return 部门名称
* @throws RuntimeException 如果部门不存在
*/
private String getDeptNameById(Long deptId) {
if (deptId == null) {
return null;
}
SysDept dept = deptMapper.selectDeptById(deptId);
if (dept == null) {
throw new RuntimeException("部门ID " + deptId + " 不存在,请检查部门信息");
}
return dept.getDeptName();
}
/**
* 批量保存
*/
private void saveBatch(List<CcdiStaffTransfer> list, int batchSize) {
for (int i = 0; i < list.size(); i += batchSize) {
int end = Math.min(i + batchSize, list.size());
List<CcdiStaffTransfer> subList = list.subList(i, end);
transferMapper.insertBatch(subList);
}
}
/**
* 更新导入状态
*/
private void updateImportStatus(String taskId, String status, ImportResult result) {
String key = "import:staffTransfer:" + taskId;
Map<String, Object> statusData = new HashMap<>();
statusData.put("status", status);
statusData.put("totalCount", result.getTotalCount());
statusData.put("successCount", result.getSuccessCount());
statusData.put("failureCount", result.getFailureCount());
statusData.put("progress", 100);
statusData.put("endTime", System.currentTimeMillis());
if ("SUCCESS".equals(status)) {
statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据");
} else {
statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "");
}
redisTemplate.opsForHash().putAll(key, statusData);
}
@Override
public ImportStatusVO getImportStatus(String taskId) {
String key = "import:staffTransfer:" + taskId;
Boolean hasKey = redisTemplate.hasKey(key);
if (Boolean.FALSE.equals(hasKey)) {
throw new RuntimeException("任务不存在或已过期");
}
Map<Object, Object> statusMap = redisTemplate.opsForHash().entries(key);
ImportStatusVO statusVO = new ImportStatusVO();
statusVO.setTaskId((String) statusMap.get("taskId"));
statusVO.setStatus((String) statusMap.get("status"));
statusVO.setTotalCount((Integer) statusMap.get("totalCount"));
statusVO.setSuccessCount((Integer) statusMap.get("successCount"));
statusVO.setFailureCount((Integer) statusMap.get("failureCount"));
statusVO.setProgress((Integer) statusMap.get("progress"));
statusVO.setStartTime((Long) statusMap.get("startTime"));
statusVO.setEndTime((Long) statusMap.get("endTime"));
statusVO.setMessage((String) statusMap.get("message"));
return statusVO;
}
@Override
public List<StaffTransferImportFailureVO> getImportFailures(String taskId) {
String key = "import:staffTransfer:" + taskId + ":failures";
Object failuresObj = redisTemplate.opsForValue().get(key);
if (failuresObj == null) {
return Collections.emptyList();
}
return JSON.parseArray(JSON.toJSONString(failuresObj), StaffTransferImportFailureVO.class);
}
}

View File

@@ -1,227 +0,0 @@
package com.ruoyi.info.collection.service.impl;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.ruoyi.info.collection.domain.CcdiStaffTransfer;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferAddDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferEditDTO;
import com.ruoyi.info.collection.domain.dto.CcdiStaffTransferQueryDTO;
import com.ruoyi.info.collection.domain.dto.TransferUniqueKey;
import com.ruoyi.info.collection.domain.excel.CcdiStaffTransferExcel;
import com.ruoyi.info.collection.domain.vo.CcdiStaffTransferVO;
import com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper;
import com.ruoyi.info.collection.mapper.CcdiStaffTransferMapper;
import com.ruoyi.info.collection.service.ICcdiStaffTransferImportService;
import com.ruoyi.info.collection.service.ICcdiStaffTransferService;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.SecurityUtils;
import com.ruoyi.common.utils.StringUtils;
import jakarta.annotation.Resource;
import org.springframework.beans.BeanUtils;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import java.text.SimpleDateFormat;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
/**
* 员工调动记录 服务层处理
*
* @author ruoyi
* @date 2026-02-10
*/
@Service
public class CcdiStaffTransferServiceImpl implements ICcdiStaffTransferService {
@Resource
private CcdiStaffTransferMapper transferMapper;
@Resource
private ICcdiStaffTransferImportService transferImportService;
@Resource
private RedisTemplate<String, Object> redisTemplate;
@Resource
private CcdiBaseStaffMapper staffMapper;
/**
* 查询员工调动记录列表
*
* @param queryDTO 查询条件
* @return 员工调动记录VO集合
*/
@Override
public java.util.List<CcdiStaffTransferVO> selectTransferList(CcdiStaffTransferQueryDTO queryDTO) {
Page<CcdiStaffTransferVO> page = new Page<>(1, Integer.MAX_VALUE);
Page<CcdiStaffTransferVO> resultPage = transferMapper.selectTransferPage(page, queryDTO);
return resultPage.getRecords();
}
/**
* 分页查询员工调动记录列表
*
* @param page 分页对象
* @param queryDTO 查询条件
* @return 员工调动记录VO分页结果
*/
@Override
public Page<CcdiStaffTransferVO> selectTransferPage(Page<CcdiStaffTransferVO> page, CcdiStaffTransferQueryDTO queryDTO) {
return transferMapper.selectTransferPage(page, queryDTO);
}
/**
* 查询员工调动记录列表(用于导出)
*
* @param queryDTO 查询条件
* @return 员工调动记录Excel实体集合
*/
@Override
public java.util.List<CcdiStaffTransferExcel> selectTransferListForExport(CcdiStaffTransferQueryDTO queryDTO) {
return transferMapper.selectTransferListForExport(queryDTO).stream().map(vo -> {
CcdiStaffTransferExcel excel = new CcdiStaffTransferExcel();
BeanUtils.copyProperties(vo, excel);
return excel;
}).collect(Collectors.toList());
}
/**
* 查询员工调动记录详情
*
* @param id 主键ID
* @return 员工调动记录VO
*/
@Override
public CcdiStaffTransferVO selectTransferById(Long id) {
return transferMapper.selectTransferById(id);
}
/**
* 新增员工调动记录
*
* @param addDTO 新增DTO
* @return 结果
*/
@Override
@Transactional
public int insertTransfer(CcdiStaffTransferAddDTO addDTO) {
// 唯一性校验
checkUniqueForAdd(addDTO);
CcdiStaffTransfer transfer = new CcdiStaffTransfer();
BeanUtils.copyProperties(addDTO, transfer);
int result = transferMapper.insert(transfer);
return result;
}
/**
* 修改员工调动记录
*
* @param editDTO 编辑DTO
* @return 结果
*/
@Override
@Transactional
public int updateTransfer(CcdiStaffTransferEditDTO editDTO) {
// 唯一性校验(排除当前记录)
checkUniqueForEdit(editDTO);
CcdiStaffTransfer transfer = new CcdiStaffTransfer();
BeanUtils.copyProperties(editDTO, transfer);
int result = transferMapper.updateById(transfer);
return result;
}
/**
* 批量删除员工调动记录
*
* @param ids 需要删除的主键ID
* @return 结果
*/
@Override
@Transactional
public int deleteTransferByIds(Long[] ids) {
return transferMapper.deleteBatchIds(java.util.List.of(ids));
}
/**
* 导入员工调动记录数据(异步)
*
* @param excelList Excel实体列表
* @return 任务ID
*/
@Override
@Transactional
public String importTransfer(java.util.List<CcdiStaffTransferExcel> excelList) {
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
throw new RuntimeException("至少需要一条数据");
}
// 生成任务ID
String taskId = UUID.randomUUID().toString();
long startTime = System.currentTimeMillis();
// 获取当前用户名
String userName = SecurityUtils.getUsername();
// 初始化Redis状态
String statusKey = "import:staffTransfer:" + 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);
// 调用异步导入服务
transferImportService.importTransferAsync(excelList, taskId, userName);
return taskId;
}
/**
* 新增时校验唯一性
*
* @param addDTO 新增DTO
* @throws ServiceException 如果记录已存在
*/
@Override
public void checkUniqueForAdd(CcdiStaffTransferAddDTO addDTO) {
TransferUniqueKey key = TransferUniqueKey.from(addDTO);
CcdiStaffTransfer existing = transferMapper.checkExists(key);
if (existing != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(addDTO.getTransferDate());
throw new ServiceException("该员工在 [" + dateStr + "] 的调动记录已存在(从[" +
addDTO.getDeptNameBefore() + "]调往[" + addDTO.getDeptNameAfter() + "]");
}
}
/**
* 编辑时校验唯一性
*
* @param editDTO 编辑DTO
* @throws ServiceException 如果记录已存在
*/
@Override
public void checkUniqueForEdit(CcdiStaffTransferEditDTO editDTO) {
TransferUniqueKey key = TransferUniqueKey.from(editDTO);
CcdiStaffTransfer existing = transferMapper.checkExistsExcludeId(key, editDTO.getId());
if (existing != null) {
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
String dateStr = sdf.format(editDTO.getTransferDate());
throw new ServiceException("该员工在 [" + dateStr + "] 的调动记录已存在(从[" +
editDTO.getDeptNameBefore() + "]调往[" + editDTO.getDeptNameAfter() + "]");
}
}
}

View File

@@ -1,248 +0,0 @@
package com.ruoyi.info.collection.utils;
import org.slf4j.Logger;
/**
* 导入日志工具类
* 提供统一的日志格式和进度计算
*
* @author ruoyi
* @date 2026-02-11
*/
public class ImportLogUtils {
/**
* 记录导入开始
*
* @param log 日志记录器
* @param taskId 任务ID
* @param moduleName 模块名称
* @param totalCount 总数据量
* @param userName 操作人
*/
public static void logImportStart(Logger log, String taskId, String moduleName,
int totalCount, String userName) {
log.info("[任务ID: {}] 开始异步导入{},数据量: {}条,操作人: {}",
taskId, moduleName, totalCount, userName);
}
/**
* 记录批量查询开始
*
* @param log 日志记录器
* @param taskId 任务ID
* @param queryDesc 查询描述
* @param queryCount 查询数量
*/
public static void logBatchQueryStart(Logger log, String taskId, String queryDesc, int queryCount) {
log.info("[任务ID: {}] 批量查询{},查询数量: {}个", taskId, queryDesc, queryCount);
}
/**
* 记录批量查询完成
*
* @param log 日志记录器
* @param taskId 任务ID
* @param queryDesc 查询描述
* @param existingCount 已存在数量
*/
public static void logBatchQueryComplete(Logger log, String taskId, String queryDesc, int existingCount) {
log.info("[任务ID: {}] 查询完成,已存在{}条", taskId, queryDesc, existingCount);
}
/**
* 记录进度(智能判断是否需要输出)
* 每100条或每10%输出一次
*
* @param log 日志记录器
* @param taskId 任务ID
* @param current 当前处理数
* @param total 总数
* @param success 成功数
* @param failure 失败数
*/
public static void logProgress(Logger log, String taskId, int current, int total,
int success, int failure) {
if (current <= 0) {
return;
}
// 每100条或每10%输出一次进度
boolean shouldLog = (current % 100 == 0) ||
(current * 10 / total > (current - 1) * 10 / total) ||
(current == total);
if (shouldLog) {
int progress = current * 100 / total;
log.info("[任务ID: {}] 数据处理进度: {}/{} ({}%), 成功: {}条, 失败: {}条",
taskId, current, total, progress, success, failure);
}
}
/**
* 记录数据验证失败
*
* @param log 日志记录器
* @param taskId 任务ID
* @param rowNum 行号
* @param errorMsg 错误消息
* @param keyData 关键数据可为null
*/
public static void logValidationError(Logger log, String taskId, int rowNum,
String errorMsg, String keyData) {
log.warn("[任务ID: {}] [第{}行] 数据验证失败: {}", taskId, rowNum, errorMsg);
if (keyData != null && !keyData.isEmpty()) {
log.warn("[任务ID: {}] 失败数据详情: {}", taskId, keyData);
}
}
/**
* 记录批量操作开始
*
* @param log 日志记录器
* @param taskId 任务ID
* @param operation 操作描述
* @param totalBatch 总批次数
* @param batchSize 每批大小
*/
public static void logBatchOperationStart(Logger log, String taskId, String operation,
int totalBatch, int batchSize) {
log.info("[任务ID: {}] 开始批量{},总批次: {}, 每批: {}条",
taskId, operation, totalBatch, batchSize);
}
/**
* 记录单个批次操作
*
* @param log 日志记录器
* @param taskId 任务ID
* @param operation 操作描述
* @param batchNum 当前批次号
* @param totalBatch 总批次数
* @param batchSize 本批数量
*/
public static void logBatchOperation(Logger log, String taskId, String operation,
int batchNum, int totalBatch, int batchSize) {
log.info("[任务ID: {}] 执行批次 {}/{}, 本批数量: {}条",
taskId, batchNum, totalBatch, batchSize);
}
/**
* 记录单个批次完成
*
* @param log 日志记录器
* @param taskId 任务ID
* @param operation 操作描述
* @param batchNum 当前批次号
* @param totalBatch 总批次数
* @param success 成功数量
*/
public static void logBatchComplete(Logger log, String taskId, String operation,
int batchNum, int totalBatch, int success) {
log.info("[任务ID: {}] 批次 {}/{} {}完成,成功: {}条",
taskId, batchNum, totalBatch, operation, success);
}
/**
* 记录Redis缓存操作
*
* @param log 日志记录器
* @param taskId 任务ID
* @param operation 操作描述(如"保存失败记录"
* @param count 数量
*/
public static void logRedisOperation(Logger log, String taskId, String operation, int count) {
log.debug("[任务ID: {}] {}到Redis数量: {}条", taskId, operation, count);
}
/**
* 记录Redis缓存异常
*
* @param log 日志记录器
* @param taskId 任务ID
* @param operation 操作描述
* @param e 异常
*/
public static void logRedisError(Logger log, String taskId, String operation, Exception e) {
log.error("[任务ID: {}] {}到Redis失败不影响导入结果", taskId, operation, e);
}
/**
* 记录导入完成
*
* @param log 日志记录器
* @param taskId 任务ID
* @param moduleName 模块名称
* @param total 总数
* @param success 成功数
* @param failure 失败数
* @param duration 耗时(毫秒)
*/
public static void logImportComplete(Logger log, String taskId, String moduleName,
int total, int success, int failure, long duration) {
log.info("[任务ID: {}] {}导入完成!总数: {}条, 成功: {}条, 失败: {}条, 耗时: {}ms",
taskId, moduleName, total, success, failure, duration);
// 如果有失败,记录失败汇总
if (failure > 0) {
log.warn("[任务ID: {}] 导入完成,但有{}条数据失败,请查看失败记录详情", taskId, failure);
}
}
/**
* 记录异常
*
* @param log 日志记录器
* @param taskId 任务ID
* @param errorMsg 错误描述
* @param e 异常
*/
public static void logException(Logger log, String taskId, String errorMsg, Exception e) {
log.error("[任务ID: {}] {}", taskId, errorMsg, e);
}
/**
* 记录事务回滚
*
* @param log 日志记录器
* @param taskId 任务ID
* @param processed 已处理数量
* @param total 总数量
* @param success 成功数量
* @param failure 失败数量
* @param e 异常
*/
public static void logTransactionRollback(Logger log, String taskId, int processed,
int total, int success, int failure, Exception e) {
log.error("[任务ID: {}] 导入失败,事务已回滚。已处理: {}/{}条", taskId, processed, total, e);
log.error("[任务ID: {}] 回滚前统计 - 新增: {}条, 失败: {}条", taskId, success, failure);
}
/**
* 记录唯一性冲突
*
* @param log 日志记录器
* @param taskId 任务ID
* @param rowNum 行号
* @param conflictDesc 冲突描述
*/
public static void logUniqueConflict(Logger log, String taskId, int rowNum, String conflictDesc) {
log.warn("[任务ID: {}] [第{}行] {}", taskId, rowNum, conflictDesc);
}
/**
* 记录失败原因统计
*
* @param log 日志记录器
* @param taskId 任务ID
* @param errorStats 错误统计Map
*/
public static void logErrorStatistics(Logger log, String taskId, java.util.Map<String, Long> errorStats) {
if (errorStats != null && !errorStats.isEmpty()) {
String statsStr = errorStats.entrySet().stream()
.map(entry -> entry.getKey() + "=" + entry.getValue() + "")
.collect(java.util.stream.Collectors.joining(", "));
log.warn("[任务ID: {}] 失败原因统计: {}", taskId, statsStr);
}
}
}

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiBaseStaffMapper">
<!-- 员工基本信息ResultMap用于列表查询不包含亲属 -->
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiBaseStaffVO" id="CcdiBaseStaffVOResult">
<id property="staffId" column="staff_id"/>
<result property="name" column="name"/>
<result property="deptId" column="dept_id"/>
<result property="deptName" column="dept_name"/>
<result property="idCard" column="id_card"/>
<result property="phone" column="phone"/>
<result property="hireDate" column="hire_date"/>
<result property="status" column="status"/>
<result property="createTime" column="create_time"/>
</resultMap>
<select id="selectBaseStaffPageWithDept" resultMap="CcdiBaseStaffVOResult">
SELECT
e.staff_id, e.name, e.dept_id, e.id_card, e.phone, e.hire_date, e.status, e.create_time,
d.dept_name
FROM ccdi_base_staff e
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
<where>
<if test="query.name != null and query.name != ''">
AND e.name LIKE CONCAT('%', #{query.name}, '%')
</if>
<if test="query.staffId != null">
AND e.staff_id = #{query.staffId}
</if>
<if test="query.deptId != null">
AND e.dept_id = #{query.deptId}
</if>
<if test="query.idCard != null and query.idCard != ''">
AND e.id_card LIKE CONCAT('%', #{query.idCard}, '%')
</if>
<if test="query.status != null and query.status != ''">
AND e.status = #{query.status}
</if>
</where>
ORDER BY e.create_time DESC
</select>
<!-- 批量插入或更新员工信息只更新非null字段 -->
<insert id="insertOrUpdateBatch" parameterType="java.util.List">
INSERT INTO ccdi_base_staff
(staff_id, name, dept_id, id_card, phone, hire_date, status,
create_time, create_by, update_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.staffId}, #{item.name}, #{item.deptId}, #{item.idCard},
#{item.phone}, #{item.hireDate}, #{item.status}, NOW(),
#{item.createBy}, #{item.updateBy}, NOW())
</foreach>
ON DUPLICATE KEY UPDATE
name = COALESCE(VALUES(name), name),
dept_id = COALESCE(VALUES(dept_id), dept_id),
phone = COALESCE(VALUES(phone), phone),
hire_date = COALESCE(VALUES(hire_date), hire_date),
status = COALESCE(VALUES(status), status),
update_by = COALESCE(VALUES(update_by), update_by),
update_time = NOW()
</insert>
<!-- 批量插入员工信息 -->
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO ccdi_base_staff
(staff_id, name, dept_id, id_card, phone, hire_date, status,
create_time, create_by, update_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.staffId}, #{item.name}, #{item.deptId}, #{item.idCard},
#{item.phone}, #{item.hireDate}, #{item.status}, NOW(),
#{item.createBy}, #{item.updateBy}, NOW())
</foreach>
</insert>
<!-- 查询员工选项(用于下拉选择框) -->
<!-- 支持按员工ID或姓名模糊搜索只返回在职员工 -->
<select id="selectStaffOptions" resultType="com.ruoyi.info.collection.domain.vo.CcdiBaseStaffOptionVO">
SELECT
e.staff_id,
e.name,
e.dept_id,
d.dept_name
FROM ccdi_base_staff e
LEFT JOIN sys_dept d ON e.dept_id = d.dept_id
<where>
e.status = '0'
<if test="query != null and query != ''">
AND (CAST(e.staff_id AS CHAR) LIKE CONCAT('%', #{query}, '%')
OR e.name LIKE CONCAT('%', #{query}, '%'))
</if>
</where>
ORDER BY e.staff_id
LIMIT 100
</select>
</mapper>

View File

@@ -1,98 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiCustEnterpriseRelationMapper">
<!-- 信贷客户实体关联信息ResultMap -->
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiCustEnterpriseRelationVO" id="CcdiCustEnterpriseRelationVOResult">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="relationPersonPost" column="relation_person_post"/>
<result property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="data_source"/>
<result property="isEmployee" column="is_employee"/>
<result property="isEmpFamily" column="is_emp_family"/>
<result property="isCustomer" column="is_customer"/>
<result property="isCustFamily" column="is_cust_family"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询信贷客户实体关联列表 -->
<select id="selectRelationPage" resultMap="CcdiCustEnterpriseRelationVOResult">
SELECT
id, person_id, relation_person_post,
social_credit_code, enterprise_name, status, remark,
data_source, is_employee, is_emp_family, is_customer,
is_cust_family, created_by, create_time, updated_by,
update_time
FROM ccdi_cust_enterprise_relation
<where>
<if test="query.personId != null and query.personId != ''">
AND person_id LIKE CONCAT('%', #{query.personId}, '%')
</if>
<if test="query.socialCreditCode != null and query.socialCreditCode != ''">
AND social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%')
</if>
<if test="query.enterpriseName != null and query.enterpriseName != ''">
AND enterprise_name LIKE CONCAT('%', #{query.enterpriseName}, '%')
</if>
<if test="query.status != null">
AND status = #{query.status}
</if>
</where>
ORDER BY create_time DESC
</select>
<!-- 查询信贷客户实体关联详情 -->
<select id="selectRelationById" resultMap="CcdiCustEnterpriseRelationVOResult">
SELECT
id, person_id, relation_person_post,
social_credit_code, enterprise_name, status, remark,
data_source, is_employee, is_emp_family, is_customer,
is_cust_family, created_by, create_time, updated_by,
update_time
FROM ccdi_cust_enterprise_relation
WHERE id = #{id}
</select>
<!-- 判断身份证号和统一社会信用代码的组合是否已存在 -->
<select id="existsByPersonIdAndSocialCreditCode" resultType="boolean">
SELECT COUNT(1) > 0
FROM ccdi_cust_enterprise_relation
WHERE person_id = #{personId}
AND social_credit_code = #{socialCreditCode}
</select>
<!-- 批量查询已存在的person_id + social_credit_code组合 -->
<!-- 优化导入性能一次性查询所有组合避免N+1查询问题 -->
<select id="batchExistsByCombinations" resultType="string">
SELECT CONCAT(person_id, '|', social_credit_code) AS combination
FROM ccdi_cust_enterprise_relation
WHERE CONCAT(person_id, '|', social_credit_code) IN
<foreach collection="combinations" item="combination" open="(" separator="," close=")">
#{combination}
</foreach>
</select>
<!-- 批量插入信贷客户实体关联数据 -->
<insert id="insertBatch">
INSERT INTO ccdi_cust_enterprise_relation
(person_id, relation_person_post, social_credit_code, enterprise_name,
status, remark, data_source, is_employee, is_emp_family, is_customer, is_cust_family,
created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.personId}, #{item.relationPersonPost}, #{item.socialCreditCode}, #{item.enterpriseName},
#{item.status}, #{item.remark}, #{item.dataSource}, #{item.isEmployee}, #{item.isEmpFamily}, #{item.isCustomer}, #{item.isCustFamily},
#{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
</insert>
</mapper>

View File

@@ -1,128 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiCustFmyRelationMapper">
<resultMap id="CcdiCustFmyRelationVOResult" type="com.ruoyi.info.collection.domain.vo.CcdiCustFmyRelationVO">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="relationType" column="relation_type"/>
<result property="relationName" column="relation_name"/>
<result property="gender" column="gender"/>
<result property="birthDate" column="birth_date"/>
<result property="relationCertType" column="relation_cert_type"/>
<result property="relationCertNo" column="relation_cert_no"/>
<result property="mobilePhone1" column="mobile_phone1"/>
<result property="mobilePhone2" column="mobile_phone2"/>
<result property="wechatNo1" column="wechat_no1"/>
<result property="wechatNo2" column="wechat_no2"/>
<result property="wechatNo3" column="wechat_no3"/>
<result property="contactAddress" column="contact_address"/>
<result property="relationDesc" column="relation_desc"/>
<result property="effectiveDate" column="effective_date"/>
<result property="invalidDate" column="invalid_date"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="data_source"/>
<result property="isEmpFamily" column="is_emp_family"/>
<result property="isCustFamily" column="is_cust_family"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询信贷客户家庭关系 -->
<select id="selectRelationPage" resultMap="CcdiCustFmyRelationVOResult">
SELECT
r.id, r.person_id, r.relation_type, r.relation_name,
r.gender, r.birth_date, r.relation_cert_type, r.relation_cert_no,
r.mobile_phone1, r.mobile_phone2, r.wechat_no1, r.wechat_no2, r.wechat_no3,
r.contact_address, r.relation_desc, r.effective_date, r.invalid_date,
r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_cust_fmy_relation r
WHERE r.is_cust_family = 1
<if test="query.personId != null and query.personId != ''">
AND r.person_id LIKE CONCAT('%', #{query.personId}, '%')
</if>
<if test="query.relationType != null and query.relationType != ''">
AND r.relation_type = #{query.relationType}
</if>
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
ORDER BY r.create_time DESC
</select>
<!-- 根据ID查询详情 -->
<select id="selectRelationById" resultMap="CcdiCustFmyRelationVOResult">
SELECT
r.id, r.person_id, r.relation_type, r.relation_name,
r.gender, r.birth_date, r.relation_cert_type, r.relation_cert_no,
r.mobile_phone1, r.mobile_phone2, r.wechat_no1, r.wechat_no2, r.wechat_no3,
r.contact_address, r.relation_desc, r.effective_date, r.invalid_date,
r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_cust_fmy_relation r
WHERE r.id = #{id} AND r.is_cust_family = 1 AND 1=1
</select>
<!-- 查询已存在的关系(用于导入校验) -->
<select id="selectExistingRelations" resultType="com.ruoyi.info.collection.domain.CcdiCustFmyRelation">
SELECT *
FROM ccdi_cust_fmy_relation
WHERE is_cust_family = 1
AND person_id = #{personId}
AND relation_type = #{relationType}
AND relation_cert_no = #{relationCertNo}
AND status = 1
LIMIT 1
</select>
<!-- 批量插入 -->
<insert id="insertBatch" parameterType="java.util.List">
INSERT INTO ccdi_cust_fmy_relation (
person_id, relation_type, relation_name, gender, birth_date,
relation_cert_type, relation_cert_no, mobile_phone1, mobile_phone2,
wechat_no1, wechat_no2, wechat_no3, contact_address, relation_desc,
status, effective_date, invalid_date, remark, data_source,
is_emp_family, is_cust_family, created_by, create_time
) VALUES
<foreach collection="relations" item="item" separator=",">
(
#{item.personId}, #{item.relationType}, #{item.relationName},
#{item.gender}, #{item.birthDate}, #{item.relationCertType},
#{item.relationCertNo}, #{item.mobilePhone1}, #{item.mobilePhone2},
#{item.wechatNo1}, #{item.wechatNo2}, #{item.wechatNo3},
#{item.contactAddress}, #{item.relationDesc}, #{item.status},
#{item.effectiveDate}, #{item.invalidDate}, #{item.remark},
#{item.dataSource}, #{item.isEmpFamily}, #{item.isCustFamily},
#{item.createdBy}, #{item.createTime}
)
</foreach>
</insert>
<!-- 根据证件号码查询关系数量 -->
<select id="countByCertNo" resultType="int">
SELECT COUNT(1)
FROM ccdi_cust_fmy_relation
WHERE is_cust_family = 1
AND relation_cert_no = #{relationCertNo}
AND status = 1
</select>
<!-- 批量查询已存在的关系组合(性能优化) -->
<select id="batchExistsByCombinations" resultType="string">
SELECT CONCAT(person_id, '|', relation_type, '|', relation_cert_no)
FROM ccdi_cust_fmy_relation
WHERE is_cust_family = 1
AND status = 1
AND CONCAT(person_id, '|', relation_type, '|', relation_cert_no) IN
<foreach collection="combinations" item="combo" open="(" separator="," close=")">
#{combo}
</foreach>
</select>
</mapper>

View File

@@ -1,101 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiStaffEnterpriseRelationMapper">
<!-- 员工实体关系信息ResultMap -->
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffEnterpriseRelationVO" id="CcdiStaffEnterpriseRelationVOResult">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="relationPersonPost" column="relation_person_post"/>
<result property="socialCreditCode" column="social_credit_code"/>
<result property="enterpriseName" column="enterprise_name"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="data_source"/>
<result property="isEmployee" column="is_employee"/>
<result property="isEmpFamily" column="is_emp_family"/>
<result property="isCustomer" column="is_customer"/>
<result property="isCustFamily" column="is_cust_family"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询员工实体关系列表 -->
<select id="selectRelationPage" resultMap="CcdiStaffEnterpriseRelationVOResult">
SELECT
ser.id, ser.person_id, bs.name as person_name, ser.relation_person_post,
ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark,
ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer,
ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by,
ser.update_time
FROM ccdi_staff_enterprise_relation ser
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
<where>
<if test="query.personId != null and query.personId != ''">
AND ser.person_id LIKE CONCAT('%', #{query.personId}, '%')
</if>
<if test="query.socialCreditCode != null and query.socialCreditCode != ''">
AND ser.social_credit_code LIKE CONCAT('%', #{query.socialCreditCode}, '%')
</if>
<if test="query.enterpriseName != null and query.enterpriseName != ''">
AND ser.enterprise_name LIKE CONCAT('%', #{query.enterpriseName}, '%')
</if>
<if test="query.status != null">
AND ser.status = #{query.status}
</if>
</where>
ORDER BY ser.create_time DESC
</select>
<!-- 查询员工实体关系详情 -->
<select id="selectRelationById" resultMap="CcdiStaffEnterpriseRelationVOResult">
SELECT
ser.id, ser.person_id, bs.name as person_name, ser.relation_person_post,
ser.social_credit_code, ser.enterprise_name, ser.status, ser.remark,
ser.data_source, ser.is_employee, ser.is_emp_family, ser.is_customer,
ser.is_cust_family, ser.created_by, ser.create_time, ser.updated_by,
ser.update_time
FROM ccdi_staff_enterprise_relation ser
LEFT JOIN ccdi_base_staff bs ON ser.person_id = bs.id_card
WHERE ser.id = #{id}
</select>
<!-- 判断身份证号和统一社会信用代码的组合是否已存在 -->
<select id="existsByPersonIdAndSocialCreditCode" resultType="boolean">
SELECT COUNT(1) > 0
FROM ccdi_staff_enterprise_relation
WHERE person_id = #{personId}
AND social_credit_code = #{socialCreditCode}
</select>
<!-- 批量查询已存在的person_id + social_credit_code组合 -->
<!-- 优化导入性能一次性查询所有组合避免N+1查询问题 -->
<select id="batchExistsByCombinations" resultType="string">
SELECT CONCAT(person_id, '|', social_credit_code) AS combination
FROM ccdi_staff_enterprise_relation
WHERE CONCAT(person_id, '|', social_credit_code) IN
<foreach collection="combinations" item="combination" open="(" separator="," close=")">
#{combination}
</foreach>
</select>
<!-- 批量插入员工实体关系数据 -->
<insert id="insertBatch">
INSERT INTO ccdi_staff_enterprise_relation
(person_id, relation_person_post, social_credit_code, enterprise_name,
status, remark, data_source, is_employee, is_emp_family, is_customer, is_cust_family,
created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.personId}, #{item.relationPersonPost}, #{item.socialCreditCode}, #{item.enterpriseName},
#{item.status}, #{item.remark}, #{item.dataSource}, #{item.isEmployee}, #{item.isEmpFamily}, #{item.isCustomer}, #{item.isCustFamily},
#{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
</insert>
</mapper>

View File

@@ -1,168 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiStaffFmyRelationMapper">
<!-- 员工亲属关系ResultMap -->
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffFmyRelationVO" id="CcdiStaffFmyRelationVOResult">
<id property="id" column="id"/>
<result property="personId" column="person_id"/>
<result property="personName" column="person_name"/>
<result property="relationType" column="relation_type"/>
<result property="relationName" column="relation_name"/>
<result property="gender" column="gender"/>
<result property="birthDate" column="birth_date"/>
<result property="relationCertType" column="relation_cert_type"/>
<result property="relationCertNo" column="relation_cert_no"/>
<result property="mobilePhone1" column="mobile_phone1"/>
<result property="mobilePhone2" column="mobile_phone2"/>
<result property="wechatNo1" column="wechat_no1"/>
<result property="wechatNo2" column="wechat_no2"/>
<result property="wechatNo3" column="wechat_no3"/>
<result property="contactAddress" column="contact_address"/>
<result property="relationDesc" column="relation_desc"/>
<result property="effectiveDate" column="effective_date"/>
<result property="invalidDate" column="invalid_date"/>
<result property="status" column="status"/>
<result property="remark" column="remark"/>
<result property="dataSource" column="data_source"/>
<result property="isEmpFamily" column="is_emp_family"/>
<result property="isCustFamily" column="is_cust_family"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询员工亲属关系列表 -->
<select id="selectRelationPage" resultMap="CcdiStaffFmyRelationVOResult">
SELECT
r.id, r.person_id, s.name as person_name, r.relation_type, r.relation_name,
r.gender, r.birth_date, r.relation_cert_type, r.relation_cert_no,
r.mobile_phone1, r.mobile_phone2, r.wechat_no1, r.wechat_no2, r.wechat_no3,
r.contact_address, r.relation_desc, r.effective_date, r.invalid_date,
r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_staff_fmy_relation r
LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card
<where>
r.is_emp_family = 1
<if test="query.personId != null and query.personId != ''">
AND r.person_id = #{query.personId}
</if>
<if test="query.personName != null and query.personName != ''">
AND s.name LIKE CONCAT('%', #{query.personName}, '%')
</if>
<if test="query.relationType != null and query.relationType != ''">
AND r.relation_type = #{query.relationType}
</if>
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.status != null">
AND r.status = #{query.status}
</if>
<if test="query.dataSource != null and query.dataSource != ''">
AND r.data_source = #{query.dataSource}
</if>
<if test="query.effectiveDateStart != null">
AND r.effective_date &gt;= #{query.effectiveDateStart}
</if>
<if test="query.effectiveDateEnd != null">
AND r.effective_date &lt;= #{query.effectiveDateEnd}
</if>
</where>
ORDER BY r.create_time DESC
</select>
<!-- 查询员工亲属关系详情 -->
<select id="selectRelationById" resultMap="CcdiStaffFmyRelationVOResult">
SELECT
r.id, r.person_id, s.name as person_name, r.relation_type, r.relation_name,
r.gender, r.birth_date, r.relation_cert_type, r.relation_cert_no,
r.mobile_phone1, r.mobile_phone2, r.wechat_no1, r.wechat_no2, r.wechat_no3,
r.contact_address, r.relation_desc, r.effective_date, r.invalid_date,
r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_staff_fmy_relation r
LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card
WHERE r.id = #{id}
</select>
<!-- 查询员工亲属关系列表(用于导出) -->
<select id="selectRelationListForExport" resultMap="CcdiStaffFmyRelationVOResult">
SELECT
r.id, r.person_id, s.name as person_name, r.relation_type, r.relation_name,
r.gender, r.birth_date, r.relation_cert_type, r.relation_cert_no,
r.mobile_phone1, r.mobile_phone2, r.wechat_no1, r.wechat_no2, r.wechat_no3,
r.contact_address, r.relation_desc, r.effective_date, r.invalid_date,
r.status, r.remark, r.data_source, r.is_emp_family, r.is_cust_family,
r.created_by, r.create_time, r.updated_by, r.update_time
FROM ccdi_staff_fmy_relation r
LEFT JOIN ccdi_base_staff s ON r.person_id = s.id_card
<where>
r.is_emp_family = 1
<if test="query.personId != null and query.personId != ''">
AND r.person_id = #{query.personId}
</if>
<if test="query.personName != null and query.personName != ''">
AND s.name LIKE CONCAT('%', #{query.personName}, '%')
</if>
<if test="query.relationType != null and query.relationType != ''">
AND r.relation_type = #{query.relationType}
</if>
<if test="query.relationName != null and query.relationName != ''">
AND r.relation_name LIKE CONCAT('%', #{query.relationName}, '%')
</if>
<if test="query.status != null">
AND r.status = #{query.status}
</if>
<if test="query.dataSource != null and query.dataSource != ''">
AND r.data_source = #{query.dataSource}
</if>
<if test="query.effectiveDateStart != null">
AND r.effective_date &gt;= #{query.effectiveDateStart}
</if>
<if test="query.effectiveDateEnd != null">
AND r.effective_date &lt;= #{query.effectiveDateEnd}
</if>
</where>
ORDER BY r.create_time DESC
</select>
<!-- 批量插入员工亲属关系数据 -->
<insert id="insertBatch">
INSERT INTO ccdi_staff_fmy_relation
(person_id, relation_type, relation_name, gender, birth_date, relation_cert_type, relation_cert_no,
mobile_phone1, mobile_phone2, wechat_no1, wechat_no2, wechat_no3, contact_address, relation_desc,
effective_date, invalid_date, status, remark, data_source, is_emp_family, is_cust_family,
created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.personId}, #{item.relationType}, #{item.relationName}, #{item.gender},
#{item.birthDate}, #{item.relationCertType}, #{item.relationCertNo}, #{item.mobilePhone1},
#{item.mobilePhone2}, #{item.wechatNo1}, #{item.wechatNo2}, #{item.wechatNo3}, #{item.contactAddress},
#{item.relationDesc}, #{item.effectiveDate}, #{item.invalidDate}, #{item.status}, #{item.remark},
#{item.dataSource}, #{item.isEmpFamily}, #{item.isCustFamily}, #{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
</insert>
<!-- 批量查询已存在的员工亲属关系(用于导入唯一性校验) -->
<select id="selectExistingRelations" resultType="com.ruoyi.info.collection.domain.CcdiStaffFmyRelation">
SELECT
id, person_id, relation_type, relation_name, gender, birth_date,
relation_cert_type, relation_cert_no
FROM ccdi_staff_fmy_relation
WHERE is_emp_family = 1
AND person_id IN
<foreach collection="personIds" item="personId" open="(" separator="," close=")">
#{personId}
</foreach>
AND relation_cert_no IN
<foreach collection="relationCertNos" item="relationCertNo" open="(" separator="," close=")">
#{relationCertNo}
</foreach>
</select>
</mapper>

View File

@@ -1,159 +0,0 @@
<?xml version="1.0" encoding="UTF-8" ?>
<!DOCTYPE mapper
PUBLIC "-//mybatis.org//DTD Mapper 3.0//EN"
"http://mybatis.org/dtd/mybatis-3-mapper.dtd">
<mapper namespace="com.ruoyi.info.collection.mapper.CcdiStaffTransferMapper">
<!-- 员工调动记录ResultMap -->
<resultMap type="com.ruoyi.info.collection.domain.vo.CcdiStaffTransferVO" id="CcdiStaffTransferVOResult">
<id property="id" column="id"/>
<result property="staffId" column="staff_id"/>
<result property="staffName" column="staff_name"/>
<result property="transferType" column="transfer_type"/>
<result property="transferSubType" column="transfer_sub_type"/>
<result property="deptIdBefore" column="dept_id_before"/>
<result property="deptNameBefore" column="dept_name_before"/>
<result property="gradeBefore" column="grade_before"/>
<result property="positionBefore" column="position_before"/>
<result property="salaryLevelBefore" column="salary_level_before"/>
<result property="deptIdAfter" column="dept_id_after"/>
<result property="deptNameAfter" column="dept_name_after"/>
<result property="gradeAfter" column="grade_after"/>
<result property="positionAfter" column="position_after"/>
<result property="salaryLevelAfter" column="salary_level_after"/>
<result property="transferDate" column="transfer_date"/>
<result property="createTime" column="create_time"/>
<result property="updateTime" column="update_time"/>
<result property="createdBy" column="created_by"/>
<result property="updatedBy" column="updated_by"/>
</resultMap>
<!-- 分页查询员工调动记录列表 -->
<select id="selectTransferPage" resultMap="CcdiStaffTransferVOResult">
SELECT
t.id, t.staff_id, s.name as staff_name, t.transfer_type, t.transfer_sub_type,
t.dept_id_before, t.dept_name_before, t.grade_before, t.position_before, t.salary_level_before,
t.dept_id_after, t.dept_name_after, t.grade_after, t.position_after, t.salary_level_after,
t.transfer_date, t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_staff_transfer t
LEFT JOIN ccdi_base_staff s ON t.staff_id = s.staff_id
<where>
<if test="query.staffId != null">
AND t.staff_id = #{query.staffId}
</if>
<if test="query.staffName != null and query.staffName != ''">
AND s.name LIKE CONCAT('%', #{query.staffName}, '%')
</if>
<if test="query.transferType != null and query.transferType != ''">
AND t.transfer_type = #{query.transferType}
</if>
<if test="query.transferSubType != null and query.transferSubType != ''">
AND t.transfer_sub_type = #{query.transferSubType}
</if>
<if test="query.deptIdBefore != null">
AND t.dept_id_before = #{query.deptIdBefore}
</if>
<if test="query.deptIdAfter != null">
AND t.dept_id_after = #{query.deptIdAfter}
</if>
<if test="query.transferDateStart != null">
AND t.transfer_date &gt;= #{query.transferDateStart}
</if>
<if test="query.transferDateEnd != null">
AND t.transfer_date &lt;= #{query.transferDateEnd}
</if>
</where>
ORDER BY t.transfer_date DESC, t.create_time DESC
</select>
<!-- 查询员工调动记录详情 -->
<select id="selectTransferById" resultMap="CcdiStaffTransferVOResult">
SELECT
t.id, t.staff_id, s.name as staff_name, t.transfer_type, t.transfer_sub_type,
t.dept_id_before, t.dept_name_before, t.grade_before, t.position_before, t.salary_level_before,
t.dept_id_after, t.dept_name_after, t.grade_after, t.position_after, t.salary_level_after,
t.transfer_date, t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_staff_transfer t
LEFT JOIN ccdi_base_staff s ON t.staff_id = s.staff_id
WHERE t.id = #{id}
</select>
<!-- 查询员工调动记录列表(用于导出) -->
<select id="selectTransferListForExport" resultMap="CcdiStaffTransferVOResult">
SELECT
t.id, t.staff_id, s.name as staff_name, t.transfer_type, t.transfer_sub_type,
t.dept_id_before, t.dept_name_before, t.grade_before, t.position_before, t.salary_level_before,
t.dept_id_after, t.dept_name_after, t.grade_after, t.position_after, t.salary_level_after,
t.transfer_date, t.created_by, t.create_time, t.updated_by, t.update_time
FROM ccdi_staff_transfer t
LEFT JOIN ccdi_base_staff s ON t.staff_id = s.staff_id
<where>
<if test="query.staffId != null">
AND t.staff_id = #{query.staffId}
</if>
<if test="query.staffName != null and query.staffName != ''">
AND s.name LIKE CONCAT('%', #{query.staffName}, '%')
</if>
<if test="query.transferType != null and query.transferType != ''">
AND t.transfer_type = #{query.transferType}
</if>
<if test="query.transferSubType != null and query.transferSubType != ''">
AND t.transfer_sub_type = #{query.transferSubType}
</if>
<if test="query.deptIdBefore != null">
AND t.dept_id_before = #{query.deptIdBefore}
</if>
<if test="query.deptIdAfter != null">
AND t.dept_id_after = #{query.deptIdAfter}
</if>
<if test="query.transferDateStart != null">
AND t.transfer_date &gt;= #{query.transferDateStart}
</if>
<if test="query.transferDateEnd != null">
AND t.transfer_date &lt;= #{query.transferDateEnd}
</if>
</where>
ORDER BY t.transfer_date DESC, t.create_time DESC
</select>
<!-- 批量插入员工调动记录数据 -->
<insert id="insertBatch">
INSERT INTO ccdi_staff_transfer
(staff_id, transfer_type, transfer_sub_type, dept_id_before, dept_name_before, grade_before,
position_before, salary_level_before, dept_id_after, dept_name_after, grade_after,
position_after, salary_level_after, transfer_date, created_by, create_time, updated_by, update_time)
VALUES
<foreach collection="list" item="item" separator=",">
(#{item.staffId}, #{item.transferType}, #{item.transferSubType}, #{item.deptIdBefore},
#{item.deptNameBefore}, #{item.gradeBefore}, #{item.positionBefore}, #{item.salaryLevelBefore},
#{item.deptIdAfter}, #{item.deptNameAfter}, #{item.gradeAfter}, #{item.positionAfter},
#{item.salaryLevelAfter}, #{item.transferDate}, #{item.createdBy}, NOW(), #{item.updatedBy}, NOW())
</foreach>
</insert>
<!-- 查询单条记录是否存在(根据唯一键) -->
<select id="checkExists" resultType="com.ruoyi.info.collection.domain.CcdiStaffTransfer">
SELECT
id, staff_id, dept_id_before, dept_id_after, transfer_date
FROM ccdi_staff_transfer
WHERE staff_id = #{key.staffId}
AND dept_id_before = #{key.deptIdBefore}
AND dept_id_after = #{key.deptIdAfter}
AND transfer_date = #{key.transferDate}
LIMIT 1
</select>
<!-- 查询单条记录是否存在排除指定ID -->
<select id="checkExistsExcludeId" resultType="com.ruoyi.info.collection.domain.CcdiStaffTransfer">
SELECT
id, staff_id, dept_id_before, dept_id_after, transfer_date
FROM ccdi_staff_transfer
WHERE staff_id = #{key.staffId}
AND dept_id_before = #{key.deptIdBefore}
AND dept_id_after = #{key.deptIdAfter}
AND transfer_date = #{key.transferDate}
AND id != #{excludeId}
LIMIT 1
</select>
</mapper>

View File

@@ -1,48 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<parent>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi</artifactId>
<version>3.9.1</version>
</parent>
<modelVersion>4.0.0</modelVersion>
<artifactId>ccdi-lsfx</artifactId>
<description>流水分析平台对接模块</description>
<dependencies>
<!-- 通用工具 -->
<dependency>
<groupId>com.ruoyi</groupId>
<artifactId>ruoyi-common</artifactId>
</dependency>
<!-- Spring Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Apache HttpClient (用于连接池) -->
<dependency>
<groupId>org.apache.httpcomponents.client5</groupId>
<artifactId>httpclient5</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- SpringDoc OpenAPI (Swagger) -->
<dependency>
<groupId>org.springdoc</groupId>
<artifactId>springdoc-openapi-starter-webmvc-ui</artifactId>
</dependency>
</dependencies>
</project>

View File

@@ -1,246 +0,0 @@
package com.ruoyi.lsfx.client;
import com.ruoyi.lsfx.constants.LsfxConstants;
import com.ruoyi.lsfx.domain.request.*;
import com.ruoyi.lsfx.domain.response.*;
import com.ruoyi.lsfx.exception.LsfxApiException;
import com.ruoyi.lsfx.util.HttpUtil;
import com.ruoyi.lsfx.util.MD5Util;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;
import jakarta.annotation.Resource;
import java.util.HashMap;
import java.util.Map;
/**
* 流水分析平台客户端
*/
@Slf4j
@Component
public class LsfxAnalysisClient {
@Resource
private HttpUtil httpUtil;
@Value("${lsfx.api.base-url}")
private String baseUrl;
@Value("${lsfx.api.app-id}")
private String appId;
@Value("${lsfx.api.app-secret}")
private String appSecret;
@Value("${lsfx.api.client-id}")
private String clientId;
@Value("${lsfx.api.endpoints.get-token}")
private String getTokenEndpoint;
@Value("${lsfx.api.endpoints.upload-file}")
private String uploadFileEndpoint;
@Value("${lsfx.api.endpoints.fetch-inner-flow}")
private String fetchInnerFlowEndpoint;
@Value("${lsfx.api.endpoints.check-parse-status}")
private String checkParseStatusEndpoint;
@Value("${lsfx.api.endpoints.get-bank-statement}")
private String getBankStatementEndpoint;
/**
* 获取Token
*/
public GetTokenResponse getToken(GetTokenRequest request) {
log.info("【流水分析】获取Token请求: projectNo={}, entityName={}", request.getProjectNo(), request.getEntityName());
long startTime = System.currentTimeMillis();
try {
String secretCode = MD5Util.generateSecretCode(
request.getProjectNo(),
request.getEntityName(),
appSecret
);
request.setAppSecretCode(secretCode);
request.setAppId(appId);
if (request.getAnalysisType() == null) {
request.setAnalysisType(LsfxConstants.ANALYSIS_TYPE);
}
if (request.getRole() == null) {
request.setRole(LsfxConstants.DEFAULT_ROLE);
}
String url = baseUrl + getTokenEndpoint;
GetTokenResponse response = httpUtil.postJson(url, request, null, GetTokenResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】获取Token成功: projectId={}, 耗时={}ms",
response.getData().getProjectId(), elapsed);
} else {
log.warn("【流水分析】获取Token响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】获取Token失败: projectNo={}, error={}", request.getProjectNo(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】获取Token未知异常: projectNo={}", request.getProjectNo(), e);
throw new LsfxApiException("获取Token失败: " + e.getMessage(), e);
}
}
/**
* 上传文件
*/
public UploadFileResponse uploadFile(Integer groupId, org.springframework.core.io.Resource file) {
log.info("【流水分析】上传文件请求: groupId={}, fileName={}", groupId, file.getFilename());
long startTime = System.currentTimeMillis();
try {
String url = baseUrl + uploadFileEndpoint;
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("files", file);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
UploadFileResponse response = httpUtil.uploadFile(url, params, headers, UploadFileResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】上传文件成功: uploadStatus={}, 耗时={}ms",
response.getData().getUploadStatus(), elapsed);
} else {
log.warn("【流水分析】上传文件响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】上传文件失败: groupId={}, error={}", groupId, e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】上传文件未知异常: groupId={}", groupId, e);
throw new LsfxApiException("上传文件失败: " + e.getMessage(), e);
}
}
/**
* 拉取行内流水
*/
public FetchInnerFlowResponse fetchInnerFlow(FetchInnerFlowRequest request) {
log.info("【流水分析】拉取行内流水请求: groupId={}, customerNo={}", request.getGroupId(), request.getCustomerNo());
long startTime = System.currentTimeMillis();
try {
String url = baseUrl + fetchInnerFlowEndpoint;
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
FetchInnerFlowResponse response = httpUtil.postJson(url, request, headers, FetchInnerFlowResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】拉取行内流水完成: code={}, message={}, 耗时={}ms",
response.getData().getCode(), response.getData().getMessage(), elapsed);
} else {
log.warn("【流水分析】拉取行内流水响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】拉取行内流水失败: groupId={}, error={}", request.getGroupId(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】拉取行内流水未知异常: groupId={}", request.getGroupId(), e);
throw new LsfxApiException("拉取行内流水失败: " + e.getMessage(), e);
}
}
/**
* 检查文件解析状态
*/
public CheckParseStatusResponse checkParseStatus(Integer groupId, String inprogressList) {
log.info("【流水分析】检查文件解析状态: groupId={}, inprogressList={}", groupId, inprogressList);
long startTime = System.currentTimeMillis();
try {
String url = baseUrl + checkParseStatusEndpoint;
Map<String, Object> params = new HashMap<>();
params.put("groupId", groupId);
params.put("inprogressList", inprogressList);
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
CheckParseStatusResponse response = httpUtil.postJson(url, params, headers, CheckParseStatusResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】检查解析状态完成: parsing={}, pendingList.size={}, 耗时={}ms",
response.getData().getParsing(),
response.getData().getPendingList() != null ? response.getData().getPendingList().size() : 0,
elapsed);
} else {
log.warn("【流水分析】检查解析状态响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】检查解析状态失败: groupId={}, error={}", groupId, e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】检查解析状态未知异常: groupId={}", groupId, e);
throw new LsfxApiException("检查解析状态失败: " + e.getMessage(), e);
}
}
/**
* 获取银行流水(新版接口)
* 注意: 需要传入logId参数,参数名已从pageNum改为pageNow
*
* @param request 请求参数(groupId, logId, pageNow, pageSize)
* @return 流水明细列表
*/
public GetBankStatementResponse getBankStatement(GetBankStatementRequest request) {
log.info("【流水分析】获取银行流水请求: groupId={}, logId={}, pageNow={}, pageSize={}",
request.getGroupId(), request.getLogId(), request.getPageNow(), request.getPageSize());
long startTime = System.currentTimeMillis();
try {
String url = baseUrl + getBankStatementEndpoint;
Map<String, String> headers = new HashMap<>();
headers.put(LsfxConstants.HEADER_CLIENT_ID, clientId);
GetBankStatementResponse response = httpUtil.postJson(url, request, headers, GetBankStatementResponse.class);
long elapsed = System.currentTimeMillis() - startTime;
if (response != null && response.getData() != null) {
log.info("【流水分析】获取银行流水成功: totalCount={}, 耗时={}ms",
response.getData().getTotalCount(), elapsed);
} else {
log.warn("【流水分析】获取银行流水响应异常: 耗时={}ms", elapsed);
}
return response;
} catch (LsfxApiException e) {
log.error("【流水分析】获取银行流水失败: groupId={}, logId={}, error={}",
request.getGroupId(), request.getLogId(), e.getMessage(), e);
throw e;
} catch (Exception e) {
log.error("【流水分析】获取银行流水未知异常: groupId={}, logId={}",
request.getGroupId(), request.getLogId(), e);
throw new LsfxApiException("获取银行流水失败: " + e.getMessage(), e);
}
}
}

View File

@@ -1,50 +0,0 @@
package com.ruoyi.lsfx.config;
import org.apache.hc.client5.http.classic.HttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.client5.http.impl.io.PoolingHttpClientConnectionManager;
import org.apache.hc.client5.http.io.HttpClientConnectionManager;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.client.HttpComponentsClientHttpRequestFactory;
import org.springframework.web.client.RestTemplate;
/**
* RestTemplate配置使用连接池优化性能
*/
@Configuration
public class RestTemplateConfig {
@Value("${lsfx.api.connection-timeout:30000}")
private int connectionTimeout;
@Value("${lsfx.api.read-timeout:60000}")
private int readTimeout;
@Value("${lsfx.api.pool.max-total:100}")
private int maxTotal;
@Value("${lsfx.api.pool.default-max-per-route:20}")
private int defaultMaxPerRoute;
@Bean
public RestTemplate restTemplate() {
// 创建连接池管理器
PoolingHttpClientConnectionManager connectionManager = new PoolingHttpClientConnectionManager();
connectionManager.setMaxTotal(maxTotal); // 最大连接数
connectionManager.setDefaultMaxPerRoute(defaultMaxPerRoute); // 每个路由的最大连接数
// 创建HttpClient并设置连接池
HttpClient httpClient = HttpClients.custom()
.setConnectionManager(connectionManager)
.build();
// 创建HttpComponentsClientHttpRequestFactory
HttpComponentsClientHttpRequestFactory factory = new HttpComponentsClientHttpRequestFactory(httpClient);
factory.setConnectTimeout(connectionTimeout);
factory.setConnectionRequestTimeout(connectionTimeout);
return new RestTemplate(factory);
}
}

View File

@@ -1,30 +0,0 @@
package com.ruoyi.lsfx.constants;
/**
* 流水分析平台常量
*/
public class LsfxConstants {
/** 基础URL配置键 */
public static final String BASE_URL_KEY = "lsfx.api.base-url";
/** 成功状态码 */
public static final String SUCCESS_CODE = "200";
/** 文件解析成功状态 */
public static final int PARSE_SUCCESS_STATUS = -5;
public static final String PARSE_SUCCESS_DESC = "data.wait.confirm.newaccount";
/** 数据渠道编码 */
public static final String DATA_CHANNEL_ZJRCU = "ZJRCU";
/** 分析类型 */
public static final String ANALYSIS_TYPE = "-1";
/** 请求头 */
public static final String HEADER_CLIENT_ID = "X-Xencio-Client-Id";
public static final String HEADER_CONTENT_TYPE = "Content-Type";
/** 默认角色 */
public static final String DEFAULT_ROLE = "VIEWER";
}

View File

@@ -1,142 +0,0 @@
package com.ruoyi.lsfx.controller;
import com.ruoyi.common.core.domain.AjaxResult;
import com.ruoyi.common.utils.StringUtils;
import com.ruoyi.lsfx.client.LsfxAnalysisClient;
import com.ruoyi.lsfx.domain.request.*;
import com.ruoyi.lsfx.domain.response.*;
import io.swagger.v3.oas.annotations.Operation;
import io.swagger.v3.oas.annotations.Parameter;
import io.swagger.v3.oas.annotations.tags.Tag;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import jakarta.annotation.Resource;
/**
* 流水分析平台接口测试控制器
*/
@Tag(name = "流水分析平台接口测试", description = "用于测试流水分析平台的7个接口")
@RestController
@RequestMapping("/lsfx/test")
public class LsfxTestController {
@Resource
private LsfxAnalysisClient lsfxAnalysisClient;
@Operation(summary = "获取Token", description = "创建项目并获取访问Token")
@PostMapping("/getToken")
public AjaxResult getToken(@RequestBody GetTokenRequest request) {
// 参数校验
if (StringUtils.isBlank(request.getProjectNo())) {
return AjaxResult.error("参数不完整projectNo为必填");
}
if (StringUtils.isBlank(request.getEntityName())) {
return AjaxResult.error("参数不完整entityName为必填");
}
if (StringUtils.isBlank(request.getUserId())) {
return AjaxResult.error("参数不完整userId为必填");
}
if (StringUtils.isBlank(request.getUserName())) {
return AjaxResult.error("参数不完整userName为必填");
}
if (StringUtils.isBlank(request.getOrgCode())) {
return AjaxResult.error("参数不完整orgCode为必填");
}
if (StringUtils.isBlank(request.getDepartmentCode())) {
return AjaxResult.error("参数不完整departmentCode为必填");
}
GetTokenResponse response = lsfxAnalysisClient.getToken(request);
return AjaxResult.success(response);
}
@Operation(summary = "上传流水文件", description = "上传银行流水文件到流水分析平台")
@PostMapping("/uploadFile")
public AjaxResult uploadFile(
@Parameter(description = "项目ID") @RequestParam Integer groupId,
@Parameter(description = "流水文件") @RequestParam("file") MultipartFile file
) {
// 参数校验
if (groupId == null || groupId <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (file == null || file.isEmpty()) {
return AjaxResult.error("参数不完整:文件不能为空");
}
if (file.getSize() > 10 * 1024 * 1024) { // 10MB限制
return AjaxResult.error("文件大小超过限制最大10MB");
}
org.springframework.core.io.Resource fileResource = file.getResource();
UploadFileResponse response = lsfxAnalysisClient.uploadFile(groupId, fileResource);
return AjaxResult.success(response);
}
@Operation(summary = "拉取行内流水", description = "从数仓拉取行内流水数据")
@PostMapping("/fetchInnerFlow")
public AjaxResult fetchInnerFlow(@RequestBody FetchInnerFlowRequest request) {
// 参数校验
if (request.getGroupId() == null || request.getGroupId() <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (StringUtils.isEmpty(request.getCustomerNo())) {
return AjaxResult.error("参数不完整customerNo为必填");
}
if (request.getRequestDateId() == null) {
return AjaxResult.error("参数不完整requestDateId为必填");
}
if (request.getDataStartDateId() == null) {
return AjaxResult.error("参数不完整dataStartDateId为必填");
}
if (request.getDataEndDateId() == null) {
return AjaxResult.error("参数不完整dataEndDateId为必填");
}
if (request.getDataStartDateId() > request.getDataEndDateId()) {
return AjaxResult.error("参数错误:开始日期不能大于结束日期");
}
FetchInnerFlowResponse response = lsfxAnalysisClient.fetchInnerFlow(request);
return AjaxResult.success(response);
}
@Operation(summary = "检查文件解析状态", description = "轮询检查上传文件的解析状态")
@PostMapping("/checkParseStatus")
public AjaxResult checkParseStatus(
@Parameter(description = "项目ID") @RequestParam Integer groupId,
@Parameter(description = "文件ID列表") @RequestParam String inprogressList
) {
// 参数校验
if (groupId == null || groupId <= 0) {
return AjaxResult.error("参数不完整groupId为必填且大于0");
}
if (StringUtils.isEmpty(inprogressList)) {
return AjaxResult.error("参数不完整inprogressList为必填");
}
CheckParseStatusResponse response = lsfxAnalysisClient.checkParseStatus(groupId, inprogressList);
return AjaxResult.success(response);
}
@Operation(summary = "获取银行流水列表(新版)",
description = "分页获取指定文件的银行流水数据需要提供logId参数")
@PostMapping("/getBankStatement")
public AjaxResult getBankStatement(@RequestBody GetBankStatementRequest request) {
// 参数校验
if (request.getGroupId() == null) {
return AjaxResult.error("参数不完整groupId为必填");
}
if (request.getLogId() == null) {
return AjaxResult.error("参数不完整logId为必填(文件ID)");
}
if (request.getPageNow() == null || request.getPageNow() < 1) {
return AjaxResult.error("参数不完整pageNow为必填且大于0");
}
if (request.getPageSize() == null || request.getPageSize() < 1) {
return AjaxResult.error("参数不完整pageSize为必填且大于0");
}
GetBankStatementResponse response = lsfxAnalysisClient.getBankStatement(request);
return AjaxResult.success(response);
}
}

View File

@@ -1,31 +0,0 @@
package com.ruoyi.lsfx.domain.request;
import lombok.Data;
/**
* 拉取行内流水请求参数(匹配新文档3.2节)
*/
@Data
public class FetchInnerFlowRequest {
/** 项目ID */
private Integer groupId;
/** 客户身份证号 */
private String customerNo;
/** 数据渠道编码(固定值:ZJRCU) */
private String dataChannelCode;
/** 发起请求的时间(格式:yyyyMMdd) */
private Integer requestDateId;
/** 拉取开始日期(格式:yyyyMMdd) */
private Integer dataStartDateId;
/** 拉取结束日期(格式:yyyyMMdd) */
private Integer dataEndDateId;
/** 柜员号 */
private Integer uploadUserId;
}

View File

@@ -1,22 +0,0 @@
package com.ruoyi.lsfx.domain.request;
import lombok.Data;
/**
* 获取银行流水请求参数(匹配新文档6.2节)
*/
@Data
public class GetBankStatementRequest {
/** 项目ID */
private Integer groupId;
/** 文件ID(新增必填参数) */
private Integer logId;
/** 当前页码(原pageNum) */
private Integer pageNow;
/** 每页数量 */
private Integer pageSize;
}

View File

@@ -1,55 +0,0 @@
package com.ruoyi.lsfx.domain.request;
import lombok.Data;
/**
* 获取Token请求参数
*/
@Data
public class GetTokenRequest {
/** 项目编号 */
private String projectNo;
/** 项目名称 */
private String entityName;
/** 操作人员编号 */
private String userId;
/** 操作人员姓名 */
private String userName;
/** 见知提供appId */
private String appId;
/** 安全码 md5(projectNo + "_" + entityName + "_" + appSecret) */
private String appSecretCode;
/** 人员角色 */
private String role;
/** 行社机构号 */
private String orgCode;
/** 企业统信码或个人身份证号 */
private String entityId;
/** 信贷关联人信息 */
private String xdRelatedPersons;
/** 金综链流水日期ID */
private String jzDataDateId;
/** 行内流水开始日期 */
private String innerBSStartDateId;
/** 行内流水结束日期 */
private String innerBSEndDateId;
/** 分析类型 */
private String analysisType;
/** 客户经理所属营业部机构编码 */
private String departmentCode;
}

View File

@@ -1,116 +0,0 @@
package com.ruoyi.lsfx.domain.response;
import lombok.Data;
import java.util.List;
/**
* 检查文件解析状态响应(匹配新文档4.5节)
*/
@Data
public class CheckParseStatusResponse {
/** 返回码 */
private String code;
/** 状态 */
private String status;
/** 成功标识 */
private Boolean successResponse;
/** 响应数据 */
private ParseStatusData data;
@Data
public static class ParseStatusData {
/** 是否正在解析(true=解析中, false=解析结束) - 关键字段 */
private Boolean parsing;
/** 待处理文件列表 */
private List<PendingItem> pendingList;
}
@Data
public static class PendingItem {
/** 账号列表 */
private List<String> accountNoList;
/** 银行名称 */
private String bankName;
/** 数据类型信息 */
private List<String> dataTypeInfo;
/** 下载文件名 */
private String downloadFileName;
/** 企业名称列表 */
private List<String> enterpriseNameList;
/** 文件包ID */
private String filePackageId;
/** 文件大小(字节) */
private Long fileSize;
/** 上传用户ID */
private Integer fileUploadBy;
/** 上传用户名 */
private String fileUploadByUserName;
/** 上传时间 */
private String fileUploadTime;
/** 是否拆分 */
private Integer isSplit;
/** 企业ID */
private Integer leId;
/** 文件ID(重要) */
private Integer logId;
/** 日志元数据 */
private String logMeta;
/** 日志类型 */
private String logType;
/** 登录企业ID */
private Integer loginLeId;
/** 丢失的表头 */
private List<String> lostHeader;
/** 真实银行名称 */
private String realBankName;
/** 行数 */
private Integer rows;
/** 来源 */
private String source;
/** 状态(-5表示成功) */
private Integer status;
/** 模板名称 */
private String templateName;
/** 总记录数 */
private Integer totalRecords;
/** 交易结束日期ID */
private Integer trxDateEndId;
/** 交易开始日期ID */
private Integer trxDateStartId;
/** 上传文件名 */
private String uploadFileName;
/** 上传状态描述(data.wait.confirm.newaccount表示成功) */
private String uploadStatusDesc;
}
}

View File

@@ -1,31 +0,0 @@
package com.ruoyi.lsfx.domain.response;
import lombok.Data;
/**
* 拉取行内流水响应(匹配新文档3.5节)
*/
@Data
public class FetchInnerFlowResponse {
/** 返回码 */
private String code;
/** 状态 */
private String status;
/** 成功标识 */
private Boolean successResponse;
/** 响应数据 */
private FetchData data;
@Data
public static class FetchData {
/** 状态码(如:501014表示无行内流水文件) */
private String code;
/** 消息(如:无行内流水文件) */
private String message;
}
}

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