除员工外 取消导入更新
This commit is contained in:
478
doc/plans/2026-02-09-remove-import-update-support-design.md
Normal file
478
doc/plans/2026-02-09-remove-import-update-support-design.md
Normal file
@@ -0,0 +1,478 @@
|
|||||||
|
# 移除招聘信和采购交易导入更新支持功能设计文档
|
||||||
|
|
||||||
|
**日期:** 2026-02-09
|
||||||
|
**模块:** 招聘信息管理、采购交易管理
|
||||||
|
**类型:** 功能简化
|
||||||
|
|
||||||
|
## 1. 需求概述
|
||||||
|
|
||||||
|
### 1.1 背景
|
||||||
|
当前招聘信息和采购交易信息模块的导入功能支持"导入更新"模式,允许用户通过导入文件来更新已存在的数据。但实际业务场景中,这两个模块不应该支持导入更新操作。
|
||||||
|
|
||||||
|
### 1.2 目标
|
||||||
|
- 完全移除招聘信和采购交易的导入更新功能
|
||||||
|
- 简化代码逻辑,降低维护成本
|
||||||
|
- 导入时遇到已存在的数据直接报错,避免意外覆盖
|
||||||
|
|
||||||
|
### 1.3 处理策略
|
||||||
|
- **遇到已存在数据:** 跳过该条数据,记录到失败列表
|
||||||
|
- **错误提示:** 显示具体重复的数据ID(招聘项目编号/采购事项ID)
|
||||||
|
- **其他数据:** 继续正常导入
|
||||||
|
|
||||||
|
## 2. 技术方案
|
||||||
|
|
||||||
|
### 2.1 后端修改
|
||||||
|
|
||||||
|
#### 2.1.1 招聘信模块
|
||||||
|
|
||||||
|
**Controller层:** `CcdiStaffRecruitmentController.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
@PostMapping("/import")
|
||||||
|
public AjaxResult importRecruitment(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam Boolean isUpdateSupport) throws Exception {
|
||||||
|
// ...
|
||||||
|
importService.importRecruitmentAsync(excelList, isUpdateSupport, taskId, username);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
@PostMapping("/import")
|
||||||
|
public AjaxResult importRecruitment(@RequestParam("file") MultipartFile file) throws Exception {
|
||||||
|
// ...
|
||||||
|
importService.importRecruitmentAsync(excelList, taskId, username);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Service接口:** `ICcdiStaffRecruitmentImportService.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||||
|
Boolean isUpdateSupport,
|
||||||
|
String taskId,
|
||||||
|
String userName);
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Service实现:** `CcdiStaffRecruitmentImportServiceImpl.java`
|
||||||
|
|
||||||
|
主要修改点:
|
||||||
|
1. 移除方法参数 `Boolean isUpdateSupport`
|
||||||
|
2. 移除 `List<CcdiStaffRecruitment> updateRecords` 变量
|
||||||
|
3. 简化数据分类逻辑(第73-92行)
|
||||||
|
4. 移除批量更新逻辑(第107-110行)
|
||||||
|
5. 修改错误提示信息
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改后的数据分类逻辑
|
||||||
|
for (CcdiStaffRecruitmentExcel excel : excelList) {
|
||||||
|
try {
|
||||||
|
// 验证数据(不再需要isUpdateSupport参数)
|
||||||
|
validateRecruitmentData(excel, existingRecruitIds);
|
||||||
|
|
||||||
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
|
BeanUtils.copyProperties(excel, recruitment);
|
||||||
|
|
||||||
|
if (existingRecruitIds.contains(excel.getRecruitId())) {
|
||||||
|
// 直接抛出异常,记录为失败
|
||||||
|
throw new RuntimeException(
|
||||||
|
String.format("招聘项目编号[%s]已存在,请勿重复导入", excel.getRecruitId())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
recruitment.setCreatedBy(userName);
|
||||||
|
recruitment.setUpdatedBy(userName);
|
||||||
|
newRecords.add(recruitment);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
RecruitmentImportFailureVO failure = new RecruitmentImportFailureVO();
|
||||||
|
BeanUtils.copyProperties(excel, failure);
|
||||||
|
failure.setErrorMessage(e.getMessage());
|
||||||
|
failures.add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除批量更新代码
|
||||||
|
// 删除以下代码:
|
||||||
|
// if (!updateRecords.isEmpty() && isUpdateSupport) {
|
||||||
|
// recruitmentMapper.updateBatch(updateRecords);
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证方法简化:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
private void validateRecruitmentData(CcdiStaffRecruitmentExcel excel,
|
||||||
|
Boolean isUpdateSupport,
|
||||||
|
Set<String> existingRecruitIds)
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
private void validateRecruitmentData(CcdiStaffRecruitmentExcel excel,
|
||||||
|
Set<String> existingRecruitIds)
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.1.2 采购交易模块
|
||||||
|
|
||||||
|
**Controller层:** `CcdiPurchaseTransactionController.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
@PostMapping("/import")
|
||||||
|
public AjaxResult importTransaction(@RequestParam("file") MultipartFile file,
|
||||||
|
@RequestParam Boolean isUpdateSupport) throws Exception {
|
||||||
|
// ...
|
||||||
|
importService.importTransactionAsync(excelList, isUpdateSupport, taskId, username);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
@PostMapping("/import")
|
||||||
|
public AjaxResult importTransaction(@RequestParam("file") MultipartFile file) throws Exception {
|
||||||
|
// ...
|
||||||
|
importService.importTransactionAsync(excelList, taskId, username);
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**Service接口:** `ICcdiPurchaseTransactionImportService.java`
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList,
|
||||||
|
Boolean isUpdateSupport,
|
||||||
|
String taskId,
|
||||||
|
String userName);
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
void importTransactionAsync(List<CcdiPurchaseTransactionExcel> excelList,
|
||||||
|
String taskId,
|
||||||
|
String userName);
|
||||||
|
```
|
||||||
|
|
||||||
|
**Service实现:** `CcdiPurchaseTransactionImportServiceImpl.java`
|
||||||
|
|
||||||
|
主要修改点:
|
||||||
|
1. 移除方法参数 `Boolean isUpdateSupport`
|
||||||
|
2. 移除 `List<CcdiPurchaseTransaction> updateRecords` 变量
|
||||||
|
3. 简化数据分类逻辑(第54-88行)
|
||||||
|
4. 移除批量更新逻辑(第95-98行)
|
||||||
|
5. 修改错误提示信息
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改后的数据分类逻辑
|
||||||
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
|
CcdiPurchaseTransactionExcel excel = excelList.get(i);
|
||||||
|
|
||||||
|
try {
|
||||||
|
// 转换为AddDTO进行验证
|
||||||
|
CcdiPurchaseTransactionAddDTO addDTO = new CcdiPurchaseTransactionAddDTO();
|
||||||
|
BeanUtils.copyProperties(excel, addDTO);
|
||||||
|
|
||||||
|
// 验证数据(不再需要isUpdateSupport参数)
|
||||||
|
validateTransactionData(addDTO, existingIds);
|
||||||
|
|
||||||
|
CcdiPurchaseTransaction transaction = new CcdiPurchaseTransaction();
|
||||||
|
BeanUtils.copyProperties(excel, transaction);
|
||||||
|
|
||||||
|
if (existingIds.contains(excel.getPurchaseId())) {
|
||||||
|
// 直接抛出异常,记录为失败
|
||||||
|
throw new RuntimeException(
|
||||||
|
String.format("采购事项ID[%s]已存在,请勿重复导入", excel.getPurchaseId())
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
transaction.setCreatedBy(userName);
|
||||||
|
transaction.setUpdatedBy(userName);
|
||||||
|
newRecords.add(transaction);
|
||||||
|
}
|
||||||
|
} catch (Exception e) {
|
||||||
|
PurchaseTransactionImportFailureVO failure = new PurchaseTransactionImportFailureVO();
|
||||||
|
BeanUtils.copyProperties(excel, failure);
|
||||||
|
failure.setErrorMessage(e.getMessage());
|
||||||
|
failures.add(failure);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 移除批量更新代码
|
||||||
|
// 删除以下代码:
|
||||||
|
// if (!updateRecords.isEmpty() && isUpdateSupport) {
|
||||||
|
// transactionMapper.insertOrUpdateBatch(updateRecords);
|
||||||
|
// }
|
||||||
|
```
|
||||||
|
|
||||||
|
**验证方法简化:**
|
||||||
|
|
||||||
|
```java
|
||||||
|
// 修改前
|
||||||
|
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO,
|
||||||
|
Boolean isUpdateSupport,
|
||||||
|
Set<String> existingIds)
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
private void validateTransactionData(CcdiPurchaseTransactionAddDTO addDTO,
|
||||||
|
Set<String> existingIds)
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2.2 前端修改
|
||||||
|
|
||||||
|
#### 2.2.1 招聘信模块
|
||||||
|
|
||||||
|
**文件:** `ruoyi-ui/src/views/ccdiStaffRecruitment/index.vue`
|
||||||
|
|
||||||
|
**修改1:** 移除 `upload` 对象中的 `updateSupport` 字段
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 修改前 (约第461行)
|
||||||
|
upload: {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 弹出层标题
|
||||||
|
title: "",
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
// 是否更新已经存在的招聘信息数据
|
||||||
|
updateSupport: 0,
|
||||||
|
// 设置上传的请求头部
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
// 上传的地址
|
||||||
|
url: process.env.VUE_APP_BASE_API + "/ccdi/staffRecruitment/import"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
upload: {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 弹出层标题
|
||||||
|
title: "",
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
// 设置上传的请求头部
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
// 上传的地址
|
||||||
|
url: process.env.VUE_APP_BASE_API + "/ccdi/staffRecruitment/import"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改2:** 移除导入对话框中的"是否更新"复选框 (约第327行)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 删除此行 -->
|
||||||
|
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的招聘信息数据
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改3:** 移除URL中的updateSupport参数 (约第317行)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 修改前 -->
|
||||||
|
<el-upload
|
||||||
|
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||||
|
...
|
||||||
|
>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 修改后 -->
|
||||||
|
<el-upload
|
||||||
|
:action="upload.url"
|
||||||
|
...
|
||||||
|
>
|
||||||
|
</el-upload>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### 2.2.2 采购交易模块
|
||||||
|
|
||||||
|
**文件:** `ruoyi-ui/src/views/ccdiPurchaseTransaction/index.vue`
|
||||||
|
|
||||||
|
**修改1:** 移除 `upload` 对象中的 `updateSupport` 字段
|
||||||
|
|
||||||
|
```javascript
|
||||||
|
// 修改前 (约第719行)
|
||||||
|
upload: {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 弹出层标题
|
||||||
|
title: "",
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
// 是否更新已经存在的采购交易数据
|
||||||
|
updateSupport: 0,
|
||||||
|
// 设置上传的请求头部
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
// 上传的地址
|
||||||
|
url: process.env.VUE_APP_BASE_API + "/ccdi/purchaseTransaction/import"
|
||||||
|
}
|
||||||
|
|
||||||
|
// 修改后
|
||||||
|
upload: {
|
||||||
|
// 是否显示弹出层
|
||||||
|
open: false,
|
||||||
|
// 弹出层标题
|
||||||
|
title: "",
|
||||||
|
// 是否禁用上传
|
||||||
|
isUploading: false,
|
||||||
|
// 设置上传的请求头部
|
||||||
|
headers: { Authorization: "Bearer " + getToken() },
|
||||||
|
// 上传的地址
|
||||||
|
url: process.env.VUE_APP_BASE_API + "/ccdi/purchaseTransaction/import"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改2:** 移除导入对话框中的"是否更新"复选框 (约第513行)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 删除此行 -->
|
||||||
|
<el-checkbox v-model="upload.updateSupport" />是否更新已经存在的采购交易数据
|
||||||
|
```
|
||||||
|
|
||||||
|
**修改3:** 移除URL中的updateSupport参数 (约第503行)
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- 修改前 -->
|
||||||
|
<el-upload
|
||||||
|
:action="upload.url + '?updateSupport=' + upload.updateSupport"
|
||||||
|
...
|
||||||
|
>
|
||||||
|
</el-upload>
|
||||||
|
|
||||||
|
<!-- 修改后 -->
|
||||||
|
<el-upload
|
||||||
|
:action="upload.url"
|
||||||
|
...
|
||||||
|
>
|
||||||
|
</el-upload>
|
||||||
|
```
|
||||||
|
|
||||||
|
## 3. 数据流变化
|
||||||
|
|
||||||
|
### 3.1 修改前
|
||||||
|
|
||||||
|
```
|
||||||
|
用户上传文件
|
||||||
|
→ 前端传递 isUpdateSupport 参数
|
||||||
|
→ 后端检查数据是否存在
|
||||||
|
→ 存在且 isUpdateSupport=true: 更新数据
|
||||||
|
→ 存在且 isUpdateSupport=false: 报错
|
||||||
|
→ 不存在: 新增数据
|
||||||
|
```
|
||||||
|
|
||||||
|
### 3.2 修改后
|
||||||
|
|
||||||
|
```
|
||||||
|
用户上传文件
|
||||||
|
→ 后端检查数据是否存在
|
||||||
|
→ 存在: 报错(显示重复ID),记录为失败
|
||||||
|
→ 不存在: 新增数据
|
||||||
|
```
|
||||||
|
|
||||||
|
## 4. 代码变更统计
|
||||||
|
|
||||||
|
### 4.1 后端变更
|
||||||
|
|
||||||
|
| 模块 | 文件 | 变更类型 | 变更行数(预估) |
|
||||||
|
|------|------|----------|---------------|
|
||||||
|
| 招聘信 | CcdiStaffRecruitmentController.java | 修改 | ~5行 |
|
||||||
|
| 招聘信 | ICcdiStaffRecruitmentImportService.java | 修改 | ~3行 |
|
||||||
|
| 招聘信 | CcdiStaffRecruitmentImportServiceImpl.java | 修改/删除 | ~30行 |
|
||||||
|
| 采购交易 | CcdiPurchaseTransactionController.java | 修改 | ~5行 |
|
||||||
|
| 采购交易 | ICcdiPurchaseTransactionImportService.java | 修改 | ~3行 |
|
||||||
|
| 采购交易 | CcdiPurchaseTransactionImportServiceImpl.java | 修改/删除 | ~30行 |
|
||||||
|
|
||||||
|
**总计:** 约76行
|
||||||
|
|
||||||
|
### 4.2 前端变更
|
||||||
|
|
||||||
|
| 模块 | 文件 | 变更类型 | 变更行数(预估) |
|
||||||
|
|------|------|----------|---------------|
|
||||||
|
| 招聘信 | index.vue | 修改/删除 | ~10行 |
|
||||||
|
| 采购交易 | index.vue | 修改/删除 | ~10行 |
|
||||||
|
|
||||||
|
**总计:** 约20行
|
||||||
|
|
||||||
|
## 5. 测试计划
|
||||||
|
|
||||||
|
### 5.1 功能测试
|
||||||
|
|
||||||
|
**测试场景1: 导入全新数据**
|
||||||
|
- 输入: 导入文件中的所有数据都不存在于数据库
|
||||||
|
- 预期: 全部导入成功,成功数=总数
|
||||||
|
|
||||||
|
**测试场景2: 导入部分重复数据**
|
||||||
|
- 输入: 导入文件中包含部分已存在的招聘项目编号/采购事项ID
|
||||||
|
- 预期:
|
||||||
|
- 已存在的数据记录为失败
|
||||||
|
- 失败信息显示具体的重复ID
|
||||||
|
- 其他数据正常导入
|
||||||
|
|
||||||
|
**测试场景3: 导入全部重复数据**
|
||||||
|
- 输入: 导入文件中的所有数据都已存在
|
||||||
|
- 预期: 全部导入失败,失败数=总数
|
||||||
|
|
||||||
|
**测试场景4: 前端UI验证**
|
||||||
|
- 检查导入对话框中不再显示"是否更新"复选框
|
||||||
|
- 检查上传请求URL中不包含updateSupport参数
|
||||||
|
|
||||||
|
### 5.2 接口测试
|
||||||
|
|
||||||
|
使用测试脚本验证后端接口:
|
||||||
|
1. 不传递isUpdateSupport参数,接口应正常工作
|
||||||
|
2. 验证重复数据的错误提示信息格式
|
||||||
|
|
||||||
|
## 6. 风险评估
|
||||||
|
|
||||||
|
### 6.1 兼容性风险
|
||||||
|
- **风险:** 旧版前端可能会传递isUpdateSupport参数
|
||||||
|
- **影响:** 后端接口会报参数错误
|
||||||
|
- **缓解:** 确保前后端同时部署,或后端暂时兼容接收该参数但不处理
|
||||||
|
|
||||||
|
### 6.2 用户体验风险
|
||||||
|
- **风险:** 用户习惯使用"导入更新"功能
|
||||||
|
- **影响:** 需要先删除旧数据再导入新数据
|
||||||
|
- **缓解:** 在失败提示中明确告知数据ID,方便用户删除
|
||||||
|
|
||||||
|
### 6.3 数据一致性风险
|
||||||
|
- **风险:** 低风险,因为只是移除更新功能
|
||||||
|
- **影响:** 无
|
||||||
|
- **缓解:** 无需特殊处理
|
||||||
|
|
||||||
|
## 7. 部署建议
|
||||||
|
|
||||||
|
### 7.1 部署顺序
|
||||||
|
1. 先部署后端代码
|
||||||
|
2. 再部署前端代码
|
||||||
|
3. 前后端必须同时上线,避免调用失败
|
||||||
|
|
||||||
|
### 7.2 数据库变更
|
||||||
|
- 无需数据库变更
|
||||||
|
|
||||||
|
### 7.3 配置变更
|
||||||
|
- 无需配置变更
|
||||||
|
|
||||||
|
## 8. 回滚方案
|
||||||
|
|
||||||
|
如果需要回滚,可以:
|
||||||
|
1. 恢复后端代码,恢复isUpdateSupport参数处理逻辑
|
||||||
|
2. 恢复前端代码,恢复"是否更新"复选框
|
||||||
|
|
||||||
|
## 9. 附录
|
||||||
|
|
||||||
|
### 9.1 相关文档
|
||||||
|
- 招聘信息导入功能设计: `doc/plans/2026-02-06-recruitment-async-import-design.md`
|
||||||
|
- 采购交易导入功能设计: `doc/plans/2026-02-08-purchase-transaction-import-design.md`
|
||||||
|
|
||||||
|
### 9.2 相关API文档
|
||||||
|
- 招聘信息API: `doc/api/ccdi_staff_recruitment_api.md`
|
||||||
|
- 采购交易API: `doc/api/ccdi_purchase_transaction_api.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
**审批记录**
|
||||||
|
|
||||||
|
| 角色 | 姓名 | 日期 | 状态 |
|
||||||
|
|------|------|------|------|
|
||||||
|
| 开发 | - | 2026-02-09 | 待审批 |
|
||||||
|
| 审批 | - | - | 待审批 |
|
||||||
BIN
doc/test-data/employee/employee_test_data_1000 - 副本 (3).xlsx
Normal file
BIN
doc/test-data/employee/employee_test_data_1000 - 副本 (3).xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/employee/employee_test_data_phone.xlsx
Normal file
BIN
doc/test-data/employee/employee_test_data_phone.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/recruitment/recruitment_test_data_100.xlsx
Normal file
BIN
doc/test-data/recruitment/recruitment_test_data_100.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/recruitment/recruitment_test_data_1000_part1.xlsx
Normal file
BIN
doc/test-data/recruitment/recruitment_test_data_1000_part1.xlsx
Normal file
Binary file not shown.
BIN
doc/test-data/recruitment/recruitment_test_data_1000_part2.xlsx
Normal file
BIN
doc/test-data/recruitment/recruitment_test_data_1000_part2.xlsx
Normal file
Binary file not shown.
@@ -1,23 +1,13 @@
|
|||||||
package com.ruoyi.ccdi.controller;
|
package com.ruoyi.ccdi.controller;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryEntityAddDTO;
|
import com.ruoyi.ccdi.domain.dto.*;
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryEntityEditDTO;
|
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryPersonAddDTO;
|
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryPersonEditDTO;
|
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiIntermediaryQueryDTO;
|
|
||||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel;
|
||||||
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
import com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel;
|
||||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryEntityDetailVO;
|
import com.ruoyi.ccdi.domain.vo.*;
|
||||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryPersonDetailVO;
|
|
||||||
import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO;
|
|
||||||
import com.ruoyi.ccdi.domain.vo.ImportResultVO;
|
|
||||||
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
|
|
||||||
import com.ruoyi.ccdi.domain.vo.IntermediaryPersonImportFailureVO;
|
|
||||||
import com.ruoyi.ccdi.domain.vo.IntermediaryEntityImportFailureVO;
|
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryService;
|
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryPersonImportService;
|
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryEntityImportService;
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryEntityImportService;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryPersonImportService;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryService;
|
||||||
import com.ruoyi.ccdi.utils.EasyExcelUtil;
|
import com.ruoyi.ccdi.utils.EasyExcelUtil;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
import com.ruoyi.common.core.controller.BaseController;
|
import com.ruoyi.common.core.controller.BaseController;
|
||||||
@@ -26,7 +16,6 @@ import com.ruoyi.common.core.page.PageDomain;
|
|||||||
import com.ruoyi.common.core.page.TableDataInfo;
|
import com.ruoyi.common.core.page.TableDataInfo;
|
||||||
import com.ruoyi.common.core.page.TableSupport;
|
import com.ruoyi.common.core.page.TableSupport;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -193,7 +182,7 @@ public class CcdiIntermediaryController extends BaseController {
|
|||||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
||||||
@Log(title = "个人中介", businessType = BusinessType.IMPORT)
|
@Log(title = "个人中介", businessType = BusinessType.IMPORT)
|
||||||
@PostMapping("/importPersonData")
|
@PostMapping("/importPersonData")
|
||||||
public AjaxResult importPersonData(MultipartFile file, boolean updateSupport) throws Exception {
|
public AjaxResult importPersonData(MultipartFile file) throws Exception {
|
||||||
List<CcdiIntermediaryPersonExcel> list = EasyExcelUtil.importExcel(
|
List<CcdiIntermediaryPersonExcel> list = EasyExcelUtil.importExcel(
|
||||||
file.getInputStream(), CcdiIntermediaryPersonExcel.class);
|
file.getInputStream(), CcdiIntermediaryPersonExcel.class);
|
||||||
|
|
||||||
@@ -202,7 +191,7 @@ public class CcdiIntermediaryController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 提交异步任务
|
// 提交异步任务
|
||||||
String taskId = intermediaryService.importIntermediaryPerson(list, updateSupport);
|
String taskId = intermediaryService.importIntermediaryPerson(list);
|
||||||
|
|
||||||
// 立即返回,不等待后台任务完成
|
// 立即返回,不等待后台任务完成
|
||||||
ImportResultVO result = new ImportResultVO();
|
ImportResultVO result = new ImportResultVO();
|
||||||
@@ -220,7 +209,7 @@ public class CcdiIntermediaryController extends BaseController {
|
|||||||
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
@PreAuthorize("@ss.hasPermi('ccdi:intermediary:import')")
|
||||||
@Log(title = "实体中介", businessType = BusinessType.IMPORT)
|
@Log(title = "实体中介", businessType = BusinessType.IMPORT)
|
||||||
@PostMapping("/importEntityData")
|
@PostMapping("/importEntityData")
|
||||||
public AjaxResult importEntityData(MultipartFile file, boolean updateSupport) throws Exception {
|
public AjaxResult importEntityData(MultipartFile file) throws Exception {
|
||||||
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel(
|
List<CcdiIntermediaryEntityExcel> list = EasyExcelUtil.importExcel(
|
||||||
file.getInputStream(), CcdiIntermediaryEntityExcel.class);
|
file.getInputStream(), CcdiIntermediaryEntityExcel.class);
|
||||||
|
|
||||||
@@ -229,7 +218,7 @@ public class CcdiIntermediaryController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 提交异步任务
|
// 提交异步任务
|
||||||
String taskId = intermediaryService.importIntermediaryEntity(list, updateSupport);
|
String taskId = intermediaryService.importIntermediaryEntity(list);
|
||||||
|
|
||||||
// 立即返回,不等待后台任务完成
|
// 立即返回,不等待后台任务完成
|
||||||
ImportResultVO result = new ImportResultVO();
|
ImportResultVO result = new ImportResultVO();
|
||||||
|
|||||||
@@ -6,6 +6,10 @@ import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentEditDTO;
|
|||||||
import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentQueryDTO;
|
import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentQueryDTO;
|
||||||
import com.ruoyi.ccdi.domain.excel.CcdiStaffRecruitmentExcel;
|
import com.ruoyi.ccdi.domain.excel.CcdiStaffRecruitmentExcel;
|
||||||
import com.ruoyi.ccdi.domain.vo.CcdiStaffRecruitmentVO;
|
import com.ruoyi.ccdi.domain.vo.CcdiStaffRecruitmentVO;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.ImportResultVO;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
|
||||||
|
import com.ruoyi.ccdi.domain.vo.RecruitmentImportFailureVO;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentImportService;
|
||||||
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentService;
|
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentService;
|
||||||
import com.ruoyi.ccdi.utils.EasyExcelUtil;
|
import com.ruoyi.ccdi.utils.EasyExcelUtil;
|
||||||
import com.ruoyi.common.annotation.Log;
|
import com.ruoyi.common.annotation.Log;
|
||||||
@@ -16,6 +20,7 @@ import com.ruoyi.common.core.page.TableDataInfo;
|
|||||||
import com.ruoyi.common.core.page.TableSupport;
|
import com.ruoyi.common.core.page.TableSupport;
|
||||||
import com.ruoyi.common.enums.BusinessType;
|
import com.ruoyi.common.enums.BusinessType;
|
||||||
import io.swagger.v3.oas.annotations.Operation;
|
import io.swagger.v3.oas.annotations.Operation;
|
||||||
|
import io.swagger.v3.oas.annotations.Parameter;
|
||||||
import io.swagger.v3.oas.annotations.tags.Tag;
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import jakarta.servlet.http.HttpServletResponse;
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
@@ -40,6 +45,9 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
|||||||
@Resource
|
@Resource
|
||||||
private ICcdiStaffRecruitmentService recruitmentService;
|
private ICcdiStaffRecruitmentService recruitmentService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiStaffRecruitmentImportService recruitmentImportService;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询招聘信息列表
|
* 查询招聘信息列表
|
||||||
*/
|
*/
|
||||||
@@ -120,15 +128,66 @@ public class CcdiStaffRecruitmentController extends BaseController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入招聘信息
|
* 异步导入招聘信息
|
||||||
*/
|
*/
|
||||||
@Operation(summary = "导入招聘信息")
|
@Operation(summary = "异步导入招聘信息")
|
||||||
|
@Parameter(name = "file", description = "导入文件", required = true)
|
||||||
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:import')")
|
@PreAuthorize("@ss.hasPermi('ccdi:staffRecruitment:import')")
|
||||||
@Log(title = "员工招聘信息", businessType = BusinessType.IMPORT)
|
@Log(title = "员工招聘信息", businessType = BusinessType.IMPORT)
|
||||||
@PostMapping("/importData")
|
@PostMapping("/importData")
|
||||||
public AjaxResult importData(MultipartFile file) throws Exception {
|
public AjaxResult importData(@Parameter(description = "导入文件") MultipartFile file) throws Exception {
|
||||||
List<CcdiStaffRecruitmentExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentExcel.class);
|
List<CcdiStaffRecruitmentExcel> list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiStaffRecruitmentExcel.class);
|
||||||
String message = recruitmentService.importRecruitment(list);
|
|
||||||
return success(message);
|
if (list == null || list.isEmpty()) {
|
||||||
|
return error("至少需要一条数据");
|
||||||
|
}
|
||||||
|
|
||||||
|
// 提交异步任务
|
||||||
|
String taskId = recruitmentService.importRecruitment(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:staffRecruitment:import')")
|
||||||
|
@GetMapping("/importStatus/{taskId}")
|
||||||
|
public AjaxResult getImportStatus(@PathVariable String taskId) {
|
||||||
|
ImportStatusVO statusVO = recruitmentImportService.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:staffRecruitment:import')")
|
||||||
|
@GetMapping("/importFailures/{taskId}")
|
||||||
|
public TableDataInfo getImportFailures(
|
||||||
|
@PathVariable String taskId,
|
||||||
|
@RequestParam(defaultValue = "1") Integer pageNum,
|
||||||
|
@RequestParam(defaultValue = "10") Integer pageSize) {
|
||||||
|
|
||||||
|
List<RecruitmentImportFailureVO> failures = recruitmentImportService.getImportFailures(taskId);
|
||||||
|
|
||||||
|
// 手动分页
|
||||||
|
int fromIndex = (pageNum - 1) * pageSize;
|
||||||
|
int toIndex = Math.min(fromIndex + pageSize, failures.size());
|
||||||
|
|
||||||
|
List<RecruitmentImportFailureVO> pageData = failures.subList(fromIndex, toIndex);
|
||||||
|
|
||||||
|
return getDataTable(pageData, failures.size());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ public interface ICcdiIntermediaryEntityImportService {
|
|||||||
* 异步导入实体中介数据
|
* 异步导入实体中介数据
|
||||||
*
|
*
|
||||||
* @param excelList Excel数据列表
|
* @param excelList Excel数据列表
|
||||||
* @param isUpdateSupport 是否更新已存在的数据
|
|
||||||
* @param taskId 任务ID
|
* @param taskId 任务ID
|
||||||
* @param userName 当前用户名(用于审计字段)
|
* @param userName 当前用户名(用于审计字段)
|
||||||
*/
|
*/
|
||||||
void importEntityAsync(List<CcdiIntermediaryEntityExcel> excelList,
|
void importEntityAsync(List<CcdiIntermediaryEntityExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName);
|
String userName);
|
||||||
|
|
||||||
|
|||||||
@@ -18,12 +18,10 @@ public interface ICcdiIntermediaryPersonImportService {
|
|||||||
* 异步导入个人中介数据
|
* 异步导入个人中介数据
|
||||||
*
|
*
|
||||||
* @param excelList Excel数据列表
|
* @param excelList Excel数据列表
|
||||||
* @param isUpdateSupport 是否更新已存在的数据
|
|
||||||
* @param taskId 任务ID
|
* @param taskId 任务ID
|
||||||
* @param userName 当前用户名(用于审计字段)
|
* @param userName 当前用户名(用于审计字段)
|
||||||
*/
|
*/
|
||||||
void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
|
void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName);
|
String userName);
|
||||||
|
|
||||||
|
|||||||
@@ -101,17 +101,15 @@ public interface ICcdiIntermediaryService {
|
|||||||
* 导入个人中介数据
|
* 导入个人中介数据
|
||||||
*
|
*
|
||||||
* @param list Excel实体列表
|
* @param list Excel实体列表
|
||||||
* @param updateSupport 是否更新支持
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
String importIntermediaryPerson(java.util.List<com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel> list, boolean updateSupport);
|
String importIntermediaryPerson(java.util.List<com.ruoyi.ccdi.domain.excel.CcdiIntermediaryPersonExcel> list);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入实体中介数据
|
* 导入实体中介数据
|
||||||
*
|
*
|
||||||
* @param list Excel实体列表
|
* @param list Excel实体列表
|
||||||
* @param updateSupport 是否更新支持
|
|
||||||
* @return 结果
|
* @return 结果
|
||||||
*/
|
*/
|
||||||
String importIntermediaryEntity(java.util.List<com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel> list, boolean updateSupport);
|
String importIntermediaryEntity(java.util.List<com.ruoyi.ccdi.domain.excel.CcdiIntermediaryEntityExcel> list);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -18,11 +18,10 @@ public interface ICcdiStaffRecruitmentImportService {
|
|||||||
* 异步导入招聘信息数据
|
* 异步导入招聘信息数据
|
||||||
*
|
*
|
||||||
* @param excelList Excel数据列表
|
* @param excelList Excel数据列表
|
||||||
* @param isUpdateSupport 是否更新已存在的数据
|
|
||||||
* @param taskId 任务ID
|
* @param taskId 任务ID
|
||||||
|
* @param userName 用户名
|
||||||
*/
|
*/
|
||||||
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName);
|
String userName);
|
||||||
|
|
||||||
|
|||||||
@@ -42,20 +42,24 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
@Async
|
@Async
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void importEntityAsync(List<CcdiIntermediaryEntityExcel> excelList,
|
public void importEntityAsync(List<CcdiIntermediaryEntityExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName) {
|
String userName) {
|
||||||
List<CcdiEnterpriseBaseInfo> validRecords = new ArrayList<>();
|
List<CcdiEnterpriseBaseInfo> newRecords = new ArrayList<>();
|
||||||
List<IntermediaryEntityImportFailureVO> failures = new ArrayList<>();
|
List<IntermediaryEntityImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
// 1. 批量查询已存在的统一社会信用代码
|
// 批量查询已存在的统一社会信用代码
|
||||||
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
Set<String> existingCreditCodes = getExistingCreditCodes(excelList);
|
||||||
|
|
||||||
// 2. 验证并转换数据
|
// 用于检测Excel内部的重复ID
|
||||||
for (CcdiIntermediaryEntityExcel excel : excelList) {
|
Set<String> excelProcessedIds = new HashSet<>();
|
||||||
|
|
||||||
|
// 分类数据
|
||||||
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
|
CcdiIntermediaryEntityExcel excel = excelList.get(i);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 验证数据
|
// 验证数据
|
||||||
validateEntityData(excel, isUpdateSupport, existingCreditCodes);
|
validateEntityData(excel, existingCreditCodes);
|
||||||
|
|
||||||
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
CcdiEnterpriseBaseInfo entity = new CcdiEnterpriseBaseInfo();
|
||||||
BeanUtils.copyProperties(excel, entity);
|
BeanUtils.copyProperties(excel, entity);
|
||||||
@@ -64,57 +68,41 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
entity.setDataSource("IMPORT");
|
entity.setDataSource("IMPORT");
|
||||||
entity.setEntSource("INTERMEDIARY");
|
entity.setEntSource("INTERMEDIARY");
|
||||||
entity.setCreatedBy(userName);
|
entity.setCreatedBy(userName);
|
||||||
if (existingCreditCodes.contains(excel.getSocialCreditCode()) && isUpdateSupport) {
|
|
||||||
entity.setUpdatedBy(userName);
|
entity.setUpdatedBy(userName);
|
||||||
}
|
|
||||||
|
|
||||||
validRecords.add(entity);
|
if (existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
||||||
|
// 统一社会信用代码在数据库中已存在,直接报错
|
||||||
|
throw new RuntimeException(String.format("统一社会信用代码[%s]已存在,请勿重复导入", excel.getSocialCreditCode()));
|
||||||
|
} else if (excelProcessedIds.contains(excel.getSocialCreditCode())) {
|
||||||
|
// 统一社会信用代码在Excel文件内部重复
|
||||||
|
throw new RuntimeException(String.format("统一社会信用代码[%s]在导入文件中重复,已跳过此条记录", excel.getSocialCreditCode()));
|
||||||
|
} else {
|
||||||
|
newRecords.add(entity);
|
||||||
|
excelProcessedIds.add(excel.getSocialCreditCode()); // 标记为已处理
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failures.add(createFailureVO(excel, e.getMessage()));
|
failures.add(createFailureVO(excel, e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 根据isUpdateSupport选择处理方式
|
// 批量插入新数据
|
||||||
int actualSuccessCount = 0;
|
if (!newRecords.isEmpty()) {
|
||||||
if (isUpdateSupport) {
|
saveBatch(newRecords, 500);
|
||||||
// 更新模式:直接批量导入,数据库自动处理INSERT或UPDATE
|
|
||||||
if (!validRecords.isEmpty()) {
|
|
||||||
actualSuccessCount = saveBatchWithUpsert(validRecords, 500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 仅新增模式:先查询已存在的记录,对冲突的抛出异常
|
|
||||||
Set<String> actualExistingCreditCodes = getExistingCreditCodesFromDb(validRecords);
|
|
||||||
List<CcdiEnterpriseBaseInfo> actualNewRecords = new ArrayList<>();
|
|
||||||
|
|
||||||
for (CcdiEnterpriseBaseInfo record : validRecords) {
|
|
||||||
if (actualExistingCreditCodes.contains(record.getSocialCreditCode())) {
|
|
||||||
// 记录到失败列表
|
|
||||||
failures.add(createFailureVO(record, "该统一社会信用代码已存在"));
|
|
||||||
} else {
|
|
||||||
actualNewRecords.add(record);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量插入新记录
|
// 保存失败记录到Redis
|
||||||
if (!actualNewRecords.isEmpty()) {
|
|
||||||
int insertCount = saveBatch(actualNewRecords, 500);
|
|
||||||
actualSuccessCount = insertCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 保存失败记录到Redis
|
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
String failuresKey = "import:intermediary-entity:" + taskId + ":failures";
|
String failuresKey = "import:intermediary-entity:" + taskId + ":failures";
|
||||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 更新最终状态
|
|
||||||
ImportResult result = new ImportResult();
|
ImportResult result = new ImportResult();
|
||||||
result.setTotalCount(excelList.size());
|
result.setTotalCount(excelList.size());
|
||||||
result.setSuccessCount(actualSuccessCount);
|
result.setSuccessCount(newRecords.size());
|
||||||
result.setFailureCount(failures.size());
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
|
// 更新最终状态
|
||||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
updateImportStatus(taskId, finalStatus, result);
|
updateImportStatus(taskId, finalStatus, result);
|
||||||
}
|
}
|
||||||
@@ -273,11 +261,9 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
* 验证实体中介数据
|
* 验证实体中介数据
|
||||||
*
|
*
|
||||||
* @param excel Excel数据
|
* @param excel Excel数据
|
||||||
* @param isUpdateSupport 是否支持更新
|
|
||||||
* @param existingCreditCodes 已存在的统一社会信用代码集合
|
* @param existingCreditCodes 已存在的统一社会信用代码集合
|
||||||
*/
|
*/
|
||||||
private void validateEntityData(CcdiIntermediaryEntityExcel excel,
|
private void validateEntityData(CcdiIntermediaryEntityExcel excel,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
Set<String> existingCreditCodes) {
|
Set<String> existingCreditCodes) {
|
||||||
// 验证必填字段:机构名称
|
// 验证必填字段:机构名称
|
||||||
if (StringUtils.isEmpty(excel.getEnterpriseName())) {
|
if (StringUtils.isEmpty(excel.getEnterpriseName())) {
|
||||||
@@ -289,18 +275,9 @@ public class CcdiIntermediaryEntityImportServiceImpl implements ICcdiIntermediar
|
|||||||
throw new RuntimeException("统一社会信用代码不能为空");
|
throw new RuntimeException("统一社会信用代码不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果统一社会信用代码已存在但未启用更新支持,抛出异常
|
// 统一社会信用代码格式验证(18位)
|
||||||
if (existingCreditCodes.contains(excel.getSocialCreditCode()) && !isUpdateSupport) {
|
if (excel.getSocialCreditCode().length() != 18) {
|
||||||
throw new RuntimeException("该统一社会信用代码已存在");
|
throw new RuntimeException("统一社会信用代码必须为18位");
|
||||||
}
|
|
||||||
|
|
||||||
// 如果统一社会信用代码不存在,检查唯一性
|
|
||||||
if (!existingCreditCodes.contains(excel.getSocialCreditCode())) {
|
|
||||||
LambdaQueryWrapper<CcdiEnterpriseBaseInfo> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(CcdiEnterpriseBaseInfo::getSocialCreditCode, excel.getSocialCreditCode());
|
|
||||||
if (entityMapper.selectCount(wrapper) > 0) {
|
|
||||||
throw new RuntimeException("该统一社会信用代码已存在");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -43,20 +43,24 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
@Async
|
@Async
|
||||||
@Transactional(rollbackFor = Exception.class)
|
@Transactional(rollbackFor = Exception.class)
|
||||||
public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
|
public void importPersonAsync(List<CcdiIntermediaryPersonExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName) {
|
String userName) {
|
||||||
List<CcdiBizIntermediary> validRecords = new ArrayList<>();
|
List<CcdiBizIntermediary> newRecords = new ArrayList<>();
|
||||||
List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();
|
List<IntermediaryPersonImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
// 1. 批量查询已存在的证件号
|
// 批量查询已存在的证件号
|
||||||
Set<String> existingPersonIds = getExistingPersonIds(excelList);
|
Set<String> existingPersonIds = getExistingPersonIds(excelList);
|
||||||
|
|
||||||
// 2. 验证并转换数据
|
// 用于检测Excel内部的重复ID
|
||||||
for (CcdiIntermediaryPersonExcel excel : excelList) {
|
Set<String> excelProcessedIds = new HashSet<>();
|
||||||
|
|
||||||
|
// 分类数据
|
||||||
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
|
CcdiIntermediaryPersonExcel excel = excelList.get(i);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// 验证数据
|
// 验证数据
|
||||||
validatePersonData(excel, isUpdateSupport, existingPersonIds);
|
validatePersonData(excel, existingPersonIds);
|
||||||
|
|
||||||
CcdiBizIntermediary intermediary = new CcdiBizIntermediary();
|
CcdiBizIntermediary intermediary = new CcdiBizIntermediary();
|
||||||
BeanUtils.copyProperties(excel, intermediary);
|
BeanUtils.copyProperties(excel, intermediary);
|
||||||
@@ -64,57 +68,41 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
// 设置数据来源和审计字段
|
// 设置数据来源和审计字段
|
||||||
intermediary.setDataSource("IMPORT");
|
intermediary.setDataSource("IMPORT");
|
||||||
intermediary.setCreatedBy(userName);
|
intermediary.setCreatedBy(userName);
|
||||||
if (existingPersonIds.contains(excel.getPersonId()) && isUpdateSupport) {
|
|
||||||
intermediary.setUpdatedBy(userName);
|
intermediary.setUpdatedBy(userName);
|
||||||
}
|
|
||||||
|
|
||||||
validRecords.add(intermediary);
|
if (existingPersonIds.contains(excel.getPersonId())) {
|
||||||
|
// 证件号码在数据库中已存在,直接报错
|
||||||
|
throw new RuntimeException(String.format("证件号码[%s]已存在,请勿重复导入", excel.getPersonId()));
|
||||||
|
} else if (excelProcessedIds.contains(excel.getPersonId())) {
|
||||||
|
// 证件号码在Excel文件内部重复
|
||||||
|
throw new RuntimeException(String.format("证件号码[%s]在导入文件中重复,已跳过此条记录", excel.getPersonId()));
|
||||||
|
} else {
|
||||||
|
newRecords.add(intermediary);
|
||||||
|
excelProcessedIds.add(excel.getPersonId()); // 标记为已处理
|
||||||
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
failures.add(createFailureVO(excel, e.getMessage()));
|
failures.add(createFailureVO(excel, e.getMessage()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// 3. 根据isUpdateSupport选择处理方式
|
// 批量插入新数据
|
||||||
int actualSuccessCount = 0;
|
if (!newRecords.isEmpty()) {
|
||||||
if (isUpdateSupport) {
|
saveBatch(newRecords, 500);
|
||||||
// 更新模式:直接批量导入,数据库自动处理INSERT或UPDATE
|
|
||||||
if (!validRecords.isEmpty()) {
|
|
||||||
actualSuccessCount = saveBatchWithUpsert(validRecords, 500);
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
// 仅新增模式:先查询已存在的记录,对冲突的抛出异常
|
|
||||||
Set<String> actualExistingPersonIds = getExistingPersonIdsFromDb(validRecords);
|
|
||||||
List<CcdiBizIntermediary> actualNewRecords = new ArrayList<>();
|
|
||||||
|
|
||||||
for (CcdiBizIntermediary record : validRecords) {
|
|
||||||
if (actualExistingPersonIds.contains(record.getPersonId())) {
|
|
||||||
// 记录到失败列表
|
|
||||||
failures.add(createFailureVO(record, "该证件号码已存在"));
|
|
||||||
} else {
|
|
||||||
actualNewRecords.add(record);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 批量插入新记录
|
// 保存失败记录到Redis
|
||||||
if (!actualNewRecords.isEmpty()) {
|
|
||||||
int insertCount = saveBatch(actualNewRecords, 500);
|
|
||||||
actualSuccessCount = insertCount;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 4. 保存失败记录到Redis
|
|
||||||
if (!failures.isEmpty()) {
|
if (!failures.isEmpty()) {
|
||||||
String failuresKey = "import:intermediary:" + taskId + ":failures";
|
String failuresKey = "import:intermediary:" + taskId + ":failures";
|
||||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 5. 更新最终状态
|
|
||||||
ImportResult result = new ImportResult();
|
ImportResult result = new ImportResult();
|
||||||
result.setTotalCount(excelList.size());
|
result.setTotalCount(excelList.size());
|
||||||
result.setSuccessCount(actualSuccessCount);
|
result.setSuccessCount(newRecords.size());
|
||||||
result.setFailureCount(failures.size());
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
|
// 更新最终状态
|
||||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
updateImportStatus(taskId, finalStatus, result);
|
updateImportStatus(taskId, finalStatus, result);
|
||||||
}
|
}
|
||||||
@@ -273,11 +261,9 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
* 验证个人中介数据
|
* 验证个人中介数据
|
||||||
*
|
*
|
||||||
* @param excel Excel数据
|
* @param excel Excel数据
|
||||||
* @param isUpdateSupport 是否支持更新
|
|
||||||
* @param existingPersonIds 已存在的证件号集合
|
* @param existingPersonIds 已存在的证件号集合
|
||||||
*/
|
*/
|
||||||
private void validatePersonData(CcdiIntermediaryPersonExcel excel,
|
private void validatePersonData(CcdiIntermediaryPersonExcel excel,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
Set<String> existingPersonIds) {
|
Set<String> existingPersonIds) {
|
||||||
// 验证必填字段:姓名
|
// 验证必填字段:姓名
|
||||||
if (StringUtils.isEmpty(excel.getName())) {
|
if (StringUtils.isEmpty(excel.getName())) {
|
||||||
@@ -294,19 +280,5 @@ public class CcdiIntermediaryPersonImportServiceImpl implements ICcdiIntermediar
|
|||||||
if (idCardError != null) {
|
if (idCardError != null) {
|
||||||
throw new RuntimeException("证件号码" + idCardError);
|
throw new RuntimeException("证件号码" + idCardError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 如果证件号已存在但未启用更新支持,抛出异常
|
|
||||||
if (existingPersonIds.contains(excel.getPersonId()) && !isUpdateSupport) {
|
|
||||||
throw new RuntimeException("该证件号码已存在");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 如果证件号不存在,检查唯一性
|
|
||||||
if (!existingPersonIds.contains(excel.getPersonId())) {
|
|
||||||
LambdaQueryWrapper<CcdiBizIntermediary> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.eq(CcdiBizIntermediary::getPersonId, excel.getPersonId());
|
|
||||||
if (intermediaryMapper.selectCount(wrapper) > 0) {
|
|
||||||
throw new RuntimeException("该证件号码已存在");
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -13,9 +13,9 @@ import com.ruoyi.ccdi.domain.vo.CcdiIntermediaryVO;
|
|||||||
import com.ruoyi.ccdi.mapper.CcdiBizIntermediaryMapper;
|
import com.ruoyi.ccdi.mapper.CcdiBizIntermediaryMapper;
|
||||||
import com.ruoyi.ccdi.mapper.CcdiEnterpriseBaseInfoMapper;
|
import com.ruoyi.ccdi.mapper.CcdiEnterpriseBaseInfoMapper;
|
||||||
import com.ruoyi.ccdi.mapper.CcdiIntermediaryMapper;
|
import com.ruoyi.ccdi.mapper.CcdiIntermediaryMapper;
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryService;
|
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryPersonImportService;
|
|
||||||
import com.ruoyi.ccdi.service.ICcdiIntermediaryEntityImportService;
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryEntityImportService;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryPersonImportService;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiIntermediaryService;
|
||||||
import com.ruoyi.common.utils.SecurityUtils;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
@@ -24,7 +24,6 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
@@ -259,13 +258,11 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
* 导入个人中介数据(异步)
|
* 导入个人中介数据(异步)
|
||||||
*
|
*
|
||||||
* @param list Excel实体列表
|
* @param list Excel实体列表
|
||||||
* @param updateSupport 是否更新支持
|
|
||||||
* @return 任务ID
|
* @return 任务ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public String importIntermediaryPerson(List<CcdiIntermediaryPersonExcel> list,
|
public String importIntermediaryPerson(List<CcdiIntermediaryPersonExcel> list) {
|
||||||
boolean updateSupport) {
|
|
||||||
String taskId = UUID.randomUUID().toString();
|
String taskId = UUID.randomUUID().toString();
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -288,7 +285,7 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
String userName = SecurityUtils.getUsername();
|
String userName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
// 调用异步方法
|
// 调用异步方法
|
||||||
personImportService.importPersonAsync(list, updateSupport, taskId, userName);
|
personImportService.importPersonAsync(list, taskId, userName);
|
||||||
|
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
@@ -297,13 +294,11 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
* 导入实体中介数据(异步)
|
* 导入实体中介数据(异步)
|
||||||
*
|
*
|
||||||
* @param list Excel实体列表
|
* @param list Excel实体列表
|
||||||
* @param updateSupport 是否更新支持
|
|
||||||
* @return 任务ID
|
* @return 任务ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public String importIntermediaryEntity(List<CcdiIntermediaryEntityExcel> list,
|
public String importIntermediaryEntity(List<CcdiIntermediaryEntityExcel> list) {
|
||||||
boolean updateSupport) {
|
|
||||||
String taskId = UUID.randomUUID().toString();
|
String taskId = UUID.randomUUID().toString();
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -326,7 +321,7 @@ public class CcdiIntermediaryServiceImpl implements ICcdiIntermediaryService {
|
|||||||
String userName = SecurityUtils.getUsername();
|
String userName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
// 调用异步方法
|
// 调用异步方法
|
||||||
entityImportService.importEntityAsync(list, updateSupport, taskId, userName);
|
entityImportService.importEntityAsync(list, taskId, userName);
|
||||||
|
|
||||||
return taskId;
|
return taskId;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ package com.ruoyi.ccdi.service.impl;
|
|||||||
import com.alibaba.fastjson2.JSON;
|
import com.alibaba.fastjson2.JSON;
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
||||||
import com.ruoyi.ccdi.domain.CcdiStaffRecruitment;
|
import com.ruoyi.ccdi.domain.CcdiStaffRecruitment;
|
||||||
|
import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentAddDTO;
|
||||||
import com.ruoyi.ccdi.domain.excel.CcdiStaffRecruitmentExcel;
|
import com.ruoyi.ccdi.domain.excel.CcdiStaffRecruitmentExcel;
|
||||||
import com.ruoyi.ccdi.domain.vo.ImportResult;
|
import com.ruoyi.ccdi.domain.vo.ImportResult;
|
||||||
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
|
import com.ruoyi.ccdi.domain.vo.ImportStatusVO;
|
||||||
@@ -18,6 +19,7 @@ import org.springframework.data.redis.core.RedisTemplate;
|
|||||||
import org.springframework.scheduling.annotation.Async;
|
import org.springframework.scheduling.annotation.Async;
|
||||||
import org.springframework.scheduling.annotation.EnableAsync;
|
import org.springframework.scheduling.annotation.EnableAsync;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.*;
|
import java.util.*;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
@@ -41,54 +43,45 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Async
|
@Async
|
||||||
|
@Transactional
|
||||||
public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
public void importRecruitmentAsync(List<CcdiStaffRecruitmentExcel> excelList,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
String taskId,
|
String taskId,
|
||||||
String userName) {
|
String userName) {
|
||||||
long startTime = System.currentTimeMillis();
|
|
||||||
|
|
||||||
// 初始化Redis状态
|
|
||||||
String statusKey = "import:recruitment:" + 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);
|
|
||||||
|
|
||||||
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
|
List<CcdiStaffRecruitment> newRecords = new ArrayList<>();
|
||||||
List<CcdiStaffRecruitment> updateRecords = new ArrayList<>();
|
|
||||||
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
|
List<RecruitmentImportFailureVO> failures = new ArrayList<>();
|
||||||
|
|
||||||
// 批量查询已存在的招聘项目编号
|
// 批量查询已存在的招聘项目编号
|
||||||
Set<String> existingRecruitIds = getExistingRecruitIds(excelList);
|
Set<String> existingRecruitIds = getExistingRecruitIds(excelList);
|
||||||
|
|
||||||
|
// 用于检测Excel内部的重复ID
|
||||||
|
Set<String> excelProcessedIds = new HashSet<>();
|
||||||
|
|
||||||
// 分类数据
|
// 分类数据
|
||||||
for (CcdiStaffRecruitmentExcel excel : excelList) {
|
for (int i = 0; i < excelList.size(); i++) {
|
||||||
|
CcdiStaffRecruitmentExcel excel = excelList.get(i);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
|
// 转换为AddDTO进行验证
|
||||||
|
CcdiStaffRecruitmentAddDTO addDTO = new CcdiStaffRecruitmentAddDTO();
|
||||||
|
BeanUtils.copyProperties(excel, addDTO);
|
||||||
|
|
||||||
// 验证数据
|
// 验证数据
|
||||||
validateRecruitmentData(excel, isUpdateSupport, existingRecruitIds);
|
validateRecruitmentData(addDTO, existingRecruitIds);
|
||||||
|
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
||||||
BeanUtils.copyProperties(excel, recruitment);
|
BeanUtils.copyProperties(excel, recruitment);
|
||||||
|
|
||||||
if (existingRecruitIds.contains(excel.getRecruitId())) {
|
if (existingRecruitIds.contains(excel.getRecruitId())) {
|
||||||
if (isUpdateSupport) {
|
// 招聘项目编号在数据库中已存在,直接报错
|
||||||
recruitment.setUpdatedBy(userName);
|
throw new RuntimeException(String.format("招聘项目编号[%s]已存在,请勿重复导入", excel.getRecruitId()));
|
||||||
updateRecords.add(recruitment);
|
} else if (excelProcessedIds.contains(excel.getRecruitId())) {
|
||||||
} else {
|
// 招聘项目编号在Excel文件内部重复
|
||||||
throw new RuntimeException("该招聘项目编号已存在");
|
throw new RuntimeException(String.format("招聘项目编号[%s]在导入文件中重复,已跳过此条记录", excel.getRecruitId()));
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
recruitment.setCreatedBy(userName);
|
recruitment.setCreatedBy(userName);
|
||||||
recruitment.setUpdatedBy(userName);
|
recruitment.setUpdatedBy(userName);
|
||||||
newRecords.add(recruitment);
|
newRecords.add(recruitment);
|
||||||
|
excelProcessedIds.add(excel.getRecruitId()); // 标记为已处理
|
||||||
}
|
}
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
@@ -101,12 +94,7 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
|
|
||||||
// 批量插入新数据
|
// 批量插入新数据
|
||||||
if (!newRecords.isEmpty()) {
|
if (!newRecords.isEmpty()) {
|
||||||
recruitmentMapper.insertBatch(newRecords);
|
saveBatch(newRecords, 500);
|
||||||
}
|
|
||||||
|
|
||||||
// 批量更新已有数据
|
|
||||||
if (!updateRecords.isEmpty() && isUpdateSupport) {
|
|
||||||
recruitmentMapper.updateBatch(updateRecords);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// 保存失败记录到Redis
|
// 保存失败记录到Redis
|
||||||
@@ -115,12 +103,12 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 更新最终状态
|
|
||||||
ImportResult result = new ImportResult();
|
ImportResult result = new ImportResult();
|
||||||
result.setTotalCount(excelList.size());
|
result.setTotalCount(excelList.size());
|
||||||
result.setSuccessCount(newRecords.size() + updateRecords.size());
|
result.setSuccessCount(newRecords.size());
|
||||||
result.setFailureCount(failures.size());
|
result.setFailureCount(failures.size());
|
||||||
|
|
||||||
|
// 更新最终状态
|
||||||
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS";
|
||||||
updateImportStatus(taskId, finalStatus, result);
|
updateImportStatus(taskId, finalStatus, result);
|
||||||
}
|
}
|
||||||
@@ -187,60 +175,59 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
/**
|
/**
|
||||||
* 验证招聘信息数据
|
* 验证招聘信息数据
|
||||||
*/
|
*/
|
||||||
private void validateRecruitmentData(CcdiStaffRecruitmentExcel excel,
|
private void validateRecruitmentData(CcdiStaffRecruitmentAddDTO addDTO,
|
||||||
Boolean isUpdateSupport,
|
|
||||||
Set<String> existingRecruitIds) {
|
Set<String> existingRecruitIds) {
|
||||||
// 验证必填字段
|
// 验证必填字段
|
||||||
if (StringUtils.isEmpty(excel.getRecruitId())) {
|
if (StringUtils.isEmpty(addDTO.getRecruitId())) {
|
||||||
throw new RuntimeException("招聘项目编号不能为空");
|
throw new RuntimeException("招聘项目编号不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getRecruitName())) {
|
if (StringUtils.isEmpty(addDTO.getRecruitName())) {
|
||||||
throw new RuntimeException("招聘项目名称不能为空");
|
throw new RuntimeException("招聘项目名称不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getPosName())) {
|
if (StringUtils.isEmpty(addDTO.getPosName())) {
|
||||||
throw new RuntimeException("职位名称不能为空");
|
throw new RuntimeException("职位名称不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getPosCategory())) {
|
if (StringUtils.isEmpty(addDTO.getPosCategory())) {
|
||||||
throw new RuntimeException("职位类别不能为空");
|
throw new RuntimeException("职位类别不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getPosDesc())) {
|
if (StringUtils.isEmpty(addDTO.getPosDesc())) {
|
||||||
throw new RuntimeException("职位描述不能为空");
|
throw new RuntimeException("职位描述不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandName())) {
|
if (StringUtils.isEmpty(addDTO.getCandName())) {
|
||||||
throw new RuntimeException("应聘人员姓名不能为空");
|
throw new RuntimeException("应聘人员姓名不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandEdu())) {
|
if (StringUtils.isEmpty(addDTO.getCandEdu())) {
|
||||||
throw new RuntimeException("应聘人员学历不能为空");
|
throw new RuntimeException("应聘人员学历不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandId())) {
|
if (StringUtils.isEmpty(addDTO.getCandId())) {
|
||||||
throw new RuntimeException("证件号码不能为空");
|
throw new RuntimeException("证件号码不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandSchool())) {
|
if (StringUtils.isEmpty(addDTO.getCandSchool())) {
|
||||||
throw new RuntimeException("应聘人员毕业院校不能为空");
|
throw new RuntimeException("应聘人员毕业院校不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandMajor())) {
|
if (StringUtils.isEmpty(addDTO.getCandMajor())) {
|
||||||
throw new RuntimeException("应聘人员专业不能为空");
|
throw new RuntimeException("应聘人员专业不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getCandGrad())) {
|
if (StringUtils.isEmpty(addDTO.getCandGrad())) {
|
||||||
throw new RuntimeException("应聘人员毕业年月不能为空");
|
throw new RuntimeException("应聘人员毕业年月不能为空");
|
||||||
}
|
}
|
||||||
if (StringUtils.isEmpty(excel.getAdmitStatus())) {
|
if (StringUtils.isEmpty(addDTO.getAdmitStatus())) {
|
||||||
throw new RuntimeException("录用情况不能为空");
|
throw new RuntimeException("录用情况不能为空");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证证件号码格式
|
// 验证证件号码格式
|
||||||
String idCardError = IdCardUtil.getErrorMessage(excel.getCandId());
|
String idCardError = IdCardUtil.getErrorMessage(addDTO.getCandId());
|
||||||
if (idCardError != null) {
|
if (idCardError != null) {
|
||||||
throw new RuntimeException("证件号码" + idCardError);
|
throw new RuntimeException("证件号码" + idCardError);
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证毕业年月格式(YYYYMM)
|
// 验证毕业年月格式(YYYYMM)
|
||||||
if (!excel.getCandGrad().matches("^((19|20)\\d{2})(0[1-9]|1[0-2])$")) {
|
if (!addDTO.getCandGrad().matches("^((19|20)\\d{2})(0[1-9]|1[0-2])$")) {
|
||||||
throw new RuntimeException("毕业年月格式不正确,应为YYYYMM");
|
throw new RuntimeException("毕业年月格式不正确,应为YYYYMM");
|
||||||
}
|
}
|
||||||
|
|
||||||
// 验证录用状态
|
// 验证录用状态
|
||||||
if (AdmitStatus.getDescByCode(excel.getAdmitStatus()) == null) {
|
if (AdmitStatus.getDescByCode(addDTO.getAdmitStatus()) == null) {
|
||||||
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'");
|
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -266,4 +253,36 @@ public class CcdiStaffRecruitmentImportServiceImpl implements ICcdiStaffRecruitm
|
|||||||
|
|
||||||
redisTemplate.opsForHash().putAll(key, statusData);
|
redisTemplate.opsForHash().putAll(key, statusData);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 批量保存
|
||||||
|
*/
|
||||||
|
private void saveBatch(List<CcdiStaffRecruitment> list, int batchSize) {
|
||||||
|
// 使用真正的批量插入,分批次执行以提高性能
|
||||||
|
for (int i = 0; i < list.size(); i += batchSize) {
|
||||||
|
int end = Math.min(i + batchSize, list.size());
|
||||||
|
List<CcdiStaffRecruitment> subList = list.subList(i, end);
|
||||||
|
|
||||||
|
// 过滤掉已存在的记录,防止主键冲突
|
||||||
|
List<String> recruitIds = subList.stream()
|
||||||
|
.map(CcdiStaffRecruitment::getRecruitId)
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!recruitIds.isEmpty()) {
|
||||||
|
List<CcdiStaffRecruitment> existingRecords = recruitmentMapper.selectBatchIds(recruitIds);
|
||||||
|
Set<String> existingIds = existingRecords.stream()
|
||||||
|
.map(CcdiStaffRecruitment::getRecruitId)
|
||||||
|
.collect(Collectors.toSet());
|
||||||
|
|
||||||
|
// 只插入不存在的记录
|
||||||
|
List<CcdiStaffRecruitment> toInsert = subList.stream()
|
||||||
|
.filter(r -> !existingIds.contains(r.getRecruitId()))
|
||||||
|
.collect(Collectors.toList());
|
||||||
|
|
||||||
|
if (!toInsert.isEmpty()) {
|
||||||
|
recruitmentMapper.insertBatch(toInsert);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,6 +1,5 @@
|
|||||||
package com.ruoyi.ccdi.service.impl;
|
package com.ruoyi.ccdi.service.impl;
|
||||||
|
|
||||||
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
|
|
||||||
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
|
||||||
import com.ruoyi.ccdi.domain.CcdiStaffRecruitment;
|
import com.ruoyi.ccdi.domain.CcdiStaffRecruitment;
|
||||||
import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentAddDTO;
|
import com.ruoyi.ccdi.domain.dto.CcdiStaffRecruitmentAddDTO;
|
||||||
@@ -10,19 +9,21 @@ import com.ruoyi.ccdi.domain.excel.CcdiStaffRecruitmentExcel;
|
|||||||
import com.ruoyi.ccdi.domain.vo.CcdiStaffRecruitmentVO;
|
import com.ruoyi.ccdi.domain.vo.CcdiStaffRecruitmentVO;
|
||||||
import com.ruoyi.ccdi.enums.AdmitStatus;
|
import com.ruoyi.ccdi.enums.AdmitStatus;
|
||||||
import com.ruoyi.ccdi.mapper.CcdiStaffRecruitmentMapper;
|
import com.ruoyi.ccdi.mapper.CcdiStaffRecruitmentMapper;
|
||||||
|
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentImportService;
|
||||||
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentService;
|
import com.ruoyi.ccdi.service.ICcdiStaffRecruitmentService;
|
||||||
import com.ruoyi.common.utils.IdCardUtil;
|
import com.ruoyi.common.utils.SecurityUtils;
|
||||||
import com.ruoyi.common.utils.StringUtils;
|
import com.ruoyi.common.utils.StringUtils;
|
||||||
import jakarta.annotation.Resource;
|
import jakarta.annotation.Resource;
|
||||||
import org.springframework.beans.BeanUtils;
|
import org.springframework.beans.BeanUtils;
|
||||||
|
import org.springframework.data.redis.core.RedisTemplate;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import org.springframework.transaction.annotation.Transactional;
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.stream.Collectors;
|
import java.util.UUID;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 员工招聘信息 服务层处理
|
* 员工招聘信息 服务层处理
|
||||||
@@ -36,6 +37,12 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
@Resource
|
@Resource
|
||||||
private CcdiStaffRecruitmentMapper recruitmentMapper;
|
private CcdiStaffRecruitmentMapper recruitmentMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ICcdiStaffRecruitmentImportService recruitmentImportService;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private RedisTemplate<String, Object> redisTemplate;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 查询招聘信息列表
|
* 查询招聘信息列表
|
||||||
*
|
*
|
||||||
@@ -151,149 +158,43 @@ public class CcdiStaffRecruitmentServiceImpl implements ICcdiStaffRecruitmentSer
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 导入招聘信息数据(批量优化版本)
|
* 导入招聘信息数据(异步)
|
||||||
*
|
*
|
||||||
* @param excelList Excel实体列表
|
* @param excelList Excel实体列表
|
||||||
* @return 结果
|
* @return 任务ID
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
@Transactional
|
@Transactional
|
||||||
public String importRecruitment(List<CcdiStaffRecruitmentExcel> excelList) {
|
public String importRecruitment(java.util.List<CcdiStaffRecruitmentExcel> excelList) {
|
||||||
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
if (StringUtils.isNull(excelList) || excelList.isEmpty()) {
|
||||||
return "至少需要一条数据";
|
throw new RuntimeException("至少需要一条数据");
|
||||||
}
|
}
|
||||||
|
|
||||||
int successNum = 0;
|
// 生成任务ID
|
||||||
int failureNum = 0;
|
String taskId = UUID.randomUUID().toString();
|
||||||
StringBuilder successMsg = new StringBuilder();
|
long startTime = System.currentTimeMillis();
|
||||||
StringBuilder failureMsg = new StringBuilder();
|
|
||||||
|
|
||||||
// 第一阶段:数据验证和分类
|
// 获取当前用户名
|
||||||
List<CcdiStaffRecruitment> toInsertList = new ArrayList<>();
|
String userName = SecurityUtils.getUsername();
|
||||||
|
|
||||||
// 批量收集所有招聘项目编号
|
// 初始化Redis状态
|
||||||
List<String> recruitIds = new ArrayList<>();
|
String statusKey = "import:recruitment:" + 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", "正在处理...");
|
||||||
|
|
||||||
for (CcdiStaffRecruitmentExcel excel : excelList) {
|
redisTemplate.opsForHash().putAll(statusKey, statusData);
|
||||||
if (StringUtils.isNotEmpty(excel.getRecruitId())) {
|
redisTemplate.expire(statusKey, 7, TimeUnit.DAYS);
|
||||||
recruitIds.add(excel.getRecruitId());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 批量查询已存在的招聘项目编号
|
// 调用异步导入服务
|
||||||
Map<String, CcdiStaffRecruitment> existingRecruitmentMap = new HashMap<>();
|
recruitmentImportService.importRecruitmentAsync(excelList, taskId, userName);
|
||||||
if (!recruitIds.isEmpty()) {
|
|
||||||
LambdaQueryWrapper<CcdiStaffRecruitment> wrapper = new LambdaQueryWrapper<>();
|
|
||||||
wrapper.in(CcdiStaffRecruitment::getRecruitId, recruitIds);
|
|
||||||
List<CcdiStaffRecruitment> existingRecruitments = recruitmentMapper.selectList(wrapper);
|
|
||||||
existingRecruitmentMap = existingRecruitments.stream()
|
|
||||||
.collect(Collectors.toMap(CcdiStaffRecruitment::getRecruitId, r -> r));
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第二阶段:处理每条数据
|
return taskId;
|
||||||
for (int i = 0; i < excelList.size(); i++) {
|
|
||||||
CcdiStaffRecruitmentExcel excel = excelList.get(i);
|
|
||||||
try {
|
|
||||||
// 验证必填字段和数据格式
|
|
||||||
validateRecruitmentDataBasic(excel);
|
|
||||||
|
|
||||||
CcdiStaffRecruitment recruitment = new CcdiStaffRecruitment();
|
|
||||||
BeanUtils.copyProperties(excel, recruitment);
|
|
||||||
|
|
||||||
// 检查是否已存在
|
|
||||||
CcdiStaffRecruitment existingRecruitment = existingRecruitmentMap.get(excel.getRecruitId());
|
|
||||||
|
|
||||||
// 判断数据状态
|
|
||||||
if (existingRecruitment != null) {
|
|
||||||
// 招聘项目编号已存在,直接报错
|
|
||||||
throw new RuntimeException(String.format("招聘项目编号[%s]已存在,请勿重复导入", excel.getRecruitId()));
|
|
||||||
} else {
|
|
||||||
// 招聘项目编号不存在,新增数据
|
|
||||||
recruitment.setCreatedBy("导入");
|
|
||||||
toInsertList.add(recruitment);
|
|
||||||
}
|
|
||||||
|
|
||||||
} catch (Exception e) {
|
|
||||||
failureNum++;
|
|
||||||
failureMsg.append("<br/>").append(failureNum).append("、招聘项目编号 ").append(excel.getRecruitId())
|
|
||||||
.append(" 导入失败:").append(e.getMessage());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第三阶段:批量执行数据库操作
|
|
||||||
if (!toInsertList.isEmpty()) {
|
|
||||||
// 使用自定义批量插入方法
|
|
||||||
recruitmentMapper.insertBatch(toInsertList);
|
|
||||||
successNum += toInsertList.size();
|
|
||||||
}
|
|
||||||
|
|
||||||
// 第四阶段:返回结果(只返回错误信息)
|
|
||||||
if (failureNum > 0) {
|
|
||||||
failureMsg.insert(0, "很抱歉,导入完成!成功 " + successNum + " 条,失败 " + failureNum + " 条,错误如下:");
|
|
||||||
throw new RuntimeException(failureMsg.toString());
|
|
||||||
} else {
|
|
||||||
successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据类型:新增 ");
|
|
||||||
successMsg.append(toInsertList.size()).append(" 条");
|
|
||||||
return successMsg.toString();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 验证招聘信息数据(仅基本字段验证,不进行数据库查询)
|
|
||||||
*/
|
|
||||||
private void validateRecruitmentDataBasic(CcdiStaffRecruitmentExcel excel) {
|
|
||||||
// 验证必填字段
|
|
||||||
if (StringUtils.isEmpty(excel.getRecruitId())) {
|
|
||||||
throw new RuntimeException("招聘项目编号不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getRecruitName())) {
|
|
||||||
throw new RuntimeException("招聘项目名称不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getPosName())) {
|
|
||||||
throw new RuntimeException("职位名称不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getPosCategory())) {
|
|
||||||
throw new RuntimeException("职位类别不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getPosDesc())) {
|
|
||||||
throw new RuntimeException("职位描述不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandName())) {
|
|
||||||
throw new RuntimeException("应聘人员姓名不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandEdu())) {
|
|
||||||
throw new RuntimeException("应聘人员学历不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandId())) {
|
|
||||||
throw new RuntimeException("证件号码不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandSchool())) {
|
|
||||||
throw new RuntimeException("应聘人员毕业院校不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandMajor())) {
|
|
||||||
throw new RuntimeException("应聘人员专业不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getCandGrad())) {
|
|
||||||
throw new RuntimeException("应聘人员毕业年月不能为空");
|
|
||||||
}
|
|
||||||
if (StringUtils.isEmpty(excel.getAdmitStatus())) {
|
|
||||||
throw new RuntimeException("录用情况不能为空");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证证件号码格式
|
|
||||||
String idCardError = IdCardUtil.getErrorMessage(excel.getCandId());
|
|
||||||
if (idCardError != null) {
|
|
||||||
throw new RuntimeException("证件号码" + idCardError);
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证毕业年月格式(YYYYMM)
|
|
||||||
if (!excel.getCandGrad().matches("^((19|20)\\d{2})(0[1-9]|1[0-2])$")) {
|
|
||||||
throw new RuntimeException("毕业年月格式不正确,应为YYYYMM");
|
|
||||||
}
|
|
||||||
|
|
||||||
// 验证录用状态
|
|
||||||
if (AdmitStatus.getDescByCode(excel.getAdmitStatus()) == null) {
|
|
||||||
throw new RuntimeException("录用情况只能填写'录用'、'未录用'或'放弃'");
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -52,11 +52,30 @@ export function importTemplate() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// 导入招聘信息
|
// 导入招聘信息
|
||||||
export function importData(data, updateSupport) {
|
export function importData(file) {
|
||||||
|
const formData = new FormData()
|
||||||
|
formData.append('file', file)
|
||||||
return request({
|
return request({
|
||||||
url: '/ccdi/staffRecruitment/importData?updateSupport=' + updateSupport,
|
url: '/ccdi/staffRecruitment/importData',
|
||||||
method: 'post',
|
method: 'post',
|
||||||
data: data
|
data: formData
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询导入状态
|
||||||
|
export function getImportStatus(taskId) {
|
||||||
|
return request({
|
||||||
|
url: '/ccdi/staffRecruitment/importStatus/' + taskId,
|
||||||
|
method: 'get'
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
// 查询导入失败记录
|
||||||
|
export function getImportFailures(taskId, pageNum, pageSize) {
|
||||||
|
return request({
|
||||||
|
url: '/ccdi/staffRecruitment/importFailures/' + taskId,
|
||||||
|
method: 'get',
|
||||||
|
params: { pageNum, pageSize }
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -81,6 +81,20 @@
|
|||||||
v-hasPermi="['ccdi:staffRecruitment:export']"
|
v-hasPermi="['ccdi:staffRecruitment:export']"
|
||||||
>导出</el-button>
|
>导出</el-button>
|
||||||
</el-col>
|
</el-col>
|
||||||
|
<el-col :span="1.5" v-if="showFailureButton">
|
||||||
|
<el-tooltip
|
||||||
|
:content="getLastImportTooltip()"
|
||||||
|
placement="top"
|
||||||
|
>
|
||||||
|
<el-button
|
||||||
|
type="warning"
|
||||||
|
plain
|
||||||
|
icon="el-icon-warning"
|
||||||
|
size="mini"
|
||||||
|
@click="viewImportFailures"
|
||||||
|
>查看导入失败记录</el-button>
|
||||||
|
</el-tooltip>
|
||||||
|
</el-col>
|
||||||
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
<right-toolbar :showSearch.sync="showSearch" @queryTable="getList"></right-toolbar>
|
||||||
</el-row>
|
</el-row>
|
||||||
|
|
||||||
@@ -308,7 +322,7 @@
|
|||||||
</el-dialog>
|
</el-dialog>
|
||||||
|
|
||||||
<!-- 导入对话框 -->
|
<!-- 导入对话框 -->
|
||||||
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body @close="handleImportDialogClose" v-loading="upload.isUploading" element-loading-text="正在导入数据,请稍候..." element-loading-spinner="el-icon-loading" element-loading-background="rgba(0, 0, 0, 0.7)">
|
<el-dialog :title="upload.title" :visible.sync="upload.open" width="400px" append-to-body @close="handleImportDialogClose">
|
||||||
<el-upload
|
<el-upload
|
||||||
ref="upload"
|
ref="upload"
|
||||||
:limit="1"
|
:limit="1"
|
||||||
@@ -343,11 +357,58 @@
|
|||||||
title="导入结果"
|
title="导入结果"
|
||||||
@close="handleImportResultClose"
|
@close="handleImportResultClose"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
<!-- 导入失败记录对话框 -->
|
||||||
|
<el-dialog
|
||||||
|
title="导入失败记录"
|
||||||
|
:visible.sync="failureDialogVisible"
|
||||||
|
width="1200px"
|
||||||
|
append-to-body
|
||||||
|
>
|
||||||
|
<el-alert
|
||||||
|
v-if="lastImportInfo"
|
||||||
|
:title="lastImportInfo"
|
||||||
|
type="info"
|
||||||
|
:closable="false"
|
||||||
|
style="margin-bottom: 15px"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<el-table :data="failureList" v-loading="failureLoading">
|
||||||
|
<el-table-column label="招聘项目编号" prop="recruitId" align="center" width="150" />
|
||||||
|
<el-table-column label="招聘项目名称" prop="recruitName" align="center" :show-overflow-tooltip="true"/>
|
||||||
|
<el-table-column label="职位名称" prop="posName" align="center" :show-overflow-tooltip="true"/>
|
||||||
|
<el-table-column label="候选人姓名" prop="candName" align="center" width="120"/>
|
||||||
|
<el-table-column label="证件号码" prop="candId" align="center" width="180"/>
|
||||||
|
<el-table-column label="失败原因" prop="errorMessage" align="center" min-width="200"
|
||||||
|
:show-overflow-tooltip="true" />
|
||||||
|
</el-table>
|
||||||
|
|
||||||
|
<pagination
|
||||||
|
v-show="failureTotal > 0"
|
||||||
|
:total="failureTotal"
|
||||||
|
:page.sync="failureQueryParams.pageNum"
|
||||||
|
:limit.sync="failureQueryParams.pageSize"
|
||||||
|
@pagination="getFailureList"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<div slot="footer" class="dialog-footer">
|
||||||
|
<el-button @click="failureDialogVisible = false">关闭</el-button>
|
||||||
|
<el-button type="danger" plain @click="clearImportHistory">清除历史记录</el-button>
|
||||||
|
</div>
|
||||||
|
</el-dialog>
|
||||||
</div>
|
</div>
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import { addStaffRecruitment, delStaffRecruitment, getStaffRecruitment, listStaffRecruitment, updateStaffRecruitment, importTemplate } from "@/api/ccdiStaffRecruitment";
|
import {
|
||||||
|
addStaffRecruitment,
|
||||||
|
delStaffRecruitment,
|
||||||
|
getImportFailures,
|
||||||
|
getImportStatus,
|
||||||
|
getStaffRecruitment,
|
||||||
|
listStaffRecruitment,
|
||||||
|
updateStaffRecruitment
|
||||||
|
} from "@/api/ccdiStaffRecruitment";
|
||||||
import {getToken} from "@/utils/auth";
|
import {getToken} from "@/utils/auth";
|
||||||
import ImportResultDialog from "@/components/ImportResultDialog.vue";
|
import ImportResultDialog from "@/components/ImportResultDialog.vue";
|
||||||
|
|
||||||
@@ -463,11 +524,46 @@ export default {
|
|||||||
},
|
},
|
||||||
// 导入结果弹窗
|
// 导入结果弹窗
|
||||||
importResultVisible: false,
|
importResultVisible: false,
|
||||||
importResultContent: ""
|
importResultContent: "",
|
||||||
|
// 导入轮询定时器
|
||||||
|
importPollingTimer: null,
|
||||||
|
// 是否显示查看失败记录按钮
|
||||||
|
showFailureButton: false,
|
||||||
|
// 当前导入任务ID
|
||||||
|
currentTaskId: null,
|
||||||
|
// 失败记录对话框
|
||||||
|
failureDialogVisible: false,
|
||||||
|
failureList: [],
|
||||||
|
failureLoading: false,
|
||||||
|
failureTotal: 0,
|
||||||
|
failureQueryParams: {
|
||||||
|
pageNum: 1,
|
||||||
|
pageSize: 10
|
||||||
|
}
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
computed: {
|
||||||
|
/**
|
||||||
|
* 上次导入信息摘要
|
||||||
|
*/
|
||||||
|
lastImportInfo() {
|
||||||
|
const savedTask = this.getImportTaskFromStorage();
|
||||||
|
if (savedTask && savedTask.totalCount) {
|
||||||
|
return `导入时间: ${this.parseTime(savedTask.saveTime)} | 总数: ${savedTask.totalCount}条 | 成功: ${savedTask.successCount}条 | 失败: ${savedTask.failureCount}条`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
}
|
||||||
|
},
|
||||||
created() {
|
created() {
|
||||||
this.getList();
|
this.getList();
|
||||||
|
this.restoreImportState(); // 恢复导入状态
|
||||||
|
},
|
||||||
|
beforeDestroy() {
|
||||||
|
// 清理定时器
|
||||||
|
if (this.importPollingTimer) {
|
||||||
|
clearInterval(this.importPollingTimer);
|
||||||
|
this.importPollingTimer = null;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
/** 查询招聘信息列表 */
|
/** 查询招聘信息列表 */
|
||||||
@@ -601,11 +697,255 @@ export default {
|
|||||||
handleFileSuccess(response, file, fileList) {
|
handleFileSuccess(response, file, fileList) {
|
||||||
this.upload.isUploading = false;
|
this.upload.isUploading = false;
|
||||||
this.upload.open = false;
|
this.upload.open = false;
|
||||||
|
|
||||||
|
if (response.code === 200) {
|
||||||
|
// 验证响应数据完整性
|
||||||
|
if (!response.data || !response.data.taskId) {
|
||||||
|
this.$modal.msgError('导入任务创建失败:缺少任务ID');
|
||||||
|
this.upload.isUploading = false;
|
||||||
|
this.upload.open = true;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const taskId = response.data.taskId;
|
||||||
|
|
||||||
|
// 清除旧的轮询定时器
|
||||||
|
if (this.importPollingTimer) {
|
||||||
|
clearInterval(this.importPollingTimer);
|
||||||
|
this.importPollingTimer = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
|
||||||
|
// 保存新任务的初始状态
|
||||||
|
this.saveImportTaskToStorage({
|
||||||
|
taskId: taskId,
|
||||||
|
status: 'PROCESSING',
|
||||||
|
timestamp: Date.now(),
|
||||||
|
hasFailures: false
|
||||||
|
});
|
||||||
|
|
||||||
|
// 重置状态
|
||||||
|
this.showFailureButton = false;
|
||||||
|
this.currentTaskId = taskId;
|
||||||
|
|
||||||
|
// 显示后台处理提示
|
||||||
|
this.$notify({
|
||||||
|
title: '导入任务已提交',
|
||||||
|
message: '正在后台处理中,处理完成后将通知您',
|
||||||
|
type: 'info',
|
||||||
|
duration: 3000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 开始轮询检查状态
|
||||||
|
this.startImportStatusPolling(taskId);
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError(response.msg);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 开始轮询导入状态 */
|
||||||
|
startImportStatusPolling(taskId) {
|
||||||
|
let pollCount = 0;
|
||||||
|
const maxPolls = 150; // 最多轮询150次(5分钟)
|
||||||
|
|
||||||
|
this.importPollingTimer = setInterval(async () => {
|
||||||
|
try {
|
||||||
|
pollCount++;
|
||||||
|
|
||||||
|
// 超时检查
|
||||||
|
if (pollCount > maxPolls) {
|
||||||
|
clearInterval(this.importPollingTimer);
|
||||||
|
this.$modal.msgWarning('导入任务处理超时,请联系管理员');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await getImportStatus(taskId);
|
||||||
|
|
||||||
|
if (response.data && response.data.status !== 'PROCESSING') {
|
||||||
|
clearInterval(this.importPollingTimer);
|
||||||
|
this.handleImportComplete(response.data);
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
clearInterval(this.importPollingTimer);
|
||||||
|
this.$modal.msgError('查询导入状态失败: ' + error.message);
|
||||||
|
}
|
||||||
|
}, 2000); // 每2秒轮询一次
|
||||||
|
},
|
||||||
|
/** 处理导入完成 */
|
||||||
|
handleImportComplete(statusResult) {
|
||||||
|
// 更新localStorage中的任务状态
|
||||||
|
this.saveImportTaskToStorage({
|
||||||
|
taskId: statusResult.taskId,
|
||||||
|
status: statusResult.status,
|
||||||
|
hasFailures: statusResult.failureCount > 0,
|
||||||
|
totalCount: statusResult.totalCount,
|
||||||
|
successCount: statusResult.successCount,
|
||||||
|
failureCount: statusResult.failureCount
|
||||||
|
});
|
||||||
|
|
||||||
|
if (statusResult.status === 'SUCCESS') {
|
||||||
|
// 全部成功
|
||||||
|
this.$notify({
|
||||||
|
title: '导入完成',
|
||||||
|
message: `全部成功!共导入${statusResult.totalCount}条数据`,
|
||||||
|
type: 'success',
|
||||||
|
duration: 5000
|
||||||
|
});
|
||||||
|
this.showFailureButton = false;
|
||||||
this.getList();
|
this.getList();
|
||||||
// 显示导入结果弹窗
|
} else if (statusResult.failureCount > 0) {
|
||||||
this.importResultContent = response.msg || response;
|
// 部分失败
|
||||||
this.importResultVisible = true;
|
this.$notify({
|
||||||
this.$refs.upload.clearFiles();
|
title: '导入完成',
|
||||||
|
message: `成功${statusResult.successCount}条,失败${statusResult.failureCount}条`,
|
||||||
|
type: 'warning',
|
||||||
|
duration: 5000
|
||||||
|
});
|
||||||
|
|
||||||
|
// 显示查看失败记录按钮
|
||||||
|
this.showFailureButton = true;
|
||||||
|
this.currentTaskId = statusResult.taskId;
|
||||||
|
|
||||||
|
// 刷新列表
|
||||||
|
this.getList();
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 查询失败记录列表 */
|
||||||
|
getFailureList() {
|
||||||
|
this.failureLoading = true;
|
||||||
|
getImportFailures(
|
||||||
|
this.currentTaskId,
|
||||||
|
this.failureQueryParams.pageNum,
|
||||||
|
this.failureQueryParams.pageSize
|
||||||
|
).then(response => {
|
||||||
|
this.failureList = response.rows;
|
||||||
|
this.failureTotal = response.total;
|
||||||
|
this.failureLoading = false;
|
||||||
|
}).catch(error => {
|
||||||
|
this.failureLoading = false;
|
||||||
|
|
||||||
|
// 处理不同类型的错误
|
||||||
|
if (error.response) {
|
||||||
|
const status = error.response.status;
|
||||||
|
|
||||||
|
if (status === 404) {
|
||||||
|
// 记录不存在或已过期
|
||||||
|
this.$modal.msgWarning('导入记录已过期,无法查看失败记录');
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
this.showFailureButton = false;
|
||||||
|
this.currentTaskId = null;
|
||||||
|
this.failureDialogVisible = false;
|
||||||
|
} else if (status === 500) {
|
||||||
|
this.$modal.msgError('服务器错误,请稍后重试');
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError(`查询失败: ${error.response.data.msg || '未知错误'}`);
|
||||||
|
}
|
||||||
|
} else if (error.request) {
|
||||||
|
// 请求发送了但没有收到响应
|
||||||
|
this.$modal.msgError('网络连接失败,请检查网络');
|
||||||
|
} else {
|
||||||
|
this.$modal.msgError('查询失败记录失败: ' + error.message);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
},
|
||||||
|
/** 查看导入失败记录 */
|
||||||
|
viewImportFailures() {
|
||||||
|
this.failureDialogVisible = true;
|
||||||
|
this.getFailureList();
|
||||||
|
},
|
||||||
|
/** 恢复导入状态 */
|
||||||
|
restoreImportState() {
|
||||||
|
const savedTask = this.getImportTaskFromStorage();
|
||||||
|
|
||||||
|
if (!savedTask) {
|
||||||
|
this.showFailureButton = false;
|
||||||
|
this.currentTaskId = null;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 如果有失败记录,恢复按钮显示
|
||||||
|
if (savedTask.hasFailures && savedTask.taskId) {
|
||||||
|
this.currentTaskId = savedTask.taskId;
|
||||||
|
this.showFailureButton = true;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 获取上次导入的提示信息 */
|
||||||
|
getLastImportTooltip() {
|
||||||
|
const savedTask = this.getImportTaskFromStorage();
|
||||||
|
if (savedTask && savedTask.saveTime) {
|
||||||
|
const date = new Date(savedTask.saveTime);
|
||||||
|
const timeStr = this.parseTime(date, '{y}-{m}-{d} {h}:{i}');
|
||||||
|
return `上次导入: ${timeStr}`;
|
||||||
|
}
|
||||||
|
return '';
|
||||||
|
},
|
||||||
|
/** 保存导入任务到localStorage */
|
||||||
|
saveImportTaskToStorage(taskData) {
|
||||||
|
try {
|
||||||
|
const data = {
|
||||||
|
...taskData,
|
||||||
|
saveTime: Date.now()
|
||||||
|
};
|
||||||
|
localStorage.setItem('staff_recruitment_import_last_task', JSON.stringify(data));
|
||||||
|
} catch (error) {
|
||||||
|
console.error('保存导入任务状态失败:', error);
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 从localStorage读取导入任务 */
|
||||||
|
getImportTaskFromStorage() {
|
||||||
|
try {
|
||||||
|
const data = localStorage.getItem('staff_recruitment_import_last_task');
|
||||||
|
if (!data) return null;
|
||||||
|
|
||||||
|
const task = JSON.parse(data);
|
||||||
|
|
||||||
|
// 数据格式校验
|
||||||
|
if (!task || !task.taskId) {
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 时间戳校验
|
||||||
|
if (task.saveTime && typeof task.saveTime !== 'number') {
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
// 过期检查(7天)
|
||||||
|
const sevenDays = 7 * 24 * 60 * 60 * 1000;
|
||||||
|
if (Date.now() - task.saveTime > sevenDays) {
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
return task;
|
||||||
|
} catch (error) {
|
||||||
|
console.error('读取导入任务状态失败:', error);
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
},
|
||||||
|
/** 清除导入历史记录 */
|
||||||
|
clearImportHistory() {
|
||||||
|
this.$confirm('确认清除上次导入记录?', '提示', {
|
||||||
|
confirmButtonText: '确定',
|
||||||
|
cancelButtonText: '取消',
|
||||||
|
type: 'warning'
|
||||||
|
}).then(() => {
|
||||||
|
this.clearImportTaskFromStorage();
|
||||||
|
this.showFailureButton = false;
|
||||||
|
this.currentTaskId = null;
|
||||||
|
this.failureDialogVisible = false;
|
||||||
|
this.$message.success('已清除');
|
||||||
|
}).catch(() => {});
|
||||||
|
},
|
||||||
|
/** 清除localStorage中的导入任务 */
|
||||||
|
clearImportTaskFromStorage() {
|
||||||
|
try {
|
||||||
|
localStorage.removeItem('staff_recruitment_import_last_task');
|
||||||
|
} catch (error) {
|
||||||
|
console.error('清除导入任务状态失败:', error);
|
||||||
|
}
|
||||||
},
|
},
|
||||||
// 导入结果弹窗关闭
|
// 导入结果弹窗关闭
|
||||||
handleImportResultClose() {
|
handleImportResultClose() {
|
||||||
|
|||||||
Reference in New Issue
Block a user