diff --git a/health-watch/health-watch-api/src/main/java/com/renkang/watch/dto/WatchDeviceExcelV2.java b/health-watch/health-watch-api/src/main/java/com/renkang/watch/dto/WatchDeviceExcelV2.java new file mode 100644 index 0000000..73ce117 --- /dev/null +++ b/health-watch/health-watch-api/src/main/java/com/renkang/watch/dto/WatchDeviceExcelV2.java @@ -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; +} + diff --git a/health-watch/health-watch-api/src/main/java/com/renkang/watch/entity/WatchDevice.java b/health-watch/health-watch-api/src/main/java/com/renkang/watch/entity/WatchDevice.java index a6d0862..2992fa6 100644 --- a/health-watch/health-watch-api/src/main/java/com/renkang/watch/entity/WatchDevice.java +++ b/health-watch/health-watch-api/src/main/java/com/renkang/watch/entity/WatchDevice.java @@ -58,7 +58,7 @@ public class WatchDevice implements Serializable { */ @Excel(name = "绑定状态", width = 15) @TableField(exist = false) - @Schema(title = "绑定状态") + @Schema(title = "绑定状态(0-未绑定,1-已绑定)") private java.lang.String watchStatus; /** * 在线状态 diff --git a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/async/AsyncImportService.java b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/async/AsyncImportService.java index 5841ac4..9a45c05 100644 --- a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/async/AsyncImportService.java +++ b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/async/AsyncImportService.java @@ -1,22 +1,32 @@ package com.renkang.watch.async; +import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.StrUtil; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.read.listener.PageReadListener; import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper; import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper; import com.renkang.watch.dto.WatchDeviceExcel; +import com.renkang.watch.dto.WatchDeviceExcelV2; import com.renkang.watch.entity.WatchBindHis; import com.renkang.watch.entity.WatchDevice; import com.renkang.watch.entity.WatchDeviceSwitch; +import com.renkang.watch.mapper.WatchBindHisMapper; import com.renkang.watch.service.IWatchBindHisService; import com.renkang.watch.service.IWatchDeviceService; import com.renkang.watch.service.IWatchDeviceSwitchService; +import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; 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.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.export.ExportUtils; import org.jeecg.common.system.api.ISysBaseAPI; import org.jeecg.common.system.vo.LoginUser; 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.system.entity.SysDepart; 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.scheduling.annotation.Async; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import java.io.ByteArrayInputStream; +import java.io.FileInputStream; import java.io.IOException; -import java.util.ArrayList; -import java.util.Date; -import java.util.List; -import java.util.Map; +import java.io.InputStream; +import java.util.*; import java.util.stream.Collectors; /** @@ -46,23 +60,24 @@ import java.util.stream.Collectors; */ @Service @Slf4j +@RequiredArgsConstructor public class AsyncImportService { - - @Resource - private ISysBaseAPI sysBaseAPI; - - @Resource - private AsyncTaskExecutor taskExecutor; - - @Resource - private ISysCache sysCache; - - @Resource - private IWatchDeviceService watchDeviceService; - @Resource - private IWatchBindHisService watchBindHisService; - @Resource - private IWatchDeviceSwitchService watchDeviceSwitchService; + // 导入设备code + private final String WATCH_IMPORT_BIND_CODE = "importWatchBindCode"; + // 导入设备中 + private final String WATCH_IMPORT_BIND_DOING = "9"; + // 导入设备完成 + private final String WATCH_IMPORT_BIND_FINISH = "10"; + // 导入设备失败 + private final String WATCH_IMPORT_BIND_ERRCODE = "11"; + private final ISysBaseAPI sysBaseAPI; + private final AsyncTaskExecutor taskExecutor; + private final ISysCache sysCache; + private final IWatchDeviceService watchDeviceService; + private final IWatchBindHisService watchBindHisService; + private final IWatchDeviceSwitchService watchDeviceSwitchService; + private final AsyncTaskExecutor asyncTaskExecutor; + private final WatchBindHisMapper watchBindHisMapper; /** * 验证并填充开关列表switchList @@ -436,4 +451,197 @@ public class AsyncImportService { watchDevice.setBindDate(deviceExcel.getBindDate()); return watchDevice; } + + public Result importExcelBindDataV2(MultipartFile file) { + try { + //1.导入模板和空模板校验,模板不合法直接报错,省去所有后续操作 + ImportParams params = new ImportParams(); + params.setTitleRows(1); + params.setHeadRows(2); + List 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 importExceldataList,String fileName) { + LoginUser loginUser = GlobalUtils.getLoginUser(); + int total = importExceldataList.size(); //数据总条数 + int index = 0; //成功数量计数器 + String successFlagTrue = "导入成功"; + String successFlagFalse = "导入失败"; + //新增异步导入的日志 + CommonExportsInfo exportsLog = saveCommonExportsInfo(total); + try { + //根据手表编号反查设备信息以及根据工号反查用户信息 + List userList = sysBaseAPI.getUserListByWorkNoList(importExceldataList.stream().map(WatchDeviceExcelV2::getWorkNo).collect(Collectors.toList())); + //查手表信息的同时查出本次导入的用户是否有已经绑定的手表, 用于后续的重复绑定校验 + LambdaQueryWrapper 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 watchDeviceList = watchDeviceService.list(deviceWrapper); + Map deviceMap = watchDeviceList.stream() + .collect(Collectors.toMap(WatchDevice::getWatchNo,watchDevice->watchDevice)); + //已经绑定过手表的用户信息 + Map 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 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 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); + } } diff --git a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/controller/WatchDeviceController.java b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/controller/WatchDeviceController.java index 580e692..4a71e99 100644 --- a/health-watch/health-watch-biz/src/main/java/com/renkang/watch/controller/WatchDeviceController.java +++ b/health-watch/health-watch-biz/src/main/java/com/renkang/watch/controller/WatchDeviceController.java @@ -494,6 +494,11 @@ public class WatchDeviceController extends JeecgController importExcelBindDataV2(@Parameter(description = "文件", required = true) MultipartFile file){ + return asyncImportService.importExcelBindDataV2(file); + } /** * 获取已绑定员工id