重构手表绑定导入接口

This commit is contained in:
feng
2025-12-23 16:10:50 +08:00
parent 492e44bb80
commit e45f571cd2
4 changed files with 264 additions and 21 deletions
@@ -0,0 +1,30 @@
package com.renkang.watch.dto;
import io.swagger.v3.oas.annotations.media.Schema;
import lombok.Data;
import org.jeecgframework.poi.excel.annotation.Excel;
@Data
@Schema(title = "手表绑定记录导入v2版本")
public class WatchDeviceExcelV2 {
@Excel(name = "手表SN编码(必填)", width = 30)
@Schema(title = "手表SN编码(必填)")
private String watchNo;
@Excel(name = "员工编号(必填)", width = 25)
@Schema(title = "员工编号(必填)")
private String workNo;
@Excel(name = "员工姓名", width = 20)
@Schema(title = "员工姓名")
private String realName;
@Excel(name = "处理结果", width = 20)
@Schema(title = "处理结果(导入成功,导入失败)")
private String successFlag;
@Excel(name = "备注信息", width = 50)
@Schema(title = "备注信息")
private String remark;
}
@@ -58,7 +58,7 @@ public class WatchDevice implements Serializable {
*/ */
@Excel(name = "绑定状态", width = 15) @Excel(name = "绑定状态", width = 15)
@TableField(exist = false) @TableField(exist = false)
@Schema(title = "绑定状态") @Schema(title = "绑定状态(0-未绑定,1-已绑定)")
private java.lang.String watchStatus; private java.lang.String watchStatus;
/** /**
* 在线状态 * 在线状态
@@ -1,22 +1,32 @@
package com.renkang.watch.async; package com.renkang.watch.async;
import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; import cn.hutool.core.util.StrUtil;
import com.alibaba.excel.EasyExcel; import com.alibaba.excel.EasyExcel;
import com.alibaba.excel.read.listener.PageReadListener; import com.alibaba.excel.read.listener.PageReadListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.renkang.watch.dto.WatchDeviceExcel; import com.renkang.watch.dto.WatchDeviceExcel;
import com.renkang.watch.dto.WatchDeviceExcelV2;
import com.renkang.watch.entity.WatchBindHis; import com.renkang.watch.entity.WatchBindHis;
import com.renkang.watch.entity.WatchDevice; import com.renkang.watch.entity.WatchDevice;
import com.renkang.watch.entity.WatchDeviceSwitch; import com.renkang.watch.entity.WatchDeviceSwitch;
import com.renkang.watch.mapper.WatchBindHisMapper;
import com.renkang.watch.service.IWatchBindHisService; import com.renkang.watch.service.IWatchBindHisService;
import com.renkang.watch.service.IWatchDeviceService; import com.renkang.watch.service.IWatchDeviceService;
import com.renkang.watch.service.IWatchDeviceSwitchService; import com.renkang.watch.service.IWatchDeviceSwitchService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.ObjectUtils;
import org.apache.poi.hssf.usermodel.HSSFWorkbook;
import org.apache.poi.ss.usermodel.Workbook;
import org.apache.shiro.SecurityUtils; import org.apache.shiro.SecurityUtils;
import org.jeecg.bean.request.ListUser; import org.jeecg.bean.request.ListUser;
import org.jeecg.common.api.vo.Result;
import org.jeecg.common.exception.ExceptionAssertsUtil;
import org.jeecg.common.exception.JeecgBootException; import org.jeecg.common.exception.JeecgBootException;
import org.jeecg.common.export.ExportUtils;
import org.jeecg.common.system.api.ISysBaseAPI; import org.jeecg.common.system.api.ISysBaseAPI;
import org.jeecg.common.system.vo.LoginUser; import org.jeecg.common.system.vo.LoginUser;
import org.jeecg.common.util.DateUtils; import org.jeecg.common.util.DateUtils;
@@ -26,18 +36,22 @@ import org.jeecg.modules.exports.entity.CommonExportsInfo;
import org.jeecg.modules.manager.ISysCache; import org.jeecg.modules.manager.ISysCache;
import org.jeecg.modules.system.entity.SysDepart; import org.jeecg.modules.system.entity.SysDepart;
import org.jeecg.modules.system.entity.SysUser; import org.jeecg.modules.system.entity.SysUser;
import org.jeecgframework.poi.excel.ExcelExportUtil;
import org.jeecgframework.poi.excel.ExcelImportUtil;
import org.jeecgframework.poi.excel.entity.ExportParams;
import org.jeecgframework.poi.excel.entity.ImportParams;
import org.springframework.core.task.AsyncTaskExecutor; import org.springframework.core.task.AsyncTaskExecutor;
import org.springframework.scheduling.annotation.Async; import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional; import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.multipart.MultipartFile;
import javax.annotation.Resource; import javax.annotation.Resource;
import java.io.ByteArrayInputStream; import java.io.ByteArrayInputStream;
import java.io.FileInputStream;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.io.InputStream;
import java.util.Date; import java.util.*;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors; import java.util.stream.Collectors;
/** /**
@@ -46,23 +60,24 @@ import java.util.stream.Collectors;
*/ */
@Service @Service
@Slf4j @Slf4j
@RequiredArgsConstructor
public class AsyncImportService { public class AsyncImportService {
// 导入设备code
@Resource private final String WATCH_IMPORT_BIND_CODE = "importWatchBindCode";
private ISysBaseAPI sysBaseAPI; // 导入设备中
private final String WATCH_IMPORT_BIND_DOING = "9";
@Resource // 导入设备完成
private AsyncTaskExecutor taskExecutor; private final String WATCH_IMPORT_BIND_FINISH = "10";
// 导入设备失败
@Resource private final String WATCH_IMPORT_BIND_ERRCODE = "11";
private ISysCache sysCache; private final ISysBaseAPI sysBaseAPI;
private final AsyncTaskExecutor taskExecutor;
@Resource private final ISysCache sysCache;
private IWatchDeviceService watchDeviceService; private final IWatchDeviceService watchDeviceService;
@Resource private final IWatchBindHisService watchBindHisService;
private IWatchBindHisService watchBindHisService; private final IWatchDeviceSwitchService watchDeviceSwitchService;
@Resource private final AsyncTaskExecutor asyncTaskExecutor;
private IWatchDeviceSwitchService watchDeviceSwitchService; private final WatchBindHisMapper watchBindHisMapper;
/** /**
* 验证并填充开关列表switchList * 验证并填充开关列表switchList
@@ -436,4 +451,197 @@ public class AsyncImportService {
watchDevice.setBindDate(deviceExcel.getBindDate()); watchDevice.setBindDate(deviceExcel.getBindDate());
return watchDevice; return watchDevice;
} }
public Result<String> importExcelBindDataV2(MultipartFile file) {
try {
//1.导入模板和空模板校验,模板不合法直接报错,省去所有后续操作
ImportParams params = new ImportParams();
params.setTitleRows(1);
params.setHeadRows(2);
List<WatchDeviceExcelV2> importExceldataList = ExcelImportUtil.importExcel(file.getInputStream(), WatchDeviceExcelV2.class, params);
if (ObjectUtils.isEmpty(importExceldataList) || ObjectUtils.isEmpty(importExceldataList.get(0))) {
ExceptionAssertsUtil.fail("数据解析失败,请检查导入模板是否正确或填充数据后再导入");
}
Boolean flag = sysBaseAPI.getFinishFlagByCode(WATCH_IMPORT_BIND_CODE, WATCH_IMPORT_BIND_DOING);
if (!flag) {
return Result.error("已存在执行中的导入任务,请稍后导入或点击【查看导入任务】查看任务导出进度!");
}
//异步执行数据导入操作
asyncTaskExecutor.execute(() -> importExcelBindDataV2Execute(importExceldataList,file.getOriginalFilename()));
} catch (Exception e) {
ExceptionAssertsUtil.fail("数据解析失败,请检查导入模板是否正确");
}
return Result.ok("导入任务启动成功,请点击【入库导入记录】查看导入详情!");
}
/**
* V2版本相较上一个版本的导入 最大的优化项是标记了每一项的导入结果, 如果导入失败会收集失败原因,并将结果追加到excel表中返回
* 其次V2版本中新增了 用户重复绑定的校验 以及优化了代码的查询逻辑
*/
private void importExcelBindDataV2Execute(List<WatchDeviceExcelV2> importExceldataList,String fileName) {
LoginUser loginUser = GlobalUtils.getLoginUser();
int total = importExceldataList.size(); //数据总条数
int index = 0; //成功数量计数器
String successFlagTrue = "导入成功";
String successFlagFalse = "导入失败";
//新增异步导入的日志
CommonExportsInfo exportsLog = saveCommonExportsInfo(total);
try {
//根据手表编号反查设备信息以及根据工号反查用户信息
List<SysUser> userList = sysBaseAPI.getUserListByWorkNoList(importExceldataList.stream().map(WatchDeviceExcelV2::getWorkNo).collect(Collectors.toList()));
//查手表信息的同时查出本次导入的用户是否有已经绑定的手表, 用于后续的重复绑定校验
LambdaQueryWrapper<WatchDevice> deviceWrapper = new LambdaQueryWrapper<>();
deviceWrapper.in(WatchDevice::getWatchNo,importExceldataList.stream().map(WatchDeviceExcelV2::getWatchNo).collect(Collectors.toList()));
if(ObjectUtil.isNotEmpty(userList)){
deviceWrapper.or().in(WatchDevice::getBindUserId,userList.stream().map(SysUser::getId).collect(Collectors.toList()));
}
List<WatchDevice> watchDeviceList = watchDeviceService.list(deviceWrapper);
Map<String,WatchDevice> deviceMap = watchDeviceList.stream()
.collect(Collectors.toMap(WatchDevice::getWatchNo,watchDevice->watchDevice));
//已经绑定过手表的用户信息
Map<String,WatchDevice> userDeviceMap = watchDeviceList.stream()
.filter(watchDevice -> ObjectUtil.isNotEmpty(watchDevice.getBindUserId()))
.collect(Collectors.toMap(WatchDevice::getBindUserId,watchDevice->watchDevice));
for (WatchDeviceExcelV2 excelVO : importExceldataList) {
try {
//处理时包含数据校验,包括手表编号校验, 员工工号校验, 重复绑定校验等
WatchDevice device = deviceMap.get(excelVO.getWatchNo());
SysUser user = userList.stream().filter(u -> u.getWorkNo().equals(excelVO.getWorkNo())).findFirst().orElse(null);
if(device == null){
excelVO.setSuccessFlag(successFlagFalse);
excelVO.setRemark("手表编号不存在或未入库");
continue;
}
if(user == null){
excelVO.setSuccessFlag(successFlagFalse);
excelVO.setRemark("员工工号不存在");
continue;
}
//判断用户有没有绑定手表,一个员工不能同时绑定两个手表
WatchDevice userDevice = userDeviceMap.get(user.getId());
if(userDevice != null && !excelVO.getWatchNo().equals(userDevice.getWatchNo())){
excelVO.setSuccessFlag(successFlagFalse);
excelVO.setRemark("员工已绑定手表,手表编号: " + userDevice.getWatchNo());
continue;
}
//判断手表是否是已经绑定状态,如果是已经绑定状态则解绑换人,如果未绑定则新增绑定记录
if(ObjectUtil.isNotEmpty(device.getBindUserId())){
if(!user.getId().equals(device.getBindUserId())){
device = deviceUnbindAndUpdate(device,user,loginUser);
userDeviceMap.put(user.getId(),device);
excelVO.setRemark("更换手表绑定人员");
}
excelVO.setSuccessFlag(successFlagTrue);
}else {
device = insertDeviceBind(device,user,loginUser);
userDeviceMap.put(user.getId(),device);
excelVO.setSuccessFlag(successFlagTrue);
}
index ++ ;
} catch (Exception e) {
excelVO.setSuccessFlag(successFlagFalse);
excelVO.setRemark("内部错误,请联系管理员");
log.error(e.getMessage(), e);
}
}
//上传附带错误信息记录的文件
String excelUrl = uploadBindDataExcel(importExceldataList,fileName);
exportsLog.setExportUrl(excelUrl);
//更新异步任务的状态,这里等所有的数据处理完之后只更新一次(没有必要处理一条更新一次)
String msg = index != total ? "导入成功" + index + "条,导入失败" + (total - index) +"" : total + "条数据导入完成";
logErrorAndUpdateExportInfo(exportsLog,WATCH_IMPORT_BIND_FINISH,msg,index,total);
} catch (Exception e) {
//这里只为捕获前置处理的异常,具体某条数据的导入异常由内层的catch()捕获
logErrorAndUpdateExportInfo(exportsLog, WATCH_IMPORT_BIND_ERRCODE, "系统内部错误,请联系管理员", index, total);
log.error(e.getMessage(), e);
}
}
private String uploadBindDataExcel(List<WatchDeviceExcelV2> importExceldataList,String fileName) {
String url = null;
try {
//这里将收集了上传结果的数据重新生成一个新的excel文件上传
ExportParams params = new ExportParams("设备绑定用户模板", "设备绑定用户模板");
HSSFWorkbook workbook = (HSSFWorkbook) ExcelExportUtil.exportExcel(params, WatchDeviceExcelV2.class, importExceldataList);
url = MyUploadUtil.upload(ExportUtils.workbookToInputStream(workbook), MyUploadUtil.IMPORT_BIZ, fileName + "");
} catch (Exception e) {
log.error(e.getMessage(), e);
}
return url;
}
private WatchDevice insertDeviceBind(WatchDevice device, SysUser user,LoginUser loginUser) {
//修改设备绑定状态
updateWatchDevice(device,user,loginUser);
//新增设备绑定记录
WatchBindHis watchBindHis = initWatchBindHis(device,user,loginUser);
watchDeviceService.saveOrUpdate(device);
watchBindHisService.save(watchBindHis);
return device;
}
private void updateWatchDevice(WatchDevice device,SysUser user,LoginUser loginUser){
device.setBindUserId(user.getId());
device.setBindDate(new Date());
device.setUpdateDate(new Date());
device.setUpdateBy(loginUser.getId());
}
private WatchBindHis initWatchBindHis(WatchDevice device,SysUser user,LoginUser loginUser){
WatchBindHis watchBindHis = new WatchBindHis();
watchBindHis.setWatchNo(device.getWatchNo());
watchBindHis.setBindDate(device.getBindDate());
watchBindHis.setCreateDate(new Date());
watchBindHis.setCreateBy(loginUser.getId());
watchBindHis.setBindUserId(user.getId());
if (user.getDepart()!=null && StrUtil.isNotBlank(user.getDepart().getOrgCode())){
watchBindHis.setDeptId(user.getDepart().getId());
watchBindHis.setOrgCode(user.getDepart().getOrgCode());
}
return watchBindHis;
}
/**
* 导入更换手表绑定人员 ,先解绑之前的用户再绑定新的用户
* @param device
* @param user
* @return
*/
public WatchDevice deviceUnbindAndUpdate(WatchDevice device,SysUser user,LoginUser loginUser) {
//查询之前的绑定记录,并将更新解绑时间
LambdaQueryWrapper<WatchBindHis> queryWrapper = new LambdaQueryWrapper<>();
queryWrapper.eq(WatchBindHis::getWatchNo,device.getWatchNo());
queryWrapper.isNull(WatchBindHis::getBindEndDate);
queryWrapper.orderByDesc(WatchBindHis::getCreateDate);
queryWrapper.last("limit 1");
//解绑
WatchBindHis updateHis = watchBindHisMapper.selectOne(queryWrapper);
//修改绑定用户
updateWatchDevice(device,user,loginUser);
//新增设备绑定记录
WatchBindHis watchBindHis = initWatchBindHis(device,user,loginUser);
if(null != updateHis){
updateHis.setBindEndDate(new Date());
updateHis.setUpdateBy(loginUser.getId());
updateHis.setUpdateDate(new Date());
watchBindHisMapper.updateById(updateHis);
}
watchDeviceService.saveOrUpdate(device);
watchBindHisService.save(watchBindHis);
return device;
}
private CommonExportsInfo saveCommonExportsInfo(int total) {
CommonExportsInfo info = new CommonExportsInfo();
info.setBatchNo(String.valueOf(System.currentTimeMillis()));
info.setTaskCode(WATCH_IMPORT_BIND_CODE);
info.setCreateBy(GlobalUtils.getLoginUser().getId());
info.setHandleMsg("0/"+total);
info.setCreateDate(new Date());
info.setExportMsg("设备绑定导入");
//数据导入中
info.setExportStatus(WATCH_IMPORT_BIND_DOING);
info.setHandleStartTime(new Date());
return sysBaseAPI.updateExportsInfo(info);
}
} }
@@ -494,6 +494,11 @@ public class WatchDeviceController extends JeecgController<WatchDevice, IWatchDe
return watchDeviceService.importExcelBindData(); return watchDeviceService.importExcelBindData();
} }
@Operation(summary = "导入绑定数据(V2版本)", description = "导入绑定数据(20251222重构版本)")
@RequestMapping(value = "/importExcelBindData/v2", consumes = "multipart/*", method = RequestMethod.POST, headers = "content-type=multipart/form-data")
public Result<String> importExcelBindDataV2(@Parameter(description = "文件", required = true) MultipartFile file){
return asyncImportService.importExcelBindDataV2(file);
}
/** /**
* 获取已绑定员工id * 获取已绑定员工id