From 3ffe173dd6fcca4059593c6963da16c76d8ac10e Mon Sep 17 00:00:00 2001 From: wkc <978997012@qq.com> Date: Mon, 9 Feb 2026 14:33:23 +0800 Subject: [PATCH] =?UTF-8?q?=E6=96=87=E4=BB=B6=E5=A4=B9=E6=95=B4=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../controller/CcdiEmployeeController.java | 191 ++++++++++ .../com/ruoyi/ccdi/domain/CcdiEmployee.java | 62 ++++ .../ccdi/domain/dto/CcdiEmployeeAddDTO.java | 53 +++ .../ccdi/domain/dto/CcdiEmployeeEditDTO.java | 52 +++ .../ccdi/domain/dto/CcdiEmployeeQueryDTO.java | 34 ++ .../ccdi/domain/excel/CcdiEmployeeExcel.java | 66 ++++ .../ruoyi/ccdi/domain/vo/CcdiEmployeeVO.java | 59 ++++ .../ruoyi/ccdi/mapper/CcdiEmployeeMapper.java | 41 +++ .../service/ICcdiEmployeeImportService.java | 40 +++ .../ccdi/service/ICcdiEmployeeService.java | 86 +++++ .../impl/CcdiEmployeeImportServiceImpl.java | 326 ++++++++++++++++++ .../service/impl/CcdiEmployeeServiceImpl.java | 249 +++++++++++++ ...CcdiStaffFmyRelationImportServiceImpl.java | 271 +++++++++++++++ .../impl/CcdiStaffFmyRelationServiceImpl.java | 231 +++++++++++++ .../mapper/ccdi/CcdiEmployeeMapper.xml | 81 +++++ 15 files changed, 1842 insertions(+) create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiEmployee.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeAddDTO.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeEditDTO.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeQueryDTO.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiEmployeeExcel.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiEmployeeVO.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeImportService.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java create mode 100644 ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java create mode 100644 ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java new file mode 100644 index 0000000..8498b13 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/controller/CcdiEmployeeController.java @@ -0,0 +1,191 @@ +package com.ruoyi.ccdi.controller; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiEmployeeExcel; +import com.ruoyi.ccdi.domain.vo.CcdiEmployeeVO; +import com.ruoyi.ccdi.domain.vo.ImportFailureVO; +import com.ruoyi.ccdi.domain.vo.ImportResultVO; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.service.ICcdiEmployeeImportService; +import com.ruoyi.ccdi.service.ICcdiEmployeeService; +import com.ruoyi.ccdi.utils.EasyExcelUtil; +import com.ruoyi.common.annotation.Log; +import com.ruoyi.common.core.controller.BaseController; +import com.ruoyi.common.core.domain.AjaxResult; +import com.ruoyi.common.core.page.PageDomain; +import com.ruoyi.common.core.page.TableDataInfo; +import com.ruoyi.common.core.page.TableSupport; +import com.ruoyi.common.enums.BusinessType; +import io.swagger.v3.oas.annotations.Operation; +import io.swagger.v3.oas.annotations.tags.Tag; +import jakarta.annotation.Resource; +import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.annotation.Validated; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; + +import java.util.List; + +/** + * 员工信息Controller + * + * @author ruoyi + * @date 2026-01-28 + */ +@Tag(name = "员工信息管理") +@RestController +@RequestMapping("/ccdi/employee") +public class CcdiEmployeeController extends BaseController { + + @Resource + private ICcdiEmployeeService employeeService; + + @Resource + private ICcdiEmployeeImportService importAsyncService; + + /** + * 查询员工列表 + */ + @Operation(summary = "查询员工列表") + @PreAuthorize("@ss.hasPermi('ccdi:employee:list')") + @GetMapping("/list") + public TableDataInfo list(CcdiEmployeeQueryDTO queryDTO) { + // 使用MyBatis Plus分页 + PageDomain pageDomain = TableSupport.buildPageRequest(); + Page page = new Page<>(pageDomain.getPageNum(), pageDomain.getPageSize()); + Page result = employeeService.selectEmployeePage(page, queryDTO); + return getDataTable(result.getRecords(), result.getTotal()); + } + + /** + * 导出员工列表 + */ + @Operation(summary = "导出员工列表") + @PreAuthorize("@ss.hasPermi('ccdi:employee:export')") + @Log(title = "员工信息", businessType = BusinessType.EXPORT) + @PostMapping("/export") + public void export(HttpServletResponse response, CcdiEmployeeQueryDTO queryDTO) { + List list = employeeService.selectEmployeeListForExport(queryDTO); + EasyExcelUtil.exportExcel(response, list, CcdiEmployeeExcel.class, "员工信息"); + } + + /** + * 获取员工详细信息 + */ + @Operation(summary = "获取员工详细信息") + @PreAuthorize("@ss.hasPermi('ccdi:employee:query')") + @GetMapping(value = "/{employeeId}") + public AjaxResult getInfo(@PathVariable Long employeeId) { + return success(employeeService.selectEmployeeById(employeeId)); + } + + /** + * 新增员工 + */ + @Operation(summary = "新增员工") + @PreAuthorize("@ss.hasPermi('ccdi:employee:add')") + @Log(title = "员工信息", businessType = BusinessType.INSERT) + @PostMapping + public AjaxResult add(@Validated @RequestBody CcdiEmployeeAddDTO addDTO) { + return toAjax(employeeService.insertEmployee(addDTO)); + } + + /** + * 修改员工 + */ + @Operation(summary = "修改员工") + @PreAuthorize("@ss.hasPermi('ccdi:employee:edit')") + @Log(title = "员工信息", businessType = BusinessType.UPDATE) + @PutMapping + public AjaxResult edit(@Validated @RequestBody CcdiEmployeeEditDTO editDTO) { + return toAjax(employeeService.updateEmployee(editDTO)); + } + + /** + * 删除员工 + */ + @Operation(summary = "删除员工") + @PreAuthorize("@ss.hasPermi('ccdi:employee:remove')") + @Log(title = "员工信息", businessType = BusinessType.DELETE) + @DeleteMapping("/{employeeIds}") + public AjaxResult remove(@PathVariable Long[] employeeIds) { + return toAjax(employeeService.deleteEmployeeByIds(employeeIds)); + } + + /** + * 下载带字典下拉框的导入模板 + * 使用@DictDropdown注解自动添加下拉框 + */ + @Operation(summary = "下载导入模板") + @PostMapping("/importTemplate") + public void importTemplate(HttpServletResponse response) { + EasyExcelUtil.importTemplateWithDictDropdown(response, CcdiEmployeeExcel.class, "员工信息"); + } + + /** + * 导入员工信息(异步) + */ + @Operation(summary = "导入员工信息") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @Log(title = "员工信息", businessType = BusinessType.IMPORT) + @PostMapping("/importData") + public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception { + List list = EasyExcelUtil.importExcel(file.getInputStream(), CcdiEmployeeExcel.class); + + if (list == null || list.isEmpty()) { + return error("至少需要一条数据"); + } + + // 提交异步任务 + String taskId = employeeService.importEmployee(list, updateSupport); + + // 立即返回,不等待后台任务完成 + ImportResultVO result = new ImportResultVO(); + result.setTaskId(taskId); + result.setStatus("PROCESSING"); + result.setMessage("导入任务已提交,正在后台处理"); + + return AjaxResult.success("导入任务已提交,正在后台处理", result); + } + + /** + * 查询导入状态 + */ + @Operation(summary = "查询员工导入状态") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @GetMapping("/importStatus/{taskId}") + public AjaxResult getImportStatus(@PathVariable String taskId) { + try { + ImportStatusVO status = importAsyncService.getImportStatus(taskId); + return success(status); + } catch (Exception e) { + return error(e.getMessage()); + } + } + + /** + * 查询导入失败记录 + */ + @Operation(summary = "查询导入失败记录") + @PreAuthorize("@ss.hasPermi('ccdi:employee:import')") + @GetMapping("/importFailures/{taskId}") + public TableDataInfo getImportFailures( + @PathVariable String taskId, + @RequestParam(defaultValue = "1") Integer pageNum, + @RequestParam(defaultValue = "10") Integer pageSize) { + + List failures = importAsyncService.getImportFailures( taskId); + + // 手动分页 + int fromIndex = (pageNum - 1) * pageSize; + int toIndex = Math.min(fromIndex + pageSize, failures.size()); + + List pageData = failures.subList(fromIndex, toIndex); + + return getDataTable(pageData, failures.size()); + } +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiEmployee.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiEmployee.java new file mode 100644 index 0000000..2dc0083 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/CcdiEmployee.java @@ -0,0 +1,62 @@ +package com.ruoyi.ccdi.domain; + +import com.baomidou.mybatisplus.annotation.FieldFill; +import com.baomidou.mybatisplus.annotation.IdType; +import com.baomidou.mybatisplus.annotation.TableField; +import com.baomidou.mybatisplus.annotation.TableId; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工信息对象 dpc_employee + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployee implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工ID(柜员号,7位数字) */ + @TableId(type = IdType.INPUT) + private Long employeeId; + + /** 姓名 */ + private String name; + + /** 所属部门ID */ + private Long deptId; + + /** 身份证号 */ + private String idCard; + + /** 电话 */ + private String phone; + + /** 入职时间 */ + private Date hireDate; + + /** 状态 */ + private String status; + + /** 创建者 */ + @TableField(fill = FieldFill.INSERT) + private String createBy; + + /** 创建时间 */ + @TableField(fill = FieldFill.INSERT) + private Date createTime; + + /** 更新者 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private String updateBy; + + /** 更新时间 */ + @TableField(fill = FieldFill.INSERT_UPDATE) + private Date updateTime; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeAddDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeAddDTO.java new file mode 100644 index 0000000..5eefc91 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeAddDTO.java @@ -0,0 +1,53 @@ +package com.ruoyi.ccdi.domain.dto; + +import jakarta.validation.constraints.*; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工信息新增 DTO + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployeeAddDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 姓名 */ + @NotBlank(message = "姓名不能为空") + @Size(max = 100, message = "姓名长度不能超过100个字符") + private String name; + + /** 员工ID(柜员号,7位数字) */ + @NotNull(message = "柜员号不能为空") + @Min(value = 1000000L, message = "柜员号必须为7位数字") + @Max(value = 9999999L, message = "柜员号必须为7位数字") + private Long employeeId; + + /** 所属部门ID */ + @NotNull(message = "所属部门不能为空") + private Long deptId; + + /** 身份证号 */ + @NotBlank(message = "身份证号不能为空") + @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确") + private String idCard; + + /** 电话 */ + @NotBlank(message = "电话不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "电话格式不正确") + private String phone; + + /** 入职时间 */ + private Date hireDate; + + /** 状态 */ + @NotBlank(message = "状态不能为空") + private String status; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeEditDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeEditDTO.java new file mode 100644 index 0000000..2cfcdb1 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeEditDTO.java @@ -0,0 +1,52 @@ +package com.ruoyi.ccdi.domain.dto; + +import jakarta.validation.constraints.NotBlank; +import jakarta.validation.constraints.NotNull; +import jakarta.validation.constraints.Pattern; +import jakarta.validation.constraints.Size; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工信息编辑 DTO + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployeeEditDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工ID */ + @NotNull(message = "员工ID不能为空") + private Long employeeId; + + /** 姓名 */ + @Size(max = 100, message = "姓名长度不能超过100个字符") + private String name; + + /** 所属部门ID */ + @NotNull(message = "所属部门不能为空") + private Long deptId; + + /** 身份证号 */ + @NotBlank(message = "身份证号不能为空") + @Pattern(regexp = "^[1-9]\\d{5}(18|19|20)\\d{2}(0[1-9]|1[0-2])(0[1-9]|[12]\\d|3[01])\\d{3}[0-9Xx]$", message = "身份证号格式不正确") + private String idCard; + + /** 电话 */ + @NotBlank(message = "电话不能为空") + @Pattern(regexp = "^1[3-9]\\d{9}$", message = "电话格式不正确") + private String phone; + + /** 入职时间 */ + private Date hireDate; + + /** 状态 */ + private String status; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeQueryDTO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeQueryDTO.java new file mode 100644 index 0000000..aacb3c2 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/dto/CcdiEmployeeQueryDTO.java @@ -0,0 +1,34 @@ +package com.ruoyi.ccdi.domain.dto; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; + +/** + * 员工信息查询 DTO + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployeeQueryDTO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 姓名(模糊查询) */ + private String name; + + /** 员工ID(柜员号,精确查询) */ + private Long employeeId; + + /** 所属部门ID */ + private Long deptId; + + /** 身份证号(模糊查询) */ + private String idCard; + + /** 状态 */ + private String status; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiEmployeeExcel.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiEmployeeExcel.java new file mode 100644 index 0000000..19a11d5 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/excel/CcdiEmployeeExcel.java @@ -0,0 +1,66 @@ +package com.ruoyi.ccdi.domain.excel; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import com.ruoyi.common.annotation.DictDropdown; +import com.ruoyi.common.annotation.Required; +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工信息Excel导入导出对象 + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployeeExcel implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 姓名 */ + @ExcelProperty(value = "姓名", index = 0) + @ColumnWidth(15) + @Required + private String name; + + /** 员工ID(柜员号) */ + @ExcelProperty(value = "柜员号", index = 1) + @ColumnWidth(15) + @Required + private Long employeeId; + + /** 所属部门ID */ + @ExcelProperty(value = "所属部门ID", index = 2) + @ColumnWidth(15) + @Required + private Long deptId; + + /** 身份证号 */ + @ExcelProperty(value = "身份证号", index = 3) + @ColumnWidth(20) + @Required + private String idCard; + + /** 电话 */ + @ExcelProperty(value = "电话", index = 4) + @ColumnWidth(15) + @Required + private String phone; + + /** 入职时间 */ + @ExcelProperty(value = "入职时间", index = 5) + @ColumnWidth(15) + private Date hireDate; + + /** 状态 */ + @ExcelProperty(value = "状态", index = 6) + @ColumnWidth(10) + @DictDropdown(dictType = "ccdi_employee_status") + @Required + private String status; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiEmployeeVO.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiEmployeeVO.java new file mode 100644 index 0000000..b46d7ca --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/domain/vo/CcdiEmployeeVO.java @@ -0,0 +1,59 @@ +package com.ruoyi.ccdi.domain.vo; + +import lombok.Data; + +import java.io.Serial; +import java.io.Serializable; +import java.util.Date; + +/** + * 员工信息 VO + * + * @author ruoyi + * @date 2026-01-28 + */ +@Data +public class CcdiEmployeeVO implements Serializable { + + @Serial + private static final long serialVersionUID = 1L; + + /** 员工ID(柜员号) */ + private Long employeeId; + + /** 姓名 */ + private String name; + + /** 所属部门ID */ + private Long deptId; + + /** 所属部门名称 */ + private String deptName; + + /** 身份证号 */ + private String idCard; + + /** 电话 */ + private String phone; + + /** 入职时间 */ + private Date hireDate; + + /** 状态 */ + private String status; + + /** 状态描述 */ + private String statusDesc; + + /** 创建时间 */ + private Date createTime; + + /** 创建者 */ + private String createBy; + + /** 更新时间 */ + private Date updateTime; + + /** 更新者 */ + private String updateBy; +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java new file mode 100644 index 0000000..2afc15a --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/mapper/CcdiEmployeeMapper.java @@ -0,0 +1,41 @@ +package com.ruoyi.ccdi.mapper; + +import com.baomidou.mybatisplus.core.mapper.BaseMapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.CcdiEmployee; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeQueryDTO; +import com.ruoyi.ccdi.domain.vo.CcdiEmployeeVO; +import org.apache.ibatis.annotations.Param; + +import java.util.List; + +/** + * 员工信息 数据层 + * + * @author ruoyi + * @date 2026-01-28 + */ +public interface CcdiEmployeeMapper extends BaseMapper { + + /** + * 分页查询员工列表(包含部门名称) + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工VO分页结果 + */ + Page selectEmployeePageWithDept(@Param("page") Page page, + @Param("query") CcdiEmployeeQueryDTO queryDTO); + + + + int insertOrUpdateBatch(@Param("list") List list); + + /** + * 批量插入员工信息 + * + * @param list 员工信息列表 + * @return 影响行数 + */ + int insertBatch(@Param("list") List list); +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeImportService.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeImportService.java new file mode 100644 index 0000000..acce4c4 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeImportService.java @@ -0,0 +1,40 @@ +package com.ruoyi.ccdi.service; + +import com.ruoyi.ccdi.domain.excel.CcdiEmployeeExcel; +import com.ruoyi.ccdi.domain.vo.ImportFailureVO; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; + +import java.util.List; + +/** + * @Author: wkc + * @CreateTime: 2026-02-06 + */ +public interface ICcdiEmployeeImportService { + + + + /** + * 异步导入员工数据 + * + * @param excelList Excel数据列表 + * @param isUpdateSupport 是否更新已存在的数据 + */ + void importEmployeeAsync(List excelList, Boolean isUpdateSupport, String taskId); + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态信息 + */ + ImportStatusVO getImportStatus(String taskId); + + /** + * 获取导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + List getImportFailures(String taskId); +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java new file mode 100644 index 0000000..a2cf196 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/ICcdiEmployeeService.java @@ -0,0 +1,86 @@ +package com.ruoyi.ccdi.service; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiEmployeeExcel; +import com.ruoyi.ccdi.domain.vo.CcdiEmployeeVO; + +import java.util.List; + +/** + * 员工信息 服务层 + * + * @author ruoyi + * @date 2026-01-28 + */ +public interface ICcdiEmployeeService { + + /** + * 查询员工列表 + * + * @param queryDTO 查询条件 + * @return 员工VO集合 + */ + List selectEmployeeList(CcdiEmployeeQueryDTO queryDTO); + + /** + * 分页查询员工列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工VO分页结果 + */ + Page selectEmployeePage(Page page, CcdiEmployeeQueryDTO queryDTO); + + /** + * 查询员工列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工Excel实体集合 + */ + List selectEmployeeListForExport(CcdiEmployeeQueryDTO queryDTO); + + /** + * 查询员工详情 + * + * @param employeeId 员工ID + * @return 员工VO + */ + CcdiEmployeeVO selectEmployeeById(Long employeeId); + + /** + * 新增员工 + * + * @param addDTO 新增DTO + * @return 结果 + */ + int insertEmployee(CcdiEmployeeAddDTO addDTO); + + /** + * 修改员工 + * + * @param editDTO 编辑DTO + * @return 结果 + */ + int updateEmployee(CcdiEmployeeEditDTO editDTO); + + /** + * 批量删除员工 + * + * @param employeeIds 需要删除的员工ID + * @return 结果 + */ + int deleteEmployeeByIds(Long[] employeeIds); + + /** + * 导入员工数据 + * + * @param excelList Excel实体列表 + * @param isUpdateSupport 是否更新支持 + * @return 结果 + */ + String importEmployee(List excelList, Boolean isUpdateSupport); + +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java new file mode 100644 index 0000000..a4a826e --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeImportServiceImpl.java @@ -0,0 +1,326 @@ +package com.ruoyi.ccdi.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.ruoyi.ccdi.domain.CcdiEmployee; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeAddDTO; +import com.ruoyi.ccdi.domain.excel.CcdiEmployeeExcel; +import com.ruoyi.ccdi.domain.vo.ImportFailureVO; +import com.ruoyi.ccdi.domain.vo.ImportResult; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.mapper.CcdiEmployeeMapper; +import com.ruoyi.ccdi.service.ICcdiEmployeeImportService; +import com.ruoyi.common.utils.IdCardUtil; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Service; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * @Author: wkc + * @CreateTime: 2026-02-06 + */ +@Service +@EnableAsync +public class CcdiEmployeeImportServiceImpl implements ICcdiEmployeeImportService { + + @Resource + private CcdiEmployeeMapper employeeMapper; + + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Async + public void importEmployeeAsync(List excelList, Boolean isUpdateSupport, String taskId) { + List newRecords = new ArrayList<>(); + List updateRecords = new ArrayList<>(); + List failures = new ArrayList<>(); + + // 批量查询已存在的柜员号和身份证号 + Set existingIds = getExistingEmployeeIds(excelList); + Set existingIdCards = getExistingIdCards(excelList); + + // 用于跟踪Excel文件内已处理的主键 + Set processedEmployeeIds = new HashSet<>(); + Set processedIdCards = new HashSet<>(); + + // 分类数据 + for (int i = 0; i < excelList.size(); i++) { + CcdiEmployeeExcel excel = excelList.get(i); + + try { + // 转换为AddDTO进行验证 + CcdiEmployeeAddDTO addDTO = new CcdiEmployeeAddDTO(); + BeanUtils.copyProperties(excel, addDTO); + + // 验证数据(支持更新模式) + validateEmployeeData(addDTO, isUpdateSupport, existingIds, existingIdCards); + + CcdiEmployee employee = new CcdiEmployee(); + BeanUtils.copyProperties(excel, employee); + + // 统一检查Excel内重复(更新和新增两个分支都需要检查) + if (processedEmployeeIds.contains(excel.getEmployeeId())) { + throw new RuntimeException(String.format("柜员号[%d]在导入文件中重复,已跳过此条记录", excel.getEmployeeId())); + } + if (StringUtils.isNotEmpty(excel.getIdCard()) && + processedIdCards.contains(excel.getIdCard())) { + throw new RuntimeException(String.format("身份证号[%s]在导入文件中重复,已跳过此条记录", excel.getIdCard())); + } + + // 检查柜员号是否在数据库中已存在 + if (existingIds.contains(excel.getEmployeeId())) { + // 柜员号已存在于数据库 + if (!isUpdateSupport) { + throw new RuntimeException("柜员号已存在且未启用更新支持"); + } + + // 通过检查,添加到更新列表 + updateRecords.add(employee); + + } else { + // 柜员号不存在,添加到新增列表 + newRecords.add(employee); + } + + // 统一标记为已处理(只有成功添加到列表后才会执行到这里) + if (excel.getEmployeeId() != null) { + processedEmployeeIds.add(excel.getEmployeeId()); + } + if (StringUtils.isNotEmpty(excel.getIdCard())) { + processedIdCards.add(excel.getIdCard()); + } + + + } catch (Exception e) { + ImportFailureVO failure = new ImportFailureVO(); + BeanUtils.copyProperties(excel, failure); + failure.setErrorMessage(e.getMessage()); + failures.add(failure); + } + } + + // 批量插入新数据 + if (!newRecords.isEmpty()) { + saveBatch(newRecords, 500); + } + + // 批量更新已有数据(先删除再插入) + if (!updateRecords.isEmpty() && isUpdateSupport) { + employeeMapper.insertOrUpdateBatch(updateRecords); + } + + // 保存失败记录到Redis + if (!failures.isEmpty()) { + String failuresKey = "import:employee:" + taskId + ":failures"; + redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(newRecords.size() + updateRecords.size()); + result.setFailureCount(failures.size()); + + // 更新最终状态 + String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; + updateImportStatus("employee", taskId, finalStatus, result); + } + + /** + * 获取导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + @Override + public List getImportFailures( String taskId) { + String key = "import:employee:" + taskId + ":failures"; + Object failuresObj = redisTemplate.opsForValue().get(key); + + if (failuresObj == null) { + return Collections.emptyList(); + } + + return JSON.parseArray(JSON.toJSONString(failuresObj), ImportFailureVO.class); + } + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态信息 + */ + @Override + public ImportStatusVO getImportStatus(String taskId) { + String key = "import:employee:" + taskId; + Boolean hasKey = redisTemplate.hasKey(key); + + if (Boolean.FALSE.equals(hasKey)) { + throw new RuntimeException("任务不存在或已过期"); + } + + Map statusMap = redisTemplate.opsForHash().entries(key); + + ImportStatusVO statusVO = new ImportStatusVO(); + statusVO.setTaskId((String) statusMap.get("taskId")); + statusVO.setStatus((String) statusMap.get("status")); + statusVO.setTotalCount((Integer) statusMap.get("totalCount")); + statusVO.setSuccessCount((Integer) statusMap.get("successCount")); + statusVO.setFailureCount((Integer) statusMap.get("failureCount")); + statusVO.setProgress((Integer) statusMap.get("progress")); + statusVO.setStartTime((Long) statusMap.get("startTime")); + statusVO.setEndTime((Long) statusMap.get("endTime")); + statusVO.setMessage((String) statusMap.get("message")); + + return statusVO; + } + + /** + * 更新导入状态 + */ + private void updateImportStatus(String taskType, String taskId, String status, ImportResult result) { + String key = "import:employee:" + taskId; + Map statusData = new HashMap<>(); + statusData.put("status", status); + statusData.put("successCount", result.getSuccessCount()); + statusData.put("failureCount", result.getFailureCount()); + statusData.put("progress", 100); + statusData.put("endTime", System.currentTimeMillis()); + + if ("SUCCESS".equals(status)) { + statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); + } else { + statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + } + + redisTemplate.opsForHash().putAll(key, statusData); + } + + /** + * 批量查询已存在的员工ID + */ + private Set getExistingEmployeeIds(List excelList) { + List employeeIds = excelList.stream() + .map(CcdiEmployeeExcel::getEmployeeId) + .filter(Objects::nonNull) + .collect(Collectors.toList()); + + if (employeeIds.isEmpty()) { + return Collections.emptySet(); + } + + List existingEmployees = employeeMapper.selectBatchIds(employeeIds); + return existingEmployees.stream() + .map(CcdiEmployee::getEmployeeId) + .collect(Collectors.toSet()); + } + + /** + * 批量查询数据库中已存在的身份证号 + * @param excelList Excel数据列表 + * @return 已存在的身份证号集合 + */ + private Set getExistingIdCards(List excelList) { + List idCards = excelList.stream() + .map(CcdiEmployeeExcel::getIdCard) + .filter(StringUtils::isNotEmpty) + .collect(Collectors.toList()); + + if (idCards.isEmpty()) { + return Collections.emptySet(); + } + + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.in(CcdiEmployee::getIdCard, idCards); + List existingEmployees = employeeMapper.selectList(wrapper); + + return existingEmployees.stream() + .map(CcdiEmployee::getIdCard) + .collect(Collectors.toSet()); + } + + /** + * 批量保存 + */ + private void saveBatch(List list, int batchSize) { + // 使用真正的批量插入,分批次执行以提高性能 + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + List subList = list.subList(i, end); + employeeMapper.insertBatch(subList); + } + } + + /** + * 验证员工数据 + * + * @param addDTO 新增DTO + * @param isUpdateSupport 是否支持更新 + * @param existingIds 已存在的员工ID集合(导入场景使用,传null表示单条新增) + * @param existingIdCards 已存在的身份证号集合(导入场景使用,传null表示单条新增) + */ + public void validateEmployeeData(CcdiEmployeeAddDTO addDTO, Boolean isUpdateSupport, Set existingIds, Set existingIdCards) { + // 验证必填字段 + if (StringUtils.isEmpty(addDTO.getName())) { + throw new RuntimeException("姓名不能为空"); + } + if (addDTO.getEmployeeId() == null) { + throw new RuntimeException("柜员号不能为空"); + } + if (addDTO.getDeptId() == null) { + throw new RuntimeException("所属部门不能为空"); + } + if (StringUtils.isEmpty(addDTO.getIdCard())) { + throw new RuntimeException("身份证号不能为空"); + } + if (StringUtils.isEmpty(addDTO.getPhone())) { + throw new RuntimeException("电话不能为空"); + } + if (StringUtils.isEmpty(addDTO.getStatus())) { + throw new RuntimeException("状态不能为空"); + } + + // 验证身份证号格式 + String idCardError = IdCardUtil.getErrorMessage(addDTO.getIdCard()); + if (idCardError != null) { + throw new RuntimeException(idCardError); + } + + // 单条新增场景:检查柜员号和身份证号唯一性 + if (existingIds == null) { + // 检查柜员号(employeeId)唯一性 + if (employeeMapper.selectById(addDTO.getEmployeeId()) != null) { + throw new RuntimeException("该柜员号已存在"); + } + + // 检查身份证号唯一性 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiEmployee::getIdCard, addDTO.getIdCard()); + if (employeeMapper.selectCount(wrapper) > 0) { + throw new RuntimeException("该身份证号已存在"); + } + } else { + // 导入场景:如果柜员号不存在,才检查身份证号唯一性 + if (!existingIds.contains(addDTO.getEmployeeId())) { + // 使用批量查询的结果检查身份证号唯一性 + if (existingIdCards != null && existingIdCards.contains(addDTO.getIdCard())) { + throw new RuntimeException("该身份证号已存在"); + } + } + } + + // 验证状态 + if (!"0".equals(addDTO.getStatus()) && !"1".equals(addDTO.getStatus())) { + throw new RuntimeException("状态只能填写'在职'或'离职'"); + } + } +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java new file mode 100644 index 0000000..47348a5 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiEmployeeServiceImpl.java @@ -0,0 +1,249 @@ +package com.ruoyi.ccdi.service.impl; + +import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.CcdiEmployee; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiEmployeeQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiEmployeeExcel; +import com.ruoyi.ccdi.domain.vo.CcdiEmployeeVO; +import com.ruoyi.ccdi.enums.EmployeeStatus; +import com.ruoyi.ccdi.mapper.CcdiEmployeeMapper; +import com.ruoyi.ccdi.service.ICcdiEmployeeImportService; +import com.ruoyi.ccdi.service.ICcdiEmployeeService; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.TimeUnit; + +/** + * 员工信息 服务层处理 + * + * @author ruoyi + * @date 2026-01-28 + */ +@Service +public class CcdiEmployeeServiceImpl implements ICcdiEmployeeService { + + @Resource + private CcdiEmployeeMapper employeeMapper; + + + @Resource + private ICcdiEmployeeImportService importAsyncService; + + @Resource + private RedisTemplate redisTemplate; + + /** + * 查询员工列表 + * + * @param queryDTO 查询条件 + * @return 员工VO集合 + */ + @Override + public List selectEmployeeList(CcdiEmployeeQueryDTO queryDTO) { + LambdaQueryWrapper wrapper = buildQueryWrapper(queryDTO); + List list = employeeMapper.selectList(wrapper); + List voList = new ArrayList<>(); + for (CcdiEmployee employee : list) { + voList.add(convertToVO(employee)); + } + return voList; + } + + /** + * 分页查询员工列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工VO分页结果 + */ + @Override + public Page selectEmployeePage(Page page, CcdiEmployeeQueryDTO queryDTO) { + // 使用关联查询获取部门名称 + Page voPage = new Page<>(page.getCurrent(), page.getSize()); + Page resultPage = employeeMapper.selectEmployeePageWithDept(voPage, queryDTO); + + // 设置状态描述 + resultPage.getRecords().forEach(vo -> + vo.setStatusDesc(EmployeeStatus.getDescByCode(vo.getStatus())) + ); + + return resultPage; + } + + /** + * 查询员工列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工Excel实体集合 + */ + @Override + public List selectEmployeeListForExport(CcdiEmployeeQueryDTO queryDTO) { + LambdaQueryWrapper wrapper = buildQueryWrapper(queryDTO); + List list = employeeMapper.selectList(wrapper); + return list.stream().map(employee -> { + CcdiEmployeeExcel excel = new CcdiEmployeeExcel(); + BeanUtils.copyProperties(employee, excel); + return excel; + }).toList(); + } + + /** + * 查询员工详情 + * + * @param employeeId 员工ID + * @return 员工VO + */ + @Override + public CcdiEmployeeVO selectEmployeeById(Long employeeId) { + CcdiEmployee employee = employeeMapper.selectById(employeeId); + return convertToVO(employee); + } + + /** + * 新增员工 + * + * @param addDTO 新增DTO + * @return 结果 + */ + @Override + @Transactional + public int insertEmployee(CcdiEmployeeAddDTO addDTO) { + // 检查柜员号(employeeId)唯一性 + if (employeeMapper.selectById(addDTO.getEmployeeId()) != null) { + throw new RuntimeException("该柜员号已存在"); + } + + // 检查身份证号唯一性 + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiEmployee::getIdCard, addDTO.getIdCard()); + if (employeeMapper.selectCount(wrapper) > 0) { + throw new RuntimeException("该身份证号已存在"); + } + + CcdiEmployee employee = new CcdiEmployee(); + BeanUtils.copyProperties(addDTO, employee); + int result = employeeMapper.insert(employee); + + return result; + } + + /** + * 修改员工 + * + * @param editDTO 编辑DTO + * @return 结果 + */ + @Override + @Transactional + public int updateEmployee(CcdiEmployeeEditDTO editDTO) { + // 检查身份证号唯一性(排除自己) + if (StringUtils.isNotEmpty(editDTO.getIdCard())) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.eq(CcdiEmployee::getIdCard, editDTO.getIdCard()) + .ne(CcdiEmployee::getEmployeeId, editDTO.getEmployeeId()); + if (employeeMapper.selectCount(wrapper) > 0) { + throw new RuntimeException("该身份证号已存在"); + } + } + + CcdiEmployee employee = new CcdiEmployee(); + BeanUtils.copyProperties(editDTO, employee); + int result = employeeMapper.updateById(employee); + + return result; + } + + /** + * 批量删除员工 + * + * @param employeeIds 需要删除的员工ID + * @return 结果 + */ + @Override + @Transactional + public int deleteEmployeeByIds(Long[] employeeIds) { + return employeeMapper.deleteBatchIds(List.of(employeeIds)); + } + + /** + * 导入员工数据 + * + * @param excelList Excel实体列表 + * @param isUpdateSupport 是否更新支持 + * @return 结果 + */ + @Override + @Transactional + public String importEmployee(List excelList, Boolean isUpdateSupport) { + String taskId = UUID.randomUUID().toString(); + long startTime = System.currentTimeMillis(); + + // 初始化Redis状态 + String statusKey = "import:employee:" + taskId; + Map 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); + + importAsyncService.importEmployeeAsync(excelList, isUpdateSupport, taskId); + + return taskId; + } + + + + /** + * 构建查询条件 + */ + private LambdaQueryWrapper buildQueryWrapper(CcdiEmployeeQueryDTO queryDTO) { + LambdaQueryWrapper wrapper = new LambdaQueryWrapper<>(); + wrapper.like(StringUtils.isNotEmpty(queryDTO.getName()), CcdiEmployee::getName, queryDTO.getName()) + .eq(queryDTO.getEmployeeId() != null, CcdiEmployee::getEmployeeId, queryDTO.getEmployeeId()) + .eq(queryDTO.getDeptId() != null, CcdiEmployee::getDeptId, queryDTO.getDeptId()) + .like(StringUtils.isNotEmpty(queryDTO.getIdCard()), CcdiEmployee::getIdCard, queryDTO.getIdCard()) + .eq(StringUtils.isNotEmpty(queryDTO.getStatus()), CcdiEmployee::getStatus, queryDTO.getStatus()) + .orderByDesc(CcdiEmployee::getCreateTime); + return wrapper; + } + + + + /** + * 转换为VO对象 + */ + public CcdiEmployeeVO convertToVO(CcdiEmployee employee) { + if (employee == null) { + return null; + } + + CcdiEmployeeVO vo = new CcdiEmployeeVO(); + BeanUtils.copyProperties(employee, vo); + vo.setStatusDesc(EmployeeStatus.getDescByCode(employee.getStatus())); + return vo; + } + + + + + + + + +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java new file mode 100644 index 0000000..fef61a2 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationImportServiceImpl.java @@ -0,0 +1,271 @@ +package com.ruoyi.ccdi.service.impl; + +import com.alibaba.fastjson2.JSON; +import com.ruoyi.ccdi.domain.CcdiStaffFmyRelation; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.ImportResult; +import com.ruoyi.ccdi.domain.vo.ImportStatusVO; +import com.ruoyi.ccdi.domain.vo.StaffFmyRelationImportFailureVO; +import com.ruoyi.ccdi.mapper.CcdiStaffFmyRelationMapper; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationImportService; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.scheduling.annotation.Async; +import org.springframework.scheduling.annotation.EnableAsync; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 员工亲属关系信息异步导入服务层处理 + * + * @author ruoyi + * @date 2026-02-09 + */ +@Service +@EnableAsync +public class CcdiStaffFmyRelationImportServiceImpl implements ICcdiStaffFmyRelationImportService { + + @Resource + private CcdiStaffFmyRelationMapper relationMapper; + + @Resource + private RedisTemplate redisTemplate; + + @Override + @Async + @Transactional + public void importRelationAsync(List excelList, String taskId, String userName) { + List newRecords = new ArrayList<>(); + List failures = new ArrayList<>(); + + // 批量查询已存在的person_id + relation_cert_no组合 + Set existingCombinations = getExistingCombinations(excelList); + + // 用于跟踪Excel文件内已处理的组合 + Set processedCombinations = new HashSet<>(); + + // 分类数据 + for (int i = 0; i < excelList.size(); i++) { + CcdiStaffFmyRelationExcel excel = excelList.get(i); + + try { + // 转换为AddDTO进行验证 + CcdiStaffFmyRelationAddDTO addDTO = new CcdiStaffFmyRelationAddDTO(); + BeanUtils.copyProperties(excel, addDTO); + + // 验证数据 + validateRelationData(addDTO, existingCombinations); + + String combinationKey = excel.getPersonId() + "_" + excel.getRelationCertNo(); + + if (existingCombinations.contains(combinationKey)) { + // person_id + relation_cert_no已存在,直接报错 + throw new RuntimeException(String.format("员工[%s]的亲属证件号[%s]已存在,请勿重复导入", + excel.getPersonId(), excel.getRelationCertNo())); + } else if (processedCombinations.contains(combinationKey)) { + // Excel文件内部重复 + throw new RuntimeException(String.format("员工[%s]的亲属证件号[%s]在导入文件中重复,已跳过此条记录", + excel.getPersonId(), excel.getRelationCertNo())); + } else { + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(excel, relation); + relation.setCreatedBy(userName); + relation.setUpdatedBy(userName); + relation.setIsEmpFamily(0); + relation.setIsCustFamily(0); + newRecords.add(relation); + processedCombinations.add(combinationKey); + } + + } catch (Exception e) { + StaffFmyRelationImportFailureVO failure = new StaffFmyRelationImportFailureVO(); + BeanUtils.copyProperties(excel, failure); + failure.setErrorMessage(e.getMessage()); + failures.add(failure); + } + } + + // 批量插入新数据 + if (!newRecords.isEmpty()) { + saveBatch(newRecords, 500); + } + + // 保存失败记录到Redis + if (!failures.isEmpty()) { + String failuresKey = "import:staffFmyRelation:" + taskId + ":failures"; + redisTemplate.opsForValue().set(failuresKey, failures, 7, TimeUnit.DAYS); + } + + ImportResult result = new ImportResult(); + result.setTotalCount(excelList.size()); + result.setSuccessCount(newRecords.size()); + result.setFailureCount(failures.size()); + + // 更新最终状态 + String finalStatus = result.getFailureCount() == 0 ? "SUCCESS" : "PARTIAL_SUCCESS"; + updateImportStatus(taskId, finalStatus, result); + } + + /** + * 获取导入失败记录 + * + * @param taskId 任务ID + * @return 失败记录列表 + */ + @Override + public List getImportFailures(String taskId) { + String key = "import:staffFmyRelation:" + taskId + ":failures"; + Object failuresObj = redisTemplate.opsForValue().get(key); + + if (failuresObj == null) { + return Collections.emptyList(); + } + + return JSON.parseArray(JSON.toJSONString(failuresObj), StaffFmyRelationImportFailureVO.class); + } + + /** + * 查询导入状态 + * + * @param taskId 任务ID + * @return 导入状态信息 + */ + @Override + public ImportStatusVO getImportStatus(String taskId) { + String key = "import:staffFmyRelation:" + taskId; + Boolean hasKey = redisTemplate.hasKey(key); + + if (Boolean.FALSE.equals(hasKey)) { + throw new RuntimeException("任务不存在或已过期"); + } + + Map statusMap = redisTemplate.opsForHash().entries(key); + + ImportStatusVO statusVO = new ImportStatusVO(); + statusVO.setTaskId((String) statusMap.get("taskId")); + statusVO.setStatus((String) statusMap.get("status")); + statusVO.setTotalCount((Integer) statusMap.get("totalCount")); + statusVO.setSuccessCount((Integer) statusMap.get("successCount")); + statusVO.setFailureCount((Integer) statusMap.get("failureCount")); + statusVO.setProgress((Integer) statusMap.get("progress")); + statusVO.setStartTime((Long) statusMap.get("startTime")); + statusVO.setEndTime((Long) statusMap.get("endTime")); + statusVO.setMessage((String) statusMap.get("message")); + + return statusVO; + } + + /** + * 更新导入状态 + */ + private void updateImportStatus(String taskId, String status, ImportResult result) { + String key = "import:staffFmyRelation:" + taskId; + Map statusData = new HashMap<>(); + statusData.put("status", status); + statusData.put("successCount", result.getSuccessCount()); + statusData.put("failureCount", result.getFailureCount()); + statusData.put("progress", 100); + statusData.put("endTime", System.currentTimeMillis()); + + if ("SUCCESS".equals(status)) { + statusData.put("message", "全部成功!共导入" + result.getTotalCount() + "条数据"); + } else { + statusData.put("message", "成功" + result.getSuccessCount() + "条,失败" + result.getFailureCount() + "条"); + } + + redisTemplate.opsForHash().putAll(key, statusData); + } + + /** + * 批量查询已存在的person_id + relation_cert_no组合 + */ + private Set getExistingCombinations(List excelList) { + // 提取所有person_id + Set personIds = excelList.stream() + .map(CcdiStaffFmyRelationExcel::getPersonId) + .filter(Objects::nonNull) + .collect(Collectors.toSet()); + + if (personIds.isEmpty()) { + return Collections.emptySet(); + } + + // 查询这些person_id的所有亲属关系 + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper wrapper = + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>(); + wrapper.in(CcdiStaffFmyRelation::getPersonId, personIds); + + List existingRelations = relationMapper.selectList(wrapper); + + return existingRelations.stream() + .map(r -> r.getPersonId() + "_" + r.getRelationCertNo()) + .collect(Collectors.toSet()); + } + + /** + * 批量保存 + */ + private void saveBatch(List list, int batchSize) { + for (int i = 0; i < list.size(); i += batchSize) { + int end = Math.min(i + batchSize, list.size()); + List subList = list.subList(i, end); + relationMapper.insertBatch(subList); + } + } + + /** + * 验证亲属关系数据 + * + * @param addDTO 新增DTO + * @param existingCombinations 已存在的组合集合 + */ + private void validateRelationData(CcdiStaffFmyRelationAddDTO addDTO, Set existingCombinations) { + // 验证必填字段 + if (StringUtils.isEmpty(addDTO.getPersonId())) { + throw new RuntimeException("员工身份证号不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationType())) { + throw new RuntimeException("关系类型不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationName())) { + throw new RuntimeException("关系人姓名不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationCertType())) { + throw new RuntimeException("证件类型不能为空"); + } + if (StringUtils.isEmpty(addDTO.getRelationCertNo())) { + throw new RuntimeException("证件号码不能为空"); + } + if (addDTO.getStatus() == null) { + throw new RuntimeException("状态不能为空"); + } + + // 验证身份证号格式 + if (!addDTO.getPersonId().matches("^\\d{17}[\\dXx]$")) { + throw new RuntimeException("员工身份证号格式不正确"); + } + + // 验证手机号格式(如果提供) + if (StringUtils.isNotEmpty(addDTO.getMobilePhone1()) && + !addDTO.getMobilePhone1().matches("^1[3-9]\\d{9}$")) { + throw new RuntimeException("手机号码1格式不正确"); + } + if (StringUtils.isNotEmpty(addDTO.getMobilePhone2()) && + !addDTO.getMobilePhone2().matches("^1[3-9]\\d{9}$")) { + throw new RuntimeException("手机号码2格式不正确"); + } + + // 验证性别格式(如果提供) + if (StringUtils.isNotEmpty(addDTO.getGender()) && + !addDTO.getGender().matches("^[MFO]$")) { + throw new RuntimeException("性别只能是M、F或O"); + } + } +} diff --git a/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java new file mode 100644 index 0000000..d498859 --- /dev/null +++ b/ruoyi-ccdi/src/main/java/com/ruoyi/ccdi/service/impl/CcdiStaffFmyRelationServiceImpl.java @@ -0,0 +1,231 @@ +package com.ruoyi.ccdi.service.impl; + +import com.baomidou.mybatisplus.extension.plugins.pagination.Page; +import com.ruoyi.ccdi.domain.CcdiStaffFmyRelation; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationAddDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationEditDTO; +import com.ruoyi.ccdi.domain.dto.CcdiStaffFmyRelationQueryDTO; +import com.ruoyi.ccdi.domain.excel.CcdiStaffFmyRelationExcel; +import com.ruoyi.ccdi.domain.vo.CcdiStaffFmyRelationVO; +import com.ruoyi.ccdi.mapper.CcdiStaffFmyRelationMapper; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationImportService; +import com.ruoyi.ccdi.service.ICcdiStaffFmyRelationService; +import com.ruoyi.common.utils.SecurityUtils; +import com.ruoyi.common.utils.StringUtils; +import jakarta.annotation.Resource; +import org.springframework.beans.BeanUtils; +import org.springframework.data.redis.core.RedisTemplate; +import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; + +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +/** + * 员工亲属关系信息 服务层处理 + * + * @author ruoyi + * @date 2026-02-09 + */ +@Service +public class CcdiStaffFmyRelationServiceImpl implements ICcdiStaffFmyRelationService { + + @Resource + private CcdiStaffFmyRelationMapper relationMapper; + + @Resource + private ICcdiStaffFmyRelationImportService relationImportService; + + @Resource + private RedisTemplate redisTemplate; + + /** + * 查询员工亲属关系列表 + * + * @param queryDTO 查询条件 + * @return 员工亲属关系VO集合 + */ + @Override + public java.util.List selectRelationList(CcdiStaffFmyRelationQueryDTO queryDTO) { + Page page = new Page<>(1, Integer.MAX_VALUE); + Page resultPage = relationMapper.selectRelationPage(page, queryDTO); + return resultPage.getRecords(); + } + + /** + * 分页查询员工亲属关系列表 + * + * @param page 分页对象 + * @param queryDTO 查询条件 + * @return 员工亲属关系VO分页结果 + */ + @Override + public Page selectRelationPage(Page page, CcdiStaffFmyRelationQueryDTO queryDTO) { + return relationMapper.selectRelationPage(page, queryDTO); + } + + /** + * 查询员工亲属关系列表(用于导出) + * + * @param queryDTO 查询条件 + * @return 员工亲属关系Excel实体集合 + */ + @Override + public java.util.List selectRelationListForExport(CcdiStaffFmyRelationQueryDTO queryDTO) { + return relationMapper.selectRelationListForExport(queryDTO).stream().map(vo -> { + CcdiStaffFmyRelationExcel excel = new CcdiStaffFmyRelationExcel(); + BeanUtils.copyProperties(vo, excel); + return excel; + }).collect(Collectors.toList()); + } + + /** + * 查询员工亲属关系详情 + * + * @param id 主键ID + * @return 员工亲属关系VO + */ + @Override + public CcdiStaffFmyRelationVO selectRelationById(Long id) { + return relationMapper.selectRelationById(id); + } + + /** + * 新增员工亲属关系 + * + * @param addDTO 新增DTO + * @return 结果 + */ + @Override + @Transactional + public int insertRelation(CcdiStaffFmyRelationAddDTO addDTO) { + // 检查唯一性:person_id + relation_cert_no + validateUniqueRelation(addDTO.getPersonId(), addDTO.getRelationCertNo(), null); + + // 验证person_id是否在ccdi_base_staff表中存在 + validatePersonIdExists(addDTO.getPersonId()); + + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(addDTO, relation); + int result = relationMapper.insert(relation); + + return result; + } + + /** + * 修改员工亲属关系 + * + * @param editDTO 编辑DTO + * @return 结果 + */ + @Override + @Transactional + public int updateRelation(CcdiStaffFmyRelationEditDTO editDTO) { + // 检查唯一性:person_id + relation_cert_no(排除自身) + validateUniqueRelation(editDTO.getPersonId(), editDTO.getRelationCertNo(), editDTO.getId()); + + // 验证person_id是否在ccdi_base_staff表中存在 + validatePersonIdExists(editDTO.getPersonId()); + + CcdiStaffFmyRelation relation = new CcdiStaffFmyRelation(); + BeanUtils.copyProperties(editDTO, relation); + int result = relationMapper.updateById(relation); + + return result; + } + + /** + * 批量删除员工亲属关系 + * + * @param ids 需要删除的主键ID数组 + * @return 结果 + */ + @Override + @Transactional + public int deleteRelationByIds(Long[] ids) { + return relationMapper.deleteBatchIds(java.util.List.of(ids)); + } + + /** + * 导入员工亲属关系数据(异步) + * + * @param excelList Excel实体列表 + * @return 任务ID + */ + @Override + @Transactional + public String importRelation(java.util.List excelList) { + if (StringUtils.isNull(excelList) || excelList.isEmpty()) { + throw new RuntimeException("至少需要一条数据"); + } + + // 生成任务ID + String taskId = UUID.randomUUID().toString(); + long startTime = System.currentTimeMillis(); + + // 获取当前用户名 + String userName = SecurityUtils.getUsername(); + + // 初始化Redis状态 + String statusKey = "import:staffFmyRelation:" + taskId; + Map statusData = new HashMap<>(); + statusData.put("taskId", taskId); + statusData.put("status", "PROCESSING"); + statusData.put("totalCount", excelList.size()); + statusData.put("successCount", 0); + statusData.put("failureCount", 0); + statusData.put("progress", 0); + statusData.put("startTime", startTime); + statusData.put("message", "正在处理..."); + + redisTemplate.opsForHash().putAll(statusKey, statusData); + redisTemplate.expire(statusKey, 7, TimeUnit.DAYS); + + // 调用异步导入服务 + relationImportService.importRelationAsync(excelList, taskId, userName); + + return taskId; + } + + /** + * 验证唯一性:person_id + relation_cert_no + * + * @param personId 员工身份证号 + * @param relationCertNo 亲属证件号 + * @param excludeId 排除的记录ID(修改时使用) + */ + private void validateUniqueRelation(String personId, String relationCertNo, Long excludeId) { + // 这里需要调用Mapper查询是否存在重复记录 + // 简化处理:使用LambdaQueryWrapper查询 + com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper wrapper = + new com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper<>(); + wrapper.eq(CcdiStaffFmyRelation::getPersonId, personId) + .eq(CcdiStaffFmyRelation::getRelationCertNo, relationCertNo); + + if (excludeId != null) { + wrapper.ne(CcdiStaffFmyRelation::getId, excludeId); + } + + CcdiStaffFmyRelation existing = relationMapper.selectOne(wrapper); + if (existing != null) { + throw new RuntimeException("该员工已存在相同亲属证件号的关系记录"); + } + } + + /** + * 验证person_id是否在ccdi_base_staff表中存在 + * + * @param personId 员工身份证号 + */ + private void validatePersonIdExists(String personId) { + // 这里需要查询ccdi_base_staff表 + // TODO: 注入CcdiBaseStaffMapper并查询 + // 暂时简化处理:如果personId格式正确则认为存在 + if (!personId.matches("^\\d{17}[\\dXx]$")) { + throw new RuntimeException("员工身份证号格式不正确"); + } + } +} diff --git a/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml b/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml new file mode 100644 index 0000000..a3d9917 --- /dev/null +++ b/ruoyi-ccdi/src/main/resources/mapper/ccdi/CcdiEmployeeMapper.xml @@ -0,0 +1,81 @@ + + + + + + + + + + + + + + + + + + + + + + + INSERT INTO ccdi_employee + (employee_id, name, dept_id, id_card, phone, hire_date, status, + create_time, create_by, update_by, update_time) + VALUES + + (#{item.employeeId}, #{item.name}, #{item.deptId}, #{item.idCard}, + #{item.phone}, #{item.hireDate}, #{item.status}, NOW(), + #{item.createBy}, #{item.updateBy}, NOW()) + + ON DUPLICATE KEY UPDATE + name = COALESCE(VALUES(name), name), + dept_id = COALESCE(VALUES(dept_id), dept_id), + phone = COALESCE(VALUES(phone), phone), + hire_date = COALESCE(VALUES(hire_date), hire_date), + status = COALESCE(VALUES(status), status), + update_by = COALESCE(VALUES(update_by), update_by), + update_time = NOW() + + + + + INSERT INTO ccdi_employee + (employee_id, name, dept_id, id_card, phone, hire_date, status, + create_time, create_by, update_by, update_time) + VALUES + + (#{item.employeeId}, #{item.name}, #{item.deptId}, #{item.idCard}, + #{item.phone}, #{item.hireDate}, #{item.status}, NOW(), + #{item.createBy}, #{item.updateBy}, NOW()) + + + +