员工关系移除
This commit is contained in:
430
doc/api/ccdi_staff_recruitment_api.md
Normal file
430
doc/api/ccdi_staff_recruitment_api.md
Normal file
@@ -0,0 +1,430 @@
|
||||
# 员工招聘信息管理 API文档
|
||||
|
||||
**模块名称:** ccdi-staff-recruitment
|
||||
**版本:** 1.0
|
||||
**生成日期:** 2025-02-05
|
||||
**基础路径:** `/ccdi/staffRecruitment`
|
||||
|
||||
---
|
||||
|
||||
## 目录
|
||||
|
||||
1. [查询接口](#1-查询接口)
|
||||
2. [操作接口](#2-操作接口)
|
||||
3. [导入导出接口](#3-导入导出接口)
|
||||
4. [数据模型](#4-数据模型)
|
||||
5. [错误码说明](#5-错误码说明)
|
||||
|
||||
---
|
||||
|
||||
## 1. 查询接口
|
||||
|
||||
### 1.1 分页查询招聘信息列表
|
||||
|
||||
**接口描述:** 分页查询员工招聘信息列表,支持多条件筛选
|
||||
|
||||
**请求方式:** `GET`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/list`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:list`
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||
|-------|------|------|------|--------|
|
||||
| pageNum | Integer | 否 | 页码,默认1 | 1 |
|
||||
| pageSize | Integer | 否 | 每页条数,默认10 | 10 |
|
||||
| recruitName | String | 否 | 招聘项目名称(模糊查询) | 2025春季招聘 |
|
||||
| posName | String | 否 | 职位名称(模糊查询) | 软件工程师 |
|
||||
| candName | String | 否 | 候选人姓名(模糊查询) | 张三 |
|
||||
| candId | String | 否 | 证件号码(精确查询) | 110101199001011234 |
|
||||
| admitStatus | String | 否 | 录用状态(精确查询) | 录用/未录用/放弃 |
|
||||
| interviewerName | String | 否 | 面试官姓名(模糊查询,查询面试官1或2) | 李四 |
|
||||
| interviewerId | String | 否 | 面试官工号(精确查询,查询面试官1或2) | 10001 |
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "查询成功",
|
||||
"rows": [
|
||||
{
|
||||
"recruitId": "REC20250205001",
|
||||
"recruitName": "2025春季校园招聘",
|
||||
"posName": "Java开发工程师",
|
||||
"posCategory": "技术类",
|
||||
"posDesc": "负责后端系统开发",
|
||||
"candName": "张三",
|
||||
"candEdu": "本科",
|
||||
"candId": "110101199001011234",
|
||||
"candSchool": "清华大学",
|
||||
"candMajor": "计算机科学与技术",
|
||||
"candGrad": "202506",
|
||||
"admitStatus": "录用",
|
||||
"admitStatusDesc": "已录用该候选人",
|
||||
"interviewerName1": "李四",
|
||||
"interviewerId1": "10001",
|
||||
"interviewerName2": "王五",
|
||||
"interviewerId2": "10002",
|
||||
"createdBy": "admin",
|
||||
"createTime": "2025-02-05 10:00:00",
|
||||
"updatedBy": null,
|
||||
"updateTime": null
|
||||
}
|
||||
],
|
||||
"total": 100
|
||||
}
|
||||
```
|
||||
|
||||
### 1.2 查询招聘信息详情
|
||||
|
||||
**接口描述:** 根据招聘项目编号查询详细信息
|
||||
|
||||
**请求方式:** `GET`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/{recruitId}`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:query`
|
||||
|
||||
**路径参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||
|-------|------|------|------|--------|
|
||||
| recruitId | String | 是 | 招聘项目编号 | REC20250205001 |
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"data": {
|
||||
"recruitId": "REC20250205001",
|
||||
"recruitName": "2025春季校园招聘",
|
||||
"posName": "Java开发工程师",
|
||||
"posCategory": "技术类",
|
||||
"posDesc": "负责后端系统开发,要求熟悉Spring Boot、MyBatis Plus等框架",
|
||||
"candName": "张三",
|
||||
"candEdu": "本科",
|
||||
"candId": "110101199001011234",
|
||||
"candSchool": "清华大学",
|
||||
"candMajor": "计算机科学与技术",
|
||||
"candGrad": "202506",
|
||||
"admitStatus": "录用",
|
||||
"admitStatusDesc": "已录用该候选人",
|
||||
"interviewerName1": "李四",
|
||||
"interviewerId1": "10001",
|
||||
"interviewerName2": "王五",
|
||||
"interviewerId2": "10002",
|
||||
"createdBy": "admin",
|
||||
"createTime": "2025-02-05 10:00:00",
|
||||
"updatedBy": null,
|
||||
"updateTime": null
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 2. 操作接口
|
||||
|
||||
### 2.1 新增招聘信息
|
||||
|
||||
**接口描述:** 新增一条员工招聘信息
|
||||
|
||||
**请求方式:** `POST`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:add`
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"recruitId": "REC20250205001",
|
||||
"recruitName": "2025春季校园招聘",
|
||||
"posName": "Java开发工程师",
|
||||
"posCategory": "技术类",
|
||||
"posDesc": "负责后端系统开发",
|
||||
"candName": "张三",
|
||||
"candEdu": "本科",
|
||||
"candId": "110101199001011234",
|
||||
"candSchool": "清华大学",
|
||||
"candMajor": "计算机科学与技术",
|
||||
"candGrad": "202506",
|
||||
"admitStatus": "录用",
|
||||
"interviewerName1": "李四",
|
||||
"interviewerId1": "10001",
|
||||
"interviewerName2": "王五",
|
||||
"interviewerId2": "10002"
|
||||
}
|
||||
```
|
||||
|
||||
**字段校验规则:**
|
||||
|
||||
| 字段 | 校验规则 | 错误提示 |
|
||||
|-----|---------|---------|
|
||||
| recruitId | @NotBlank, @Size(max=32) | 招聘项目编号不能为空/长度不能超过32 |
|
||||
| recruitName | @NotBlank, @Size(max=100) | 招聘项目名称不能为空/长度不能超过100 |
|
||||
| posName | @NotBlank, @Size(max=100) | 职位名称不能为空/长度不能超过100 |
|
||||
| posCategory | @NotBlank, @Size(max=50) | 职位类别不能为空/长度不能超过50 |
|
||||
| posDesc | @NotBlank | 职位描述不能为空 |
|
||||
| candName | @NotBlank, @Size(max=20) | 应聘人员姓名不能为空/长度不能超过20 |
|
||||
| candEdu | @NotBlank, @Size(max=20) | 应聘人员学历不能为空/长度不能超过20 |
|
||||
| candId | @NotBlank, @Pattern(身份证正则) | 证件号码不能为空/格式不正确 |
|
||||
| candSchool | @NotBlank, @Size(max=50) | 应聘人员毕业院校不能为空/长度不能超过50 |
|
||||
| candMajor | @NotBlank, @Size(max=30) | 应聘人员专业不能为空/长度不能超过30 |
|
||||
| candGrad | @NotBlank, @Pattern(YYYYMM) | 毕业年月不能为空/格式不正确 |
|
||||
| admitStatus | @NotBlank, @EnumValid | 录用情况不能为空/状态值不合法 |
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.2 修改招聘信息
|
||||
|
||||
**接口描述:** 修改已有的员工招聘信息
|
||||
|
||||
**请求方式:** `PUT`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:edit`
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"recruitId": "REC20250205001",
|
||||
"recruitName": "2025春季校园招聘",
|
||||
"posName": "Java开发工程师",
|
||||
"posCategory": "技术类",
|
||||
"posDesc": "负责后端系统开发,负责核心模块设计",
|
||||
"candName": "张三",
|
||||
"candEdu": "本科",
|
||||
"candId": "110101199001011234",
|
||||
"candSchool": "清华大学",
|
||||
"candMajor": "计算机科学与技术",
|
||||
"candGrad": "202506",
|
||||
"admitStatus": "录用",
|
||||
"interviewerName1": "李四",
|
||||
"interviewerId1": "10001",
|
||||
"interviewerName2": "王五",
|
||||
"interviewerId2": "10002"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
### 2.3 删除招聘信息
|
||||
|
||||
**接口描述:** 批量删除员工招聘信息
|
||||
|
||||
**请求方式:** `DELETE`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/{recruitIds}`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:remove`
|
||||
|
||||
**路径参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||
|-------|------|------|------|--------|
|
||||
| recruitIds | String[] | 是 | 招聘项目编号数组,多个用逗号分隔 | REC20250205001,REC20250205002 |
|
||||
|
||||
**响应示例:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. 导入导出接口
|
||||
|
||||
### 3.1 下载导入模板
|
||||
|
||||
**接口描述:** 下载Excel导入模板
|
||||
|
||||
**请求方式:** `POST`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/importTemplate`
|
||||
|
||||
**权限标识:** 无
|
||||
|
||||
**响应:** Excel文件流
|
||||
|
||||
**模板字段顺序:**
|
||||
|
||||
| 序号 | 字段名 | 说明 | 必填 |
|
||||
|-----|--------|------|------|
|
||||
| 1 | 招聘项目编号 | 唯一标识 | 是 |
|
||||
| 2 | 招聘项目名称 | - | 是 |
|
||||
| 3 | 职位名称 | - | 是 |
|
||||
| 4 | 职位类别 | - | 是 |
|
||||
| 5 | 职位描述 | - | 是 |
|
||||
| 6 | 应聘人员姓名 | - | 是 |
|
||||
| 7 | 应聘人员学历 | - | 是 |
|
||||
| 8 | 应聘人员证件号码 | 身份证号 | 是 |
|
||||
| 9 | 应聘人员毕业院校 | - | 是 |
|
||||
| 10 | 应聘人员专业 | - | 是 |
|
||||
| 11 | 应聘人员毕业年月 | 格式:YYYYMM | 是 |
|
||||
| 12 | 录用情况 | 录用/未录用/放弃 | 是 |
|
||||
| 13 | 面试官1姓名 | - | 否 |
|
||||
| 14 | 面试官1工号 | - | 否 |
|
||||
| 15 | 面试官2姓名 | - | 否 |
|
||||
| 16 | 面试官2工号 | - | 否 |
|
||||
|
||||
### 3.2 批量导入
|
||||
|
||||
**接口描述:** 通过Excel批量导入招聘信息
|
||||
|
||||
**请求方式:** `POST`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/importData?updateSupport={updateSupport}`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:import`
|
||||
|
||||
**请求参数:**
|
||||
|
||||
| 参数名 | 类型 | 必填 | 说明 | 示例值 |
|
||||
|-------|------|------|------|--------|
|
||||
| updateSupport | Boolean | 否 | 是否更新已存在的数据 | true |
|
||||
| file | File | 是 | Excel文件 | - |
|
||||
|
||||
**请求类型:** `multipart/form-data`
|
||||
|
||||
**响应示例 (成功):**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "恭喜您,数据已全部导入成功!共 10 条,数据类型:新增 8 条,更新 2 条"
|
||||
}
|
||||
```
|
||||
|
||||
**响应示例 (部分失败):**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 500,
|
||||
"msg": "很抱歉,导入完成!成功 8 条,失败 2 条,错误如下:<br/>1、招聘项目编号 REC001 导入失败:该招聘项目编号已存在<br/>2、招聘项目编号 REC002 导入失败:证件号码格式不正确"
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 导出
|
||||
|
||||
**接口描述:** 导出招聘信息到Excel
|
||||
|
||||
**请求方式:** `POST`
|
||||
|
||||
**接口路径:** `/ccdi/staffRecruitment/export`
|
||||
|
||||
**权限标识:** `ccdi:staffRecruitment:export`
|
||||
|
||||
**请求参数:** 与分页查询接口相同的查询条件
|
||||
|
||||
**响应:** Excel文件流
|
||||
|
||||
---
|
||||
|
||||
## 4. 数据模型
|
||||
|
||||
### 4.1 录用状态枚举 (AdmitStatus)
|
||||
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| 录用 | 已录用该候选人 |
|
||||
| 未录用 | 未录用该候选人 |
|
||||
| 放弃 | 候选人放弃 |
|
||||
|
||||
### 4.2 CcdiStaffRecruitmentVO
|
||||
|
||||
招聘信息返回对象,包含所有字段及状态描述。
|
||||
|
||||
### 4.3 CcdiStaffRecruitmentExcel
|
||||
|
||||
Excel导入导出对象,使用EasyExcel注解。
|
||||
|
||||
---
|
||||
|
||||
## 5. 错误码说明
|
||||
|
||||
| 错误码 | 说明 |
|
||||
|--------|------|
|
||||
| 200 | 操作成功 |
|
||||
| 400 | 参数校验失败 |
|
||||
| 401 | 未授权,请先登录 |
|
||||
| 403 | 无权限访问 |
|
||||
| 404 | 资源不存在 |
|
||||
| 409 | 主键冲突 |
|
||||
| 500 | 服务器内部错误 |
|
||||
|
||||
### 常见业务错误
|
||||
|
||||
| 错误信息 | 说明 |
|
||||
|---------|------|
|
||||
| 该招聘项目编号已存在 | 新增时recruitId重复 |
|
||||
| 招聘项目编号不能为空 | recruitId字段为空 |
|
||||
| 证件号码格式不正确 | 身份证号格式验证失败 |
|
||||
| 毕业年月格式不正确 | candGrad不是YYYYMM格式 |
|
||||
| 录用情况状态值不合法 | admitStatus不是枚举值之一 |
|
||||
|
||||
---
|
||||
|
||||
## 附录
|
||||
|
||||
### Swagger UI
|
||||
|
||||
访问地址: `/swagger-ui/index.html`
|
||||
|
||||
### 测试账号
|
||||
|
||||
- 用户名: admin
|
||||
- 密码: admin123
|
||||
|
||||
### Token获取
|
||||
|
||||
**接口:** POST `/login`
|
||||
|
||||
**请求体:**
|
||||
|
||||
```json
|
||||
{
|
||||
"username": "admin",
|
||||
"password": "admin123"
|
||||
}
|
||||
```
|
||||
|
||||
**响应:**
|
||||
|
||||
```json
|
||||
{
|
||||
"code": 200,
|
||||
"msg": "操作成功",
|
||||
"token": "Bearer eyJhbGciOiJIUzUxMiJ9..."
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**文档生成时间:** 2025-02-05
|
||||
**文档版本:** 1.0
|
||||
@@ -1,347 +0,0 @@
|
||||
# 员工招聘信息管理功能设计文档
|
||||
|
||||
**文档版本:** 1.0
|
||||
**创建日期:** 2025-02-05
|
||||
**模块名称:** ccdi-staff-recruitment
|
||||
**作者:** Claude
|
||||
|
||||
---
|
||||
|
||||
## 1. 概述
|
||||
|
||||
### 1.1 功能简介
|
||||
员工招聘信息管理模块提供招聘信息的记录、查询、导入导出等基础维护功能,支持单条和批量操作。
|
||||
|
||||
### 1.2 业务场景
|
||||
- 简单的招聘信息记录,作为数据存档使用
|
||||
- 支持招聘信息的增删改查操作
|
||||
- 支持Excel批量导入和导出
|
||||
|
||||
### 1.3 技术选型
|
||||
- **后端框架:** Spring Boot 3.5.8 + MyBatis Plus 3.5.10
|
||||
- **数据库:** MySQL 8.2.0
|
||||
- **前端框架:** Vue 2.6.12 + Element UI 2.15.14
|
||||
- **数据校验:** javax.validation + 自定义校验注解
|
||||
|
||||
---
|
||||
|
||||
## 2. 数据库设计
|
||||
|
||||
### 2.1 表结构
|
||||
|
||||
**表名:** `ccdi_staff_recruitment`
|
||||
|
||||
```sql
|
||||
CREATE TABLE `ccdi_staff_recruitment` (
|
||||
`recruit_id` varchar(32) NOT NULL COMMENT '招聘项目编号',
|
||||
`recruit_name` varchar(100) NOT NULL COMMENT '招聘项目名称',
|
||||
`pos_name` varchar(100) NOT NULL COMMENT '职位名称',
|
||||
`pos_category` varchar(50) NOT NULL COMMENT '职位类别',
|
||||
`pos_desc` text NOT NULL COMMENT '职位描述',
|
||||
`cand_name` varchar(20) NOT NULL COMMENT '应聘人员姓名',
|
||||
`cand_edu` varchar(20) NOT NULL COMMENT '应聘人员学历',
|
||||
`cand_id` varchar(18) NOT NULL COMMENT '应聘人员证件号码',
|
||||
`cand_school` varchar(50) NOT NULL COMMENT '应聘人员毕业院校',
|
||||
`cand_major` varchar(30) NOT NULL COMMENT '应聘人员专业',
|
||||
`cand_grad` varchar(6) NOT NULL COMMENT '应聘人员毕业年月',
|
||||
`admit_status` varchar(10) NOT NULL COMMENT '录用情况:录用、未录用、放弃',
|
||||
`interviewer_name1` varchar(20) DEFAULT NULL COMMENT '面试官1姓名',
|
||||
`interviewer_id1` varchar(10) DEFAULT NULL COMMENT '面试官1工号',
|
||||
`interviewer_name2` varchar(20) DEFAULT NULL COMMENT '面试官2姓名',
|
||||
`interviewer_id2` varchar(10) DEFAULT NULL COMMENT '面试官2工号',
|
||||
`created_by` varchar(20) NOT NULL COMMENT '记录创建人',
|
||||
`updated_by` varchar(20) DEFAULT NULL COMMENT '记录更新人',
|
||||
`create_time` datetime DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
|
||||
`update_time` datetime DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
|
||||
PRIMARY KEY (`recruit_id`),
|
||||
KEY `idx_cand_id` (`cand_id`),
|
||||
KEY `idx_admit_status` (`admit_status`),
|
||||
KEY `idx_interviewer_id1` (`interviewer_id1`)
|
||||
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='员工招聘信息表';
|
||||
```
|
||||
|
||||
### 2.2 索引设计
|
||||
- **主键索引:** `recruit_id`
|
||||
- **业务索引:** `cand_id`, `admit_status`, `interviewer_id1`
|
||||
|
||||
### 2.3 枚举值设计
|
||||
|
||||
**录用状态 (admit_status):**
|
||||
| 枚举值 | 说明 |
|
||||
|--------|------|
|
||||
| 录用 | 已录用该候选人 |
|
||||
| 未录用 | 未录用该候选人 |
|
||||
| 放弃 | 候选人放弃 |
|
||||
|
||||
---
|
||||
|
||||
## 3. 后端设计
|
||||
|
||||
### 3.1 模块结构
|
||||
|
||||
```
|
||||
ruoyi-ccdi/
|
||||
├── domain/
|
||||
│ ├── CcdiStaffRecruitment.java # 实体类
|
||||
│ ├── dto/
|
||||
│ │ ├── CcdiStaffRecruitmentQueryDTO.java # 查询DTO
|
||||
│ │ ├── CcdiStaffRecruitmentAddDTO.java # 新增DTO
|
||||
│ │ └── CcdiStaffRecruitmentEditDTO.java # 修改DTO
|
||||
│ ├── vo/
|
||||
│ │ └── CcdiStaffRecruitmentVO.java # 返回VO
|
||||
│ └── excel/
|
||||
│ └── CcdiStaffRecruitmentExcel.java # Excel导入导出类
|
||||
├── mapper/
|
||||
│ ├── CcdiStaffRecruitmentMapper.java # MyBatis Mapper接口
|
||||
│ └── xml/
|
||||
│ └── CcdiStaffRecruitmentMapper.xml # MyBatis XML映射
|
||||
├── service/
|
||||
│ ├── ICcdiStaffRecruitmentService.java # 服务接口
|
||||
│ └── impl/
|
||||
│ └── CcdiStaffRecruitmentServiceImpl.java # 服务实现
|
||||
└── controller/
|
||||
└── CcdiStaffRecruitmentController.java # 控制器
|
||||
```
|
||||
|
||||
### 3.2 API接口设计
|
||||
|
||||
**基础路径:** `/ccdi/staffRecruitment`
|
||||
|
||||
| 接口功能 | HTTP方法 | 路径 | 权限标识 |
|
||||
|---------|---------|------|---------|
|
||||
| 分页查询 | GET | `/list` | ccdi:staffRecruitment:list |
|
||||
| 详情查询 | GET | `/{recruitId}` | ccdi:staffRecruitment:query |
|
||||
| 新增 | POST | `/` | ccdi:staffRecruitment:add |
|
||||
| 修改 | PUT | `/` | ccdi:staffRecruitment:edit |
|
||||
| 删除 | DELETE | `/{recruitIds}` | ccdi:staffRecruitment:remove |
|
||||
| 导入模板下载 | GET | `/importTemplate` | ccdi:staffRecruitment:import |
|
||||
| 批量导入 | POST | `/importData` | ccdi:staffRecruitment:import |
|
||||
| 导出 | POST | `/export` | ccdi:staffRecruitment:export |
|
||||
|
||||
### 3.3 查询参数设计
|
||||
|
||||
**CcdiStaffRecruitmentQueryDTO:**
|
||||
```java
|
||||
// 查询条件
|
||||
private String recruitName; // 招聘项目名称(模糊查询)
|
||||
private String posName; // 职位名称(模糊查询)
|
||||
private String candName; // 候选人姓名(模糊查询)
|
||||
private String candId; // 证件号码(精确查询)
|
||||
private String admitStatus; // 录用状态(精确查询)
|
||||
private String interviewerName; // 面试官姓名(模糊查询,查询面试官1或2)
|
||||
private String interviewerId; // 面试官工号(精确查询,查询面试官1或2)
|
||||
|
||||
// 分页参数
|
||||
private Integer pageNum = 1;
|
||||
private Integer pageSize = 10;
|
||||
```
|
||||
|
||||
### 3.4 数据校验规则
|
||||
|
||||
| 字段 | 校验规则 | 错误提示 |
|
||||
|-----|---------|---------|
|
||||
| recruitName | @NotBlank, @Size(max=100) | 招聘项目名称不能为空/长度不能超过100 |
|
||||
| posName | @NotBlank, @Size(max=100) | 职位名称不能为空/长度不能超过100 |
|
||||
| candName | @NotBlank, @Size(max=20) | 应聘人员姓名不能为空/长度不能超过20 |
|
||||
| candId | @NotBlank, @Pattern(身份证正则) | 证件号码不能为空/格式不正确 |
|
||||
| candGrad | @NotBlank, @Pattern(YYYYMM) | 毕业年月不能为空/格式不正确 |
|
||||
| admitStatus | @NotBlank, @EnumValid | 录用情况不能为空/状态值不合法 |
|
||||
|
||||
### 3.5 批量导入功能设计
|
||||
|
||||
**核心优化点:**
|
||||
1. **批量查询已存在记录:** 使用 `selectBatchIds` 一次性查询
|
||||
2. **批量插入:** 使用 `saveBatch()` 方法
|
||||
3. **批量更新:** 使用 `updateBatchById()` 方法
|
||||
4. **错误信息:** 只返回错误的数据行,成功数据不展示
|
||||
|
||||
**性能提升:**
|
||||
- 原方案: ~3000次数据库操作 (导入1000条)
|
||||
- 优化后: ~3次数据库操作 (导入1000条)
|
||||
- 性能提升: ~1000倍
|
||||
|
||||
**导入逻辑:**
|
||||
```
|
||||
1. 收集所有recruit_id
|
||||
2. 批量查询已存在的记录
|
||||
3. 遍历Excel数据:
|
||||
- 数据转换和校验
|
||||
- 分类为: 待新增列表、待更新列表
|
||||
- 记录校验失败的数据
|
||||
4. 批量插入待新增数据
|
||||
5. 批量更新待更新数据
|
||||
6. 只返回错误信息
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. 前端设计
|
||||
|
||||
### 4.1 页面结构
|
||||
|
||||
```
|
||||
ruoyi-ui/src/views/ccdiStaffRecruitment/
|
||||
├── index.vue # 列表页面(主页面)
|
||||
└── components/
|
||||
├── RecruitmentForm.vue # 新增/修改表单组件
|
||||
└── ImportDialog.vue # 导入对话框组件
|
||||
```
|
||||
|
||||
### 4.2 功能列表
|
||||
|
||||
**列表页面 (index.vue):**
|
||||
- 顶部查询表单
|
||||
- 招聘项目名称(模糊查询)
|
||||
- 职位名称(模糊查询)
|
||||
- 候选人姓名(模糊查询)
|
||||
- 证件号码(精确查询)
|
||||
- 录用状态(下拉选择)
|
||||
- 面试官姓名(模糊查询)
|
||||
- 面试官工号(精确查询)
|
||||
- 数据表格
|
||||
- 展示所有字段信息
|
||||
- 支持排序
|
||||
- 操作按钮
|
||||
- 新增
|
||||
- 批量导入
|
||||
- 导出
|
||||
- 批量删除
|
||||
- 行操作
|
||||
- 修改
|
||||
- 删除
|
||||
|
||||
**表单组件 (RecruitmentForm.vue):**
|
||||
- 所有必填字段添加 `required: true`
|
||||
- 证件号码正则校验
|
||||
- 毕业年月格式校验(YYYYMM)
|
||||
- 录用状态下拉选择(枚举值)
|
||||
|
||||
---
|
||||
|
||||
## 5. 异常处理
|
||||
|
||||
### 5.1 异常分类
|
||||
|
||||
| 异常类型 | HTTP状态码 | 使用场景 |
|
||||
|---------|-----------|---------|
|
||||
| ServiceException | 500 | 业务逻辑异常 |
|
||||
| ValidationException | 400 | 参数校验失败 |
|
||||
| DuplicateKeyException | 409 | 主键冲突 |
|
||||
| FileNotFoundException | 404 | 文件不存在 |
|
||||
|
||||
### 5.2 统一异常处理
|
||||
|
||||
使用 `@RestControllerAdvice` 全局异常处理器捕获和处理异常。
|
||||
|
||||
---
|
||||
|
||||
## 6. 测试策略
|
||||
|
||||
### 6.1 单元测试
|
||||
|
||||
**测试范围:**
|
||||
- 实体类校验注解测试
|
||||
- 数据转换工具方法测试
|
||||
- 业务逻辑核心方法测试
|
||||
|
||||
**关键测试用例:**
|
||||
1. 正常数据导入测试
|
||||
2. 身份证格式校验测试
|
||||
3. 批量插入性能测试
|
||||
|
||||
### 6.2 集成测试
|
||||
|
||||
**测试流程:**
|
||||
1. 登录获取Token
|
||||
2. 分页查询测试
|
||||
3. 单条新增测试
|
||||
4. 单条修改测试
|
||||
5. 批量导入测试
|
||||
6. 导出测试
|
||||
7. 批量删除测试
|
||||
|
||||
### 6.3 性能指标
|
||||
|
||||
| 测试场景 | 预期性能 |
|
||||
|---------|---------|
|
||||
| 分页查询(1000条) | < 200ms |
|
||||
| 单条新增 | < 100ms |
|
||||
| 批量导入(1000条) | < 5s |
|
||||
| 批量删除(100条) | < 500ms |
|
||||
| 导出(1000条) | < 2s |
|
||||
|
||||
---
|
||||
|
||||
## 7. 实施步骤
|
||||
|
||||
### 第一步:数据库准备
|
||||
1. 执行建表SQL
|
||||
2. 在菜单表中添加菜单和权限配置
|
||||
|
||||
### 第二步:后端开发
|
||||
1. 创建枚举类
|
||||
2. 创建实体类、DTO、VO、Excel类
|
||||
3. 创建Mapper接口和XML
|
||||
4. 创建Service接口和实现
|
||||
5. 创建Controller
|
||||
6. 编写单元测试
|
||||
7. Swagger-UI测试
|
||||
|
||||
### 第三步:前端开发
|
||||
1. 创建API接口定义
|
||||
2. 开发表格查询页面
|
||||
3. 开发表单组件
|
||||
4. 开发导入对话框
|
||||
5. 配置路由
|
||||
6. 配置菜单
|
||||
|
||||
### 第四步:集成测试
|
||||
1. 准备测试数据
|
||||
2. 执行集成测试
|
||||
3. 验证功能
|
||||
4. 生成测试报告
|
||||
|
||||
### 第五步:文档编写
|
||||
1. 生成API文档
|
||||
2. 编写使用说明
|
||||
|
||||
---
|
||||
|
||||
## 8. 附录
|
||||
|
||||
### 8.1 Excel导入模板字段顺序
|
||||
|
||||
按CSV字段顺序设计:
|
||||
1. 招聘项目编号
|
||||
2. 招聘项目名称
|
||||
3. 职位名称
|
||||
4. 职位类别
|
||||
5. 职位描述
|
||||
6. 应聘人员姓名
|
||||
7. 应聘人员学历
|
||||
8. 应聘人员证件号码
|
||||
9. 应聘人员毕业院校
|
||||
10. 应聘人员专业
|
||||
11. 应聘人员毕业年月
|
||||
12. 录用情况
|
||||
13. 面试官1姓名
|
||||
14. 面试官1工号
|
||||
15. 面试官2姓名
|
||||
16. 面试官2工号
|
||||
|
||||
### 8.2 MyBatis Plus配置
|
||||
|
||||
确保项目中已配置MyBatis Plus分页插件:
|
||||
|
||||
```java
|
||||
@Bean
|
||||
public MybatisPlusInterceptor mybatisPlusInterceptor() {
|
||||
MybatisPlusInterceptor interceptor = new MybatisPlusInterceptor();
|
||||
interceptor.addInnerInterceptor(new PaginationInnerInterceptor(DbType.MYSQL));
|
||||
return interceptor;
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
915
doc/plans/2026-02-05-导入逻辑优化实施计划.md
Normal file
915
doc/plans/2026-02-05-导入逻辑优化实施计划.md
Normal file
@@ -0,0 +1,915 @@
|
||||
# 导入逻辑优化实施计划
|
||||
|
||||
> **For Claude:** REQUIRED SUB-SKILL: Use superpowers:executing-plans to implement this plan task-by-task.
|
||||
|
||||
**目标:** 优化员工信息、中介库(个人/实体)、招聘信息的导入功能,从"存在则更新"改为"先删除后插入"策略。
|
||||
|
||||
**架构:** 三阶段流程:数据验证 → 批量删除 → 批量插入。所有操作在一个 @Transactional 事务中执行。
|
||||
|
||||
**技术栈:** Spring Boot 3.5.8, MyBatis Plus 3.5.10, MySQL 8.2.0
|
||||
|
||||
---
|
||||
|
||||
## 模块 1:员工信息管理(验证方案)
|
||||
|
||||
此模块用于验证新逻辑的正确性,成功后应用到其他模块。
|
||||
|
||||
### Task 1.1:添加批量删除方法到 Mapper 接口
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java`
|
||||
|
||||
**Step 1: 在 Mapper 接口中添加方法声明**
|
||||
|
||||
在 `CcdiEmployeeMapper.java` 的接口中添加新方法(在现有方法后面,`insertBatch` 方法之后):
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据身份证号批量删除员工数据
|
||||
*
|
||||
* @param idCards 身份证号列表
|
||||
* @return 删除行数
|
||||
*/
|
||||
int deleteBatchByIdCard(@Param("list") List<String> idCards);
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
无需测试,这是接口声明。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java
|
||||
git commit -m "feat(employee): 添加批量删除方法声明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1.2:在 Mapper XML 中实现批量删除 SQL
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml`
|
||||
|
||||
**Step 1: 在 XML 文件中添加删除 SQL**
|
||||
|
||||
在 `CcdiEmployeeMapper.xml` 中,在 `insertBatch` 方法之后添加:
|
||||
|
||||
```xml
|
||||
<!-- 根据身份证号批量删除员工数据 -->
|
||||
<delete id="deleteBatchByIdCard">
|
||||
DELETE FROM ccdi_employee
|
||||
WHERE id_card IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
无需测试,SQL 配置。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml
|
||||
git commit -m "feat(employee): 实现批量删除SQL"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1.3:重构员工导入方法(先删后插逻辑)
|
||||
|
||||
- [x] **已完成** (commit: ebe4fd7)
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java`
|
||||
- 目标方法:`importEmployee` (第 172-311 行)
|
||||
|
||||
**Step 1: 备份原方法**
|
||||
|
||||
先注释掉原有的 `importEmployee` 方法(保留参考)。
|
||||
|
||||
**Step 2: 实现新的导入逻辑**
|
||||
|
||||
将整个 `importEmployee` 方法替换为:
|
||||
|
||||
```java
|
||||
/**
|
||||
* 导入员工数据(先删后插模式)
|
||||
*
|
||||
* @param excelList Excel实体列表
|
||||
* @param isUpdateSupport 是否更新支持(参数保留以保持兼容性,不再使用)
|
||||
* @return 结果
|
||||
*/
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importEmployee(List<CcdiEmployeeExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<CcdiEmployee> validEmployees = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> idCards = new HashSet<>();
|
||||
|
||||
for (CcdiEmployeeExcel excel : excelList) {
|
||||
try {
|
||||
// 转换为AddDTO
|
||||
CcdiEmployeeAddDTO addDTO = new CcdiEmployeeAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
|
||||
// 验证必填字段和数据格式
|
||||
validateEmployeeDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
if (!idCards.add(addDTO.getIdCard())) {
|
||||
throw new RuntimeException("导入文件中该身份证号重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
CcdiEmployee employee = new CcdiEmployee();
|
||||
BeanUtils.copyProperties(addDTO, employee);
|
||||
employee.setCreateBy("导入");
|
||||
employee.setUpdateBy("导入");
|
||||
|
||||
validEmployees.add(employee);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
excel.getName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validEmployees.isEmpty()) {
|
||||
employeeMapper.deleteBatchByIdCard(new ArrayList<>(idCards));
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validEmployees.isEmpty()) {
|
||||
employeeMapper.insertBatch(validEmployees);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
failureMsg.append("很抱歉,导入完成!成功 ")
|
||||
.append(validEmployees.size())
|
||||
.append(" 条,失败 ")
|
||||
.append(errorMessages.size())
|
||||
.append(" 条,错误如下:");
|
||||
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
failureMsg.append("<br/>")
|
||||
.append(i + 1)
|
||||
.append("、")
|
||||
.append(errorMessages.get(i));
|
||||
}
|
||||
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
}
|
||||
|
||||
return "恭喜您,数据已全部导入成功!共 " + validEmployees.size() + " 条";
|
||||
}
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
无需测试,代码修改。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java
|
||||
git commit -m "refactor(employee): 重构导入方法为先删后插模式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1.4:生成员工模块测试脚本
|
||||
|
||||
**文件:**
|
||||
- 创建:`test/test_employee_import_delete.ps1`
|
||||
|
||||
**Step 1: 创建测试脚本**
|
||||
|
||||
创建 PowerShell 测试脚本:
|
||||
|
||||
```powershell
|
||||
# 员工导入功能测试脚本(先删后插模式)
|
||||
# 目的:验证新的导入逻辑是否正常工作
|
||||
|
||||
# 配置
|
||||
$BaseUrl = "http://localhost:8080"
|
||||
$LoginUrl = "$BaseUrl/login/test"
|
||||
$ImportUrl = "$BaseUrl/ccdi/employee/importData"
|
||||
|
||||
# 测试账号
|
||||
$Username = "admin"
|
||||
$Password = "admin123"
|
||||
|
||||
# 日志文件
|
||||
$LogFile = "test/employee_import_test_$(Get-Date -Format 'yyyyMMdd_HHmmss').txt"
|
||||
|
||||
# 开始记录日志
|
||||
function Write-Log {
|
||||
param([string]$Message)
|
||||
$timestamp = Get-Date -Format "yyyy-MM-dd HH:mm:ss"
|
||||
$logMessage = "[$timestamp] $Message"
|
||||
Write-Host $logMessage
|
||||
Add-Content -Path $LogFile -Value $logMessage
|
||||
}
|
||||
|
||||
Write-Log "=========================================="
|
||||
Write-Log "员工导入功能测试(先删后插模式)"
|
||||
Write-Log "=========================================="
|
||||
Write-Log ""
|
||||
|
||||
# 步骤1:登录获取Token
|
||||
Write-Log "步骤1:登录获取Token..."
|
||||
try {
|
||||
$loginBody = @{
|
||||
username = $Username
|
||||
password = $Password
|
||||
} | ConvertTo-Json
|
||||
|
||||
$loginResponse = Invoke-RestMethod -Uri $LoginUrl -Method Post -Body $loginBody -ContentType "application/json"
|
||||
|
||||
if ($loginResponse.code -eq 200) {
|
||||
$Token = $loginResponse.token
|
||||
Write-Log "✓ 登录成功"
|
||||
} else {
|
||||
Write-Log "✗ 登录失败: $($loginResponse.msg)"
|
||||
exit 1
|
||||
}
|
||||
} catch {
|
||||
Write-Log "✗ 登录请求失败: $_"
|
||||
exit 1
|
||||
}
|
||||
|
||||
Write-Log ""
|
||||
|
||||
# 步骤2:准备测试数据
|
||||
Write-Log "步骤2:准备测试数据..."
|
||||
|
||||
$testData = @{
|
||||
list = @(
|
||||
@{
|
||||
employeeId = 1001
|
||||
name = "测试用户A"
|
||||
deptId = 103
|
||||
idCard = "110101199001011234"
|
||||
phone = "13800138001"
|
||||
hireDate = "2020-01-01"
|
||||
status = "0"
|
||||
},
|
||||
@{
|
||||
employeeId = 1002
|
||||
name = "测试用户B"
|
||||
deptId = 103
|
||||
idCard = "110101199001022345"
|
||||
phone = "13800138002"
|
||||
hireDate = "2020-01-02"
|
||||
status = "0"
|
||||
}
|
||||
)
|
||||
} | ConvertTo-Json -Depth 10
|
||||
|
||||
Write-Log "测试数据准备完成(2条记录)"
|
||||
Write-Log ""
|
||||
|
||||
# 步骤3:执行导入
|
||||
Write-Log "步骤3:执行导入..."
|
||||
|
||||
try {
|
||||
$headers = @{
|
||||
"Authorization" = "Bearer $Token"
|
||||
}
|
||||
|
||||
$importResponse = Invoke-RestMethod -Uri $ImportUrl -Method Post -Headers $headers -Body $testData -ContentType "application/json"
|
||||
|
||||
Write-Log ""
|
||||
Write-Log "=========================================="
|
||||
Write-Log "导入结果:"
|
||||
Write-Log "=========================================="
|
||||
Write-Log "响应代码: $($importResponse.code)"
|
||||
Write-Log "响应消息: $($importResponse.msg)"
|
||||
|
||||
if ($importResponse.code -eq 200) {
|
||||
Write-Log ""
|
||||
Write-Log "✓ 导入测试成功!"
|
||||
} else {
|
||||
Write-Log ""
|
||||
Write-Log "✗ 导入测试失败!"
|
||||
}
|
||||
|
||||
} catch {
|
||||
Write-Log ""
|
||||
Write-Log "✗ 导入请求失败:"
|
||||
Write-Log "错误信息: $_"
|
||||
}
|
||||
|
||||
Write-Log ""
|
||||
Write-Log "=========================================="
|
||||
Write-Log "测试完成"
|
||||
Write-Log "详细日志: $LogFile"
|
||||
Write-Log "=========================================="
|
||||
```
|
||||
|
||||
**Step 2: 保存文件**
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add test/test_employee_import_delete.ps1
|
||||
git commit -m "test(employee): 添加导入功能测试脚本"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 1.5:测试员工模块导入功能
|
||||
|
||||
**Step 1: 启动后端服务**
|
||||
|
||||
如果后端服务未启动,先启动:
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
**Step 2: 在新终端运行测试脚本**
|
||||
|
||||
```powershell
|
||||
cd D:\ccdi\ccdi
|
||||
.\test\test_employee_import_delete.ps1
|
||||
```
|
||||
|
||||
**Step 3: 验证结果**
|
||||
|
||||
检查:
|
||||
- ✅ 测试脚本显示 "导入测试成功"
|
||||
- ✅ 日志文件显示响应代码 200
|
||||
- ✅ 数据库中数据正确插入
|
||||
|
||||
**Step 4: 如果测试通过,提交工作**
|
||||
|
||||
```bash
|
||||
# 所有改动已提交,无需额外操作
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块 2:中介库个人管理
|
||||
|
||||
### Task 2.1:添加批量删除方法到 Mapper 接口
|
||||
|
||||
- [x] **已完成** (commit: ba8eedc)
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java`
|
||||
|
||||
**Step 1: 在 Mapper 接口中添加方法声明**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据个人证件号批量删除中介库个人数据
|
||||
*
|
||||
* @param personIds 个人证件号列表
|
||||
* @return 删除行数
|
||||
*/
|
||||
int deleteBatchByPersonId(@Param("list") List<String> personIds);
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java
|
||||
git commit -m "feat(intermediary): 添加个人批量删除方法声明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.2:在 Mapper XML 中实现批量删除 SQL
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiBizIntermediaryMapper.xml`
|
||||
|
||||
**Step 1: 在 XML 文件中添加删除 SQL**
|
||||
|
||||
```xml
|
||||
<!-- 根据个人证件号批量删除中介库个人数据 -->
|
||||
<delete id="deleteBatchByPersonId">
|
||||
DELETE FROM ccdi_biz_intermediary
|
||||
WHERE person_id IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiBizIntermediaryMapper.xml
|
||||
git commit -m "feat(intermediary): 实现个人批量删除SQL"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 2.3:重构中介库个人导入方法
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
|
||||
- 目标方法:`importIntermediaryPerson`
|
||||
|
||||
**Step 1: 找到 `importIntermediaryPerson` 方法**
|
||||
|
||||
在 `CcdiIntermediaryServiceImpl.java` 中定位方法。
|
||||
|
||||
**Step 2: 重构方法逻辑**
|
||||
|
||||
参考员工模块的模式,重构为先删后插:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importIntermediaryPerson(List<CcdiIntermediaryPersonExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<CcdiBizIntermediary> validList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> personIds = new HashSet<>();
|
||||
|
||||
for (CcdiIntermediaryPersonExcel excel : excelList) {
|
||||
try {
|
||||
// 转换并验证
|
||||
CcdiIntermediaryPersonAddDTO addDTO = new CcdiIntermediaryPersonAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
// 调用验证方法(需要根据实际情况调整)
|
||||
// validateIntermediaryPersonDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
if (!personIds.add(addDTO.getPersonId())) {
|
||||
throw new RuntimeException("导入文件中该个人证件号重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
CcdiBizIntermediary entity = new CcdiBizIntermediary();
|
||||
BeanUtils.copyProperties(addDTO, entity);
|
||||
entity.setCreateBy("导入");
|
||||
entity.setUpdateBy("导入");
|
||||
|
||||
validList.add(entity);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
excel.getName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiBizIntermediaryMapper.deleteBatchByPersonId(new ArrayList<>(personIds));
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiBizIntermediaryMapper.insertBatch(validList);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
failureMsg.append("很抱歉,导入完成!成功 ")
|
||||
.append(validList.size())
|
||||
.append(" 条,失败 ")
|
||||
.append(errorMessages.size())
|
||||
.append(" 条,错误如下:");
|
||||
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
failureMsg.append("<br/>")
|
||||
.append(i + 1)
|
||||
.append("、")
|
||||
.append(errorMessages.get(i));
|
||||
}
|
||||
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
}
|
||||
|
||||
return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条";
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java
|
||||
git commit -m "refactor(intermediary): 重构个人导入方法为先删后插模式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块 3:中介库实体管理
|
||||
|
||||
### Task 3.1:添加批量删除方法到 Mapper 接口
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java`
|
||||
|
||||
**Step 1: 在 Mapper 接口中添加方法声明**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据统一社会信用代码批量删除中介库实体数据
|
||||
*
|
||||
* @param socialCreditCodes 统一社会信用代码列表
|
||||
* @return 删除行数
|
||||
*/
|
||||
int deleteBatchBySocialCreditCode(@Param("list") List<String> socialCreditCodes);
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java
|
||||
git commit -m "feat(intermediary): 添加实体批量删除方法声明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3.2:在 Mapper XML 中实现批量删除 SQL
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEnterpriseBaseInfoMapper.xml`
|
||||
|
||||
**Step 1: 在 XML 文件中添加删除 SQL**
|
||||
|
||||
```xml
|
||||
<!-- 根据统一社会信用代码批量删除中介库实体数据 -->
|
||||
<delete id="deleteBatchBySocialCreditCode">
|
||||
DELETE FROM ccdi_enterprise_base_info
|
||||
WHERE social_credit_code IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEnterpriseBaseInfoMapper.xml
|
||||
git commit -m "feat(intermediary): 实现实体批量删除SQL"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 3.3:重构中介库实体导入方法
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
|
||||
- 目标方法:`importIntermediaryEntity`
|
||||
|
||||
**Step 1: 找到 `importIntermediaryEntity` 方法**
|
||||
|
||||
在 `CcdiIntermediaryServiceImpl.java` 中定位方法。
|
||||
|
||||
**Step 2: 重构方法逻辑**
|
||||
|
||||
参考个人模块的模式,重构为先删后插:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importIntermediaryEntity(List<CcdiIntermediaryEntityExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<CcdiEnterpriseBaseInfo> validList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> socialCreditCodes = new HashSet<>();
|
||||
|
||||
for (CcdiIntermediaryEntityExcel excel : excelList) {
|
||||
try {
|
||||
// 转换并验证
|
||||
CcdiIntermediaryEntityAddDTO addDTO = new CcdiIntermediaryEntityAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
// 调用验证方法(需要根据实际情况调整)
|
||||
// validateIntermediaryEntityDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
if (!socialCreditCodes.add(addDTO.getSocialCreditCode())) {
|
||||
throw new RuntimeException("导入文件中该统一社会信用代码重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||
BeanUtils.copyProperties(addDTO, entity);
|
||||
entity.setCreateBy("导入");
|
||||
entity.setUpdateBy("导入");
|
||||
|
||||
validList.add(entity);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
excel.getEnterpriseName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiEnterpriseBaseInfoMapper.deleteBatchBySocialCreditCode(new ArrayList<>(socialCreditCodes));
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiEnterpriseBaseInfoMapper.insertBatch(validList);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
failureMsg.append("很抱歉,导入完成!成功 ")
|
||||
.append(validList.size())
|
||||
.append(" 条,失败 ")
|
||||
.append(errorMessages.size())
|
||||
.append(" 条,错误如下:");
|
||||
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
failureMsg.append("<br/>")
|
||||
.append(i + 1)
|
||||
.append("、")
|
||||
.append(errorMessages.get(i));
|
||||
}
|
||||
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
}
|
||||
|
||||
return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条";
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java
|
||||
git commit -m "refactor(intermediary): 重构实体导入方法为先删后插模式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块 4:员工招聘信息管理
|
||||
|
||||
### Task 4.1:添加批量删除方法到 Mapper 接口
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffRecruitmentMapper.java`
|
||||
|
||||
**Step 1: 在 Mapper 接口中添加方法声明**
|
||||
|
||||
```java
|
||||
/**
|
||||
* 根据招聘项目编号批量删除招聘信息数据
|
||||
*
|
||||
* @param recruitIds 招聘项目编号列表
|
||||
* @return 删除行数
|
||||
*/
|
||||
int deleteBatchByRecruitId(@Param("list") List<String> recruitIds);
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffRecruitmentMapper.java
|
||||
git commit -m "feat(recruitment): 添加批量删除方法声明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4.2:在 Mapper XML 中实现批量删除 SQL
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffRecruitmentMapper.xml`
|
||||
|
||||
**Step 1: 在 XML 文件中添加删除 SQL**
|
||||
|
||||
```xml
|
||||
<!-- 根据招聘项目编号批量删除招聘信息数据 -->
|
||||
<delete id="deleteBatchByRecruitId">
|
||||
DELETE FROM ccdi_staff_recruitment
|
||||
WHERE recruit_id IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffRecruitmentMapper.xml
|
||||
git commit -m "feat(recruitment): 实现批量删除SQL"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 4.3:重构招聘信息导入方法
|
||||
|
||||
**文件:**
|
||||
- 修改:`ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentServiceImpl.java`
|
||||
- 目标方法:`importRecruitment`
|
||||
|
||||
**Step 1: 找到 `importRecruitment` 方法**
|
||||
|
||||
在 `CcdiStaffRecruitmentServiceImpl.java` 中定位方法。
|
||||
|
||||
**Step 2: 重构方法逻辑**
|
||||
|
||||
参考员工模块的模式,重构为先删后插:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importRecruitment(List<CcdiStaffRecruitmentExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<CcdiStaffRecruitment> validList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> recruitIds = new HashSet<>();
|
||||
|
||||
for (CcdiStaffRecruitmentExcel excel : excelList) {
|
||||
try {
|
||||
// 转换并验证
|
||||
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
// 调用验证方法(需要根据实际情况调整)
|
||||
// validateRecruitmentDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
if (!recruitIds.add(addDTO.getRecruitId())) {
|
||||
throw new RuntimeException("导入文件中该招聘项目编号重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
CcdiStaffRecruitment entity = new CcdiStaffRecruitment();
|
||||
BeanUtils.copyProperties(addDTO, entity);
|
||||
entity.setCreateBy("导入");
|
||||
entity.setUpdateBy("导入");
|
||||
|
||||
validList.add(entity);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
excel.getRecruitName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiStaffRecruitmentMapper.deleteBatchByRecruitId(new ArrayList<>(recruitIds));
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validList.isEmpty()) {
|
||||
ccdiStaffRecruitmentMapper.insertBatch(validList);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
failureMsg.append("很抱歉,导入完成!成功 ")
|
||||
.append(validList.size())
|
||||
.append(" 条,失败 ")
|
||||
.append(errorMessages.size())
|
||||
.append(" 条,错误如下:");
|
||||
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
failureMsg.append("<br/>")
|
||||
.append(i + 1)
|
||||
.append("、")
|
||||
.append(errorMessages.get(i));
|
||||
}
|
||||
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
}
|
||||
|
||||
return "恭喜您,数据已全部导入成功!共 " + validList.size() + " 条";
|
||||
}
|
||||
```
|
||||
|
||||
**注意**:需要根据实际的 DTO 类名、验证方法名、Mapper 注入名进行调整。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git add ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentServiceImpl.java
|
||||
git commit -m "refactor(recruitment): 重构导入方法为先删后插模式"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 模块 5:清理和文档
|
||||
|
||||
### Task 5.1:移除不再使用的批量更新方法(如果存在)
|
||||
|
||||
**文件:**
|
||||
- 检查:各模块的 Mapper XML 和 Mapper 接口
|
||||
|
||||
**Step 1: 检查是否存在 updateBatch 方法**
|
||||
|
||||
在以下文件中搜索 `updateBatch`:
|
||||
- `CcdiEmployeeMapper.xml`
|
||||
- `CcdiBizIntermediaryMapper.xml`
|
||||
- `CcdiEnterpriseBaseInfoMapper.xml`
|
||||
- `CcdiStaffRecruitmentMapper.xml`
|
||||
|
||||
**Step 2: 如果存在,删除 updateBatch 方法**
|
||||
|
||||
删除不再使用的批量更新 SQL 和接口声明。
|
||||
|
||||
**Step 3: 提交**
|
||||
|
||||
```bash
|
||||
git commit -am "refactor: 移除不再使用的批量更新方法"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
### Task 5.2:更新 API 文档
|
||||
|
||||
**文件:**
|
||||
- 修改:`doc/api/ccdi_staff_recruitment_api.md`(如果存在)
|
||||
|
||||
**Step 1: 更新导入接口文档**
|
||||
|
||||
在 API 文档中说明新的导入逻辑:
|
||||
- 采用"先删除后插入"策略
|
||||
- `isUpdateSupport` 参数保留以保持兼容性,但不再使用
|
||||
- 所有审计字段(create_time, update_time 等)会被重置为当前时间
|
||||
|
||||
**Step 2: 提交**
|
||||
|
||||
```bash
|
||||
git add doc/api/
|
||||
git commit -m "docs: 更新导入接口文档说明"
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 完成检查清单
|
||||
|
||||
在完成所有任务后,确认以下事项:
|
||||
|
||||
- [ ] 员工信息模块测试通过
|
||||
- [ ] 中介库个人模块功能正常
|
||||
- [ ] 中介库实体模块功能正常
|
||||
- [ ] 招聘信息模块功能正常
|
||||
- [ ] 所有代码已提交(不少于 11 个 commits)
|
||||
- [ ] API 文档已更新
|
||||
- [ ] 设计文档已归档到 `doc/plans/`
|
||||
|
||||
---
|
||||
|
||||
## 测试指南
|
||||
|
||||
### 完整功能测试
|
||||
|
||||
1. **启动后端服务**
|
||||
|
||||
```bash
|
||||
mvn spring-boot:run
|
||||
```
|
||||
|
||||
2. **测试各模块导入功能**
|
||||
|
||||
为每个模块运行相应的测试(参考员工模块测试脚本)。
|
||||
|
||||
3. **验证数据库**
|
||||
|
||||
检查导入的数据是否正确,旧数据是否被删除。
|
||||
|
||||
### 性能测试
|
||||
|
||||
测试不同数据量的导入性能:
|
||||
- 小数据量:10 条
|
||||
- 中数据量:100 条
|
||||
- 大数据量:1000 条
|
||||
|
||||
---
|
||||
|
||||
**实施计划完成**
|
||||
564
doc/plans/2026-02-05-导入逻辑优化设计.md
Normal file
564
doc/plans/2026-02-05-导入逻辑优化设计.md
Normal file
@@ -0,0 +1,564 @@
|
||||
# 导入逻辑优化设计文档
|
||||
|
||||
## 文档信息
|
||||
|
||||
- **创建日期**:2026-02-05
|
||||
- **版本**:1.0
|
||||
- **作者**:Claude Code
|
||||
- **状态**:待实施
|
||||
|
||||
---
|
||||
|
||||
## 1. 背景和目标
|
||||
|
||||
### 1.1 背景
|
||||
|
||||
当前系统中的导入功能采用"存在则更新,不存在则插入"的逻辑:
|
||||
- 需要区分新增和更新两种操作
|
||||
- 使用复杂的条件判断和数据分类逻辑
|
||||
- 批量更新操作依赖特殊的 SQL 语法(CASE WHEN),容易出现语法错误
|
||||
- 代码逻辑复杂,维护成本高
|
||||
|
||||
### 1.2 目标
|
||||
|
||||
优化导入逻辑,简化代码实现:
|
||||
- 统一采用"先删除后插入"的策略
|
||||
- 移除复杂的更新操作和条件判断
|
||||
- 提高代码可维护性和可读性
|
||||
- 保证数据一致性和事务完整性
|
||||
|
||||
---
|
||||
|
||||
## 2. 需求分析
|
||||
|
||||
### 2.1 功能需求
|
||||
|
||||
#### 核心需求
|
||||
1. **导入策略变更**:将"存在则更新"改为"先删后插"
|
||||
2. **删除范围**:只删除导入数据中已存在的记录
|
||||
3. **唯一性判断**:使用业务唯一键判断记录是否存在
|
||||
4. **审计字段**:重新插入的数据,所有审计字段使用当前时间
|
||||
5. **冲突处理**:批量删除所有使用相同业务键的记录
|
||||
|
||||
#### 影响模块
|
||||
- 员工信息管理(`ccdi_employee`)
|
||||
- 中介库个人管理(`ccdi_biz_intermediary`)
|
||||
- 中介库实体管理(`ccdi_enterprise_base_info`)
|
||||
- 员工招聘信息管理(`ccdi_staff_recruitment`)
|
||||
|
||||
### 2.2 非功能需求
|
||||
|
||||
- **性能**:批量操作,2-3次数据库往返
|
||||
- **事务性**:所有操作在同一事务中,保证原子性
|
||||
- **兼容性**:前端调用方式保持不变
|
||||
|
||||
---
|
||||
|
||||
## 3. 设计方案
|
||||
|
||||
### 3.1 整体架构
|
||||
|
||||
新的导入逻辑采用三阶段流程:
|
||||
|
||||
#### 阶段 1:数据验证与收集
|
||||
- 遍历所有导入数据,验证必填字段和数据格式
|
||||
- 收集所有业务唯一键
|
||||
- 检查导入数据内部的重复性
|
||||
- 验证通过的数据放入待处理列表
|
||||
|
||||
#### 阶段 2:批量删除
|
||||
- 根据收集的业务唯一键列表,执行批量删除操作
|
||||
- SQL:`DELETE FROM table WHERE unique_key IN (...)`
|
||||
- 删除所有匹配的旧记录,包括重复的记录
|
||||
|
||||
#### 阶段 3:批量插入
|
||||
- 批量插入所有验证通过的数据
|
||||
- SQL:`INSERT INTO table (...) VALUES (...), (...), ...`
|
||||
- 所有审计字段使用当前时间
|
||||
|
||||
### 3.2 数据流图
|
||||
|
||||
```
|
||||
导入数据(Excel)
|
||||
↓
|
||||
【阶段 1】数据验证与收集
|
||||
├→ 验证必填字段和数据格式
|
||||
├→ 检查导入数据内部重复
|
||||
├→ 收集业务唯一键
|
||||
└→ 构建待插入列表
|
||||
↓
|
||||
【阶段 2】批量删除已存在记录
|
||||
└→ DELETE FROM table WHERE unique_key IN (...)
|
||||
↓
|
||||
【阶段 3】批量插入所有数据
|
||||
└→ INSERT INTO table (...) VALUES (...)
|
||||
↓
|
||||
返回导入结果(成功数量、失败详情)
|
||||
```
|
||||
|
||||
### 3.3 各模块业务键定义
|
||||
|
||||
| 模块 | 表名 | 业务键 | 说明 |
|
||||
|------|------|--------|------|
|
||||
| 员工信息 | `ccdi_employee` | `id_card` | 身份证号 |
|
||||
| 中介库个人 | `ccdi_biz_intermediary` | `person_id` | 个人证件号 |
|
||||
| 中介库实体 | `ccdi_enterprise_base_info` | `social_credit_code` | 统一社会信用代码 |
|
||||
| 招聘信息 | `ccdi_staff_recruitment` | `recruit_id` | 招聘项目编号 |
|
||||
|
||||
---
|
||||
|
||||
## 4. 详细设计
|
||||
|
||||
### 4.1 数据库层设计
|
||||
|
||||
#### 4.1.1 新增 Mapper 方法
|
||||
|
||||
每个模块需要添加对应的批量删除方法:
|
||||
|
||||
**员工信息模块**:
|
||||
```java
|
||||
// CcdiEmployeeMapper.java
|
||||
int deleteBatchByIdCard(@Param("list") List<String> idCards);
|
||||
```
|
||||
|
||||
**中介库个人模块**:
|
||||
```java
|
||||
// CcdiBizIntermediaryMapper.java
|
||||
int deleteBatchByPersonId(@Param("list") List<String> personIds);
|
||||
```
|
||||
|
||||
**中介库实体模块**:
|
||||
```java
|
||||
// CcdiEnterpriseBaseInfoMapper.java
|
||||
int deleteBatchBySocialCreditCode(@Param("list") List<String> socialCreditCodes);
|
||||
```
|
||||
|
||||
**招聘信息模块**:
|
||||
```java
|
||||
// CcdiStaffRecruitmentMapper.java
|
||||
int deleteBatchByRecruitId(@Param("list") List<String> recruitIds);
|
||||
```
|
||||
|
||||
#### 4.1.2 Mapper XML 实现
|
||||
|
||||
所有删除 SQL 使用统一的模式:
|
||||
|
||||
```xml
|
||||
<delete id="deleteBatchByXxx">
|
||||
DELETE FROM {table_name}
|
||||
WHERE {unique_key_column} IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
**示例(员工信息)**:
|
||||
```xml
|
||||
<!-- CcdiEmployeeMapper.xml -->
|
||||
<delete id="deleteBatchByIdCard">
|
||||
DELETE FROM ccdi_employee
|
||||
WHERE id_card IN
|
||||
<foreach collection="list" item="item" open="(" separator="," close=")">
|
||||
#{item}
|
||||
</foreach>
|
||||
</delete>
|
||||
```
|
||||
|
||||
### 4.2 服务层设计
|
||||
|
||||
#### 4.2.1 通用导入方法模板
|
||||
|
||||
所有模块的导入方法遵循统一的实现模式:
|
||||
|
||||
```java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importXxx(List<XxxExcel> excelList, Boolean isUpdateSupport) {
|
||||
// 参数校验
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<XxxEntity> validList = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> uniqueKeys = new HashSet<>();
|
||||
|
||||
for (XxxExcel excel : excelList) {
|
||||
try {
|
||||
// 转换并验证
|
||||
XxxAddDTO addDTO = new XxxAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
validateXxxDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
String uniqueKey = getUniqueKey(addDTO);
|
||||
if (!uniqueKeys.add(uniqueKey)) {
|
||||
throw new RuntimeException("导入文件中该" + getUniqueKeyName() + "重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
XxxEntity entity = new XxxEntity();
|
||||
BeanUtils.copyProperties(addDTO, entity);
|
||||
entity.setCreateBy("导入");
|
||||
entity.setUpdateBy("导入");
|
||||
|
||||
validList.add(entity);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
getDisplayName(excel), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validList.isEmpty()) {
|
||||
List<String> uniqueKeyList = new ArrayList<>(uniqueKeys);
|
||||
mapper.deleteBatchByUniqueKey(uniqueKeyList);
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validList.isEmpty()) {
|
||||
mapper.insertBatch(validList);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
throw buildFailureException(validList.size(), errorMessages);
|
||||
}
|
||||
|
||||
return buildSuccessMessage(validList.size());
|
||||
}
|
||||
```
|
||||
|
||||
#### 4.2.2 员工信息导入方法(示例)
|
||||
|
||||
```java
|
||||
// CcdiEmployeeServiceImpl.java
|
||||
@Override
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importEmployee(List<CcdiEmployeeExcel> excelList, Boolean isUpdateSupport) {
|
||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||
return "至少需要一条数据";
|
||||
}
|
||||
|
||||
// 第一阶段:数据验证和收集
|
||||
List<CcdiEmployee> validEmployees = new ArrayList<>();
|
||||
List<String> errorMessages = new ArrayList<>();
|
||||
Set<String> idCards = new HashSet<>();
|
||||
|
||||
for (CcdiEmployeeExcel excel : excelList) {
|
||||
try {
|
||||
// 转换并验证
|
||||
CcdiEmployeeAddDTO addDTO = new CcdiEmployeeAddDTO();
|
||||
BeanUtils.copyProperties(excel, addDTO);
|
||||
validateEmployeeDataBasic(addDTO);
|
||||
|
||||
// 检查导入数据内部是否重复
|
||||
if (!idCards.add(addDTO.getIdCard())) {
|
||||
throw new RuntimeException("导入文件中该身份证号重复");
|
||||
}
|
||||
|
||||
// 转换为实体,设置审计字段
|
||||
CcdiEmployee employee = new CcdiEmployee();
|
||||
BeanUtils.copyProperties(addDTO, employee);
|
||||
employee.setCreateBy("导入");
|
||||
employee.setUpdateBy("导入");
|
||||
|
||||
validEmployees.add(employee);
|
||||
|
||||
} catch (Exception e) {
|
||||
errorMessages.add(String.format("%s 导入失败:%s",
|
||||
excel.getName(), e.getMessage()));
|
||||
}
|
||||
}
|
||||
|
||||
// 第二阶段:批量删除已存在的记录
|
||||
if (!validEmployees.isEmpty()) {
|
||||
employeeMapper.deleteBatchByIdCard(new ArrayList<>(idCards));
|
||||
}
|
||||
|
||||
// 第三阶段:批量插入所有数据
|
||||
if (!validEmployees.isEmpty()) {
|
||||
employeeMapper.insertBatch(validEmployees);
|
||||
}
|
||||
|
||||
// 第四阶段:返回结果
|
||||
if (!errorMessages.isEmpty()) {
|
||||
StringBuilder failureMsg = new StringBuilder();
|
||||
failureMsg.append("很抱歉,导入完成!成功 ")
|
||||
.append(validEmployees.size())
|
||||
.append(" 条,失败 ")
|
||||
.append(errorMessages.size())
|
||||
.append(" 条,错误如下:");
|
||||
|
||||
for (int i = 0; i < errorMessages.size(); i++) {
|
||||
failureMsg.append("<br/>")
|
||||
.append(i + 1)
|
||||
.append("、")
|
||||
.append(errorMessages.get(i));
|
||||
}
|
||||
|
||||
throw new RuntimeException(failureMsg.toString());
|
||||
}
|
||||
|
||||
return "恭喜您,数据已全部导入成功!共 " + validEmployees.size() + " 条";
|
||||
}
|
||||
```
|
||||
|
||||
### 4.3 事务管理
|
||||
|
||||
#### 事务边界
|
||||
|
||||
整个导入操作使用 `@Transactional` 注解,确保原子性:
|
||||
|
||||
```java
|
||||
@Transactional(rollbackFor = Exception.class)
|
||||
public String importXxx(List<XxxExcel> excelList, Boolean isUpdateSupport) {
|
||||
// 所有数据库操作在一个事务中
|
||||
}
|
||||
```
|
||||
|
||||
#### 事务保证
|
||||
|
||||
| 场景 | 处理方式 | 结果 |
|
||||
|------|----------|------|
|
||||
| 批量删除失败 | 自动回滚 | 不影响现有数据 |
|
||||
| 批量插入失败 | 自动回滚 | 已删除的数据恢复 |
|
||||
| 数据验证失败 | 不执行数据库操作 | 直接返回错误信息 |
|
||||
|
||||
### 4.4 错误处理
|
||||
|
||||
#### 分层错误处理策略
|
||||
|
||||
**1. 数据验证层**
|
||||
- 捕获单条数据的验证错误(必填字段、格式校验)
|
||||
- 记录到失败列表,不影响其他数据
|
||||
- 验证通过的数据继续处理
|
||||
|
||||
**2. 数据库操作层**
|
||||
- 删除/插入失败时抛出异常,触发事务回滚
|
||||
- 捕获 `DuplicateKeyException`、`DataIntegrityViolationException` 等
|
||||
- 转换为用户友好的错误消息
|
||||
|
||||
**3. 统一返回**
|
||||
- 全部成功:返回成功消息 + 统计信息
|
||||
- 部分失败(验证阶段):返回详细错误列表
|
||||
- 数据库失败:事务回滚,返回系统错误提示
|
||||
|
||||
### 4.5 数据一致性保障
|
||||
|
||||
#### 场景 1:导入数据中业务键重复
|
||||
|
||||
**示例**:导入文件中有两条记录的身份证号都是 `110101199001011234`
|
||||
|
||||
**处理结果**:
|
||||
- 数据库中的旧记录被删除(如果存在)
|
||||
- 导入文件中的最后一条记录被插入
|
||||
- 第一条记录在验证阶段被检测为重复,记录到错误列表
|
||||
|
||||
#### 场景 2:数据库中存在重复记录
|
||||
|
||||
**示例**:数据库中有两条记录的身份证号都是 `110101199001011234`(历史数据问题)
|
||||
|
||||
**处理结果**:
|
||||
- 批量删除操作会删除所有身份证号匹配的记录
|
||||
- 插入新的记录
|
||||
- 自动修复了数据不一致问题
|
||||
|
||||
#### 场景 3:并发导入
|
||||
|
||||
**示例**:用户 A 和用户 B 同时导入包含相同身份证号的数据
|
||||
|
||||
**处理结果**:
|
||||
- 依赖数据库事务隔离级别和锁机制
|
||||
- 后提交的事务可能产生 `DuplicateKeyException`
|
||||
- 事务回滚,返回错误提示
|
||||
|
||||
---
|
||||
|
||||
## 5. 实施计划
|
||||
|
||||
### 5.1 修改文件清单(11 个文件)
|
||||
|
||||
#### 员工信息管理模块
|
||||
1. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java`
|
||||
2. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml`
|
||||
3. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java`
|
||||
|
||||
#### 中介库管理模块(个人和实体)
|
||||
4. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiBizIntermediaryMapper.java`
|
||||
5. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiBizIntermediaryMapper.xml`
|
||||
6. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEnterpriseBaseInfoMapper.java`
|
||||
7. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEnterpriseBaseInfoMapper.xml`
|
||||
8. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiIntermediaryServiceImpl.java`
|
||||
- 修改 `importIntermediaryPerson` 方法
|
||||
- 修改 `importIntermediaryEntity` 方法
|
||||
|
||||
#### 员工招聘信息管理模块
|
||||
9. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiStaffRecruitmentMapper.java`
|
||||
10. `ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiStaffRecruitmentMapper.xml`
|
||||
11. `ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffRecruitmentServiceImpl.java`
|
||||
|
||||
### 5.2 实施步骤
|
||||
|
||||
#### 步骤 1:员工信息模块(验证方案)
|
||||
1. 添加 `deleteBatchByIdCard` 方法到 Mapper 接口
|
||||
2. 在 Mapper XML 中实现删除 SQL
|
||||
3. 重构 `importEmployee` 方法
|
||||
4. 生成测试脚本并验证功能
|
||||
5. **验证通过后,继续其他模块**
|
||||
|
||||
#### 步骤 2:中介库模块
|
||||
1. 添加个人表的批量删除方法
|
||||
2. 添加实体表的批量删除方法
|
||||
3. 重构两个导入方法
|
||||
4. 测试验证
|
||||
|
||||
#### 步骤 3:招聘信息模块
|
||||
1. 添加批量删除方法
|
||||
2. 重构导入方法
|
||||
3. 测试验证
|
||||
|
||||
#### 步骤 4:清理和优化
|
||||
1. 移除不再使用的 `updateBatch` 方法(如果存在)
|
||||
2. 更新 API 文档
|
||||
3. 代码审查
|
||||
|
||||
### 5.3 测试计划
|
||||
|
||||
#### 单元测试
|
||||
- 测试批量删除 SQL 语法正确性
|
||||
- 测试批量插入 SQL 语法正确性
|
||||
- 测试事务回滚机制
|
||||
|
||||
#### 集成测试
|
||||
- 测试全新数据导入(数据库中不存在)
|
||||
- 测试更新数据导入(数据库中已存在)
|
||||
- 测试混合数据导入(部分存在,部分不存在)
|
||||
- 测试导入数据内部重复
|
||||
- 测试数据库中存在重复记录的清理
|
||||
|
||||
#### 性能测试
|
||||
- 测试 100 条数据的导入性能
|
||||
- 测试 1000 条数据的导入性能
|
||||
- 对比优化前后的性能差异
|
||||
|
||||
---
|
||||
|
||||
## 6. 风险评估
|
||||
|
||||
### 6.1 技术风险
|
||||
|
||||
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| 批量删除 SQL 性能问题 | 中 | 低 | 确保 business_key 有索引 |
|
||||
| 事务超时 | 中 | 低 | 监控事务执行时间,必要时调整超时配置 |
|
||||
| 并发冲突 | 低 | 中 | 依赖数据库事务隔离机制 |
|
||||
|
||||
### 6.2 业务风险
|
||||
|
||||
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| 历史数据丢失(审计字段重置) | 中 | 低 | 在文档中说明,告知用户 |
|
||||
| 用户误操作导入错误数据 | 高 | 中 | 前端增加确认提示 |
|
||||
|
||||
### 6.3 兼容性风险
|
||||
|
||||
| 风险 | 影响 | 概率 | 缓解措施 |
|
||||
|------|------|------|----------|
|
||||
| 前端依赖 `isUpdateSupport` 参数 | 低 | 低 | 参数保留但不使用 |
|
||||
| 其他系统调用导入接口 | 低 | 低 | 保持接口签名不变 |
|
||||
|
||||
---
|
||||
|
||||
## 7. 优势与劣势
|
||||
|
||||
### 7.1 优势
|
||||
|
||||
1. **代码简化**
|
||||
- 移除复杂的条件判断和数据分类逻辑
|
||||
- 统一的实现模式,易于维护
|
||||
- 代码行数减少约 30%
|
||||
|
||||
2. **性能优化**
|
||||
- 数据库操作从 3-4 次减少到 2-3 次
|
||||
- 不再需要复杂的批量更新 SQL
|
||||
- 批量删除和批量插入都使用索引,性能更好
|
||||
|
||||
3. **数据一致性**
|
||||
- 自动清理重复数据
|
||||
- 事务保证原子性
|
||||
- 减少数据不一致的可能性
|
||||
|
||||
4. **可维护性**
|
||||
- 代码逻辑清晰易懂
|
||||
- 各模块实现模式统一
|
||||
- 新增模块导入功能时可直接复用
|
||||
|
||||
### 7.2 劣势
|
||||
|
||||
1. **审计字段丢失**
|
||||
- `create_time` 和 `create_by` 会被重置为当前值
|
||||
- 无法保留原始创建时间
|
||||
- **缓解措施**:在文档中明确说明,如果需要保留历史记录,可以考虑使用软删除或历史表
|
||||
|
||||
2. **并发性能**
|
||||
- 高并发情况下可能产生事务冲突
|
||||
- **缓解措施**:导入功能通常是管理员操作,并发概率较低
|
||||
|
||||
3. **参数失效**
|
||||
- `isUpdateSupport` 参数失去原有意义
|
||||
- **缓解措施**:保留参数以保持接口兼容性,内部不再使用
|
||||
|
||||
---
|
||||
|
||||
## 8. 后续优化建议
|
||||
|
||||
### 8.1 短期优化
|
||||
|
||||
1. **添加导入进度提示**
|
||||
- 对于大量数据导入,前端显示导入进度
|
||||
- 避免用户长时间等待
|
||||
|
||||
2. **优化错误消息**
|
||||
- 提供更详细的错误信息
|
||||
- 帮助用户快速定位问题
|
||||
|
||||
### 8.2 长期优化
|
||||
|
||||
1. **异步导入**
|
||||
- 对于超大文件(>10000条),使用异步处理
|
||||
- 导入完成后通知用户
|
||||
|
||||
2. **导入历史记录**
|
||||
- 记录每次导入的操作日志
|
||||
- 支持导入历史查询和回滚
|
||||
|
||||
3. **数据校验增强**
|
||||
- 添加更多业务规则校验
|
||||
- 支持自定义校验规则
|
||||
|
||||
---
|
||||
|
||||
## 9. 附录
|
||||
|
||||
### 9.1 术语表
|
||||
|
||||
| 术语 | 说明 |
|
||||
|------|------|
|
||||
| 业务键 | 业务层面判断记录唯一性的字段(如身份证号) |
|
||||
| 审计字段 | 记录数据创建和修改信息的字段(create_time, create_by, update_time, update_by) |
|
||||
| 批量操作 | 一次数据库操作处理多条记录 |
|
||||
| 事务 | 保证一组数据库操作原子性的机制 |
|
||||
|
||||
### 9.2 参考资料
|
||||
|
||||
- [MyBatis 官方文档 - 动态 SQL](https://mybatis.org/mybatis-3/zh/dynamic-sql.html)
|
||||
- [MySQL 批量插入最佳实践](https://dev.mysql.com/doc/refman/8.0/en/insert-optimization.html)
|
||||
- [Spring 事务管理](https://docs.spring.io/spring-framework/reference/data-access/transaction/declarative/annotations.html)
|
||||
|
||||
---
|
||||
|
||||
**文档结束**
|
||||
Reference in New Issue
Block a user