业务场景分析
用户上传一份Excel,服务器端校验Excel内数据的准确性,如有不准确的数据,对Excel对应标红并插入问题批注。返回给用户,如果数据全部校验合格,则进行插库处理。
业务流程
文件处理:
- 用户上传Excel
@ApiOperation(value = "用户信息上传", notes = " \n author:RenShiWei 2020/11/28")
@AnonymousPostMapping(value = "/uploadFile")
public Result<Object> uploadFile(@RequestPart("file") MultipartFile file, HttpServletResponse response) throws JSONException {
JSONObject jsonObject = businessService.fileHandle(file, response);
if (jsonObject.getBoolean("isSuccess")) {
return Result.success(ResultEnum.SUCCESS_UPLOAD, jsonObject.getString("fileName"));
} else {
return Result.error(ResultEnum.FAIL_UPLOAD, jsonObject.getString("fileName"));
}
}
- 上传并写入Excel文件
//上传文件
String fileName = fileService.upload(file);
File excelFile = fileService.loadFile(fileName);
//读取文件
ExcelWriter writer = ExcelUtil.getWriter(excelFile);
- 获取指定的工作视图及操作的sheet
//设置工作视窗
Workbook workbook = writer.getWorkbook();
//获取第一张图标
Sheet sheetAt = workbook.getSheetAt(0);
单元格处理
- 格式化单元格(主要清除批注和单元格背景颜色,因之后我们要对问题数据做标记)
- 在单元格数据范围内,为所有空单元格生成一个单元格(某一列是数字类型时,如果单元格内容为空,则提示无此单元格,对单元格进行操作时会报null,文本及其他类型单元格没有发现此异常)
//初始化单元格样式/注释/ 1
for (int a = 0; a < titleMap.size(); a++) {
for (int b = 1; b <= rowNum; b++) {
//所有空单元格设置有单元格
if (ObjectUtil.isNull(sheetAt.getRow(b).getCell(a))) {
sheetAt.getRow(b).createCell(a);
}
Cell cell = sheetAt.getRow(b).getCell(a);
//设置默认值和样式
if (ObjectUtil.isNotNull(cell)) {
CellStyle cellStyle = writer.createCellStyle();
cellStyle.setFillPattern(FillPatternType.NO_FILL);
cell.setCellStyle(cellStyle);
//移除注释
cell.removeCellComment();
}
}
}
- 封装错误信息描述的方法(主要为有问题的单元格设置批注的单元格背景颜色)
public void recordErrorCell(Cell cell, String error) {
errorNum++;
// 设置样式格式,设置发生错误时,要设置的单元格背景颜色
StyleSet styleError = writer.getStyleSet();
// 第二个参数表示是否也设置头部单元格背景
styleError.setBackgroundColor(IndexedColors.RED, false);
//设置默认值和样式
CellUtil.setCellValue(cell, cell, styleError, false);
//移除注释
if (ObjectUtil.isNotNull(cell)) {
//移除注解
cell.removeCellComment();
//设置注解:方式二
Sheet sheet = writer.getWorkbook().getSheetAt(0);
Drawing drawing = sheet.createDrawingPatriarch();
CreationHelper factory = sheet.getWorkbook().getCreationHelper();
ClientAnchor anchor = factory.createClientAnchor();
Comment comment = drawing.createCellComment(anchor);
RichTextString str = factory.createRichTextString(error);
comment.setString(str);
comment.setAuthor("Auto+");
cell.setCellComment(comment);
}
//设置注释 无效 这是hutool提供的批注设置工具类,会产生移除批注之后,不能再次设置的问题
// CellUtil.setComment(cell, error, null, null);
}
- 空值判断,如果单元格中某些数据要求必须存在(如姓名,手机号等),则进行空值判断。
//空值判断和数据校验 2
for (int a = 1; a <= rowNum; a++) {
Cell cellName = sheetAt.getRow(a).getCell(name);
Cell cellUserName = sheetAt.getRow(a).getCell(username);
Cell cellDept = sheetAt.getRow(a).getCell(dept);
Cell cellPhone = sheetAt.getRow(a).getCell(phone);
Cell cellEmail = sheetAt.getRow(a).getCell(email);
//手机号类型设置
if (!cellPhone.getCellTypeEnum().equals(CellType.STRING)) {
cellPhone.setCellType(CellType.STRING);
}
//空值判断
if (StrUtil.isEmpty(cellName.getStringCellValue())) {
recordErrorCell(cellName, "姓名不能为空");
}
if (StrUtil.isEmpty(cellUserName.getStringCellValue())) {
recordErrorCell(cellUserName, "姓名拼音不能为空");
} else {
//通过查库,校验用户名是否正确. 如果用户名存在,邮箱和手机号其一不一样,则数据错误
User user = userMapByUserName.get(cellUserName.getStringCellValue());
if (ObjectUtil.isNotNull(user)) {
if (!cellEmail.getStringCellValue().equals(user.getEmail()) && !cellPhone.getStringCellValue().equals(user.getPhone())) {
recordErrorCell(cellUserName, "姓名拼音与数据库中的姓名拼音重复,请手动标识姓名拼音或将其手机号或者邮箱号与数据库内容保持一致。");
}
}
}
if (StrUtil.isEmpty(cellDept.getStringCellValue())) {
recordErrorCell(cellDept, "所在单位不能为空");
}
if (StrUtil.isEmpty(cellPhone.getStringCellValue())) {
recordErrorCell(cellPhone, "手机号不能为空");
}
//数据正确性校验
//校验部门
if (StrUtil.isNotEmpty(cellDept.getStringCellValue()) && ObjectUtil.isNull(deptList.get(cellDept.getStringCellValue()))) {
recordErrorCell(cellDept, "该单位不存在:请注意空格,逗号或查看单位是否存在等");
} else {
//校验部门权限
if (!SecurityUtils.judgeOperationDataScope(deptList.get(cellDept.getStringCellValue()))) {
recordErrorCell(cellDept, "当前人员所在单位超出您所在权限范围,请更换其单位为您当前所在部门或所在单位的子单位");
}
}
}
- 特殊类型转字符串,如手机号,用户传入的数据可能是num类型(Excel中可设置),但是当你通过string类型去读取单元格的值时,会出现报错。(我这里当时遇到了num和string互相报错的问题,即用num接收,报不能接收string。用string接收爆不能接收num,所以我将此单元格直接转换成了字符串类型)
//手机号类型设置
if (!cellPhone.getCellTypeEnum().equals(CellType.STRING)) {
cellPhone.setCellType(CellType.STRING);
}
- 校验数值是否正确,如果传入的是字符串(部门名称),我们存库要存id(部门主键),则在此进行查库进行数据判断。(数据有无,权限等)
//校验部门
if (StrUtil.isNotEmpty(cellDept.getStringCellValue()) && ObjectUtil.isNull(deptList.get(cellDept.getStringCellValue()))) {
recordErrorCell(cellDept, "该单位不存在:请注意空格,逗号或查看单位是否存在等");
} else {
//校验部门权限
if (!SecurityUtils.judgeOperationDataScope(deptList.get(cellDept.getStringCellValue()))) {
recordErrorCell(cellDept, "当前人员所在单位超出您所在权限范围,请更换其单位为您当前所在部门或所在单位的子单位");
}
- 判断重复元素(传入的信息中某些内容不能重复,比如两个人,他们的身份证号不能一样).
//判断重复元素 3
for (int a = 1; a < rowNum; a++) {
Cell cellUserName = sheetAt.getRow(a).getCell(username);
Cell cellEmail = sheetAt.getRow(a).getCell(email);
for (int b = a + 1; b <= rowNum; b++) {
Cell cellUserNameTwo = sheetAt.getRow(b).getCell(username);
Cell cellEmailTwo = sheetAt.getRow(b).getCell(email);
Cell cellIdcardTwo = sheetAt.getRow(b).getCell(idcard);
//姓名拼音-用户名
if (StrUtil.isNotEmpty(cellUserName.getStringCellValue())
&& StrUtil.isNotEmpty(cellUserNameTwo.getStringCellValue())
&& cellUserName.getStringCellValue().equals(cellUserNameTwo.getStringCellValue())) {
recordErrorCell(cellUserName, "姓名拼音不能重复,请修改。如:张三/zhangsan01");
}
//邮箱
if (StrUtil.isNotEmpty(cellEmail.getStringCellValue())
&& StrUtil.isNotEmpty(cellEmailTwo.getStringCellValue())
&& cellEmail.getStringCellValue().equals(cellEmailTwo.getStringCellValue())) {
recordErrorCell(cellEmail, "邮箱不能重复,请修改。");
}
}
}
- 完成,关闭流
//关闭writer
writer.close();
生成错误报告
- 判断!有错误信息记录(这里我根据调用错误信息描述方法调用的次数判断)
if (errorNum > 0) {
//数据检验不正确
// downLoadErrorReport(response);
//生成返回对象
resultJson.put("isSuccess", false);
resultJson.put("message", "插入失败,请查看报告文件");
resultJson.put("fileName", fileName);
return resultJson;
} else {
ExcelReader reader = ExcelUtil.getReader(excelFile,0);
parseVarToNum(reader);
insertUploadUser(reader);
insertUploadLibeller(reader);
//生成返回对象
resultJson.put("isSuccess", true);
resultJson.put("message", "插入成功");
resultJson.put("fileName", fileName);
return resultJson;
}
- 将当前文件名称返回给前端,前端拿着对应的文件名,请求响应接口,下载错误报告
入库
- 判断!无错误记录产生
if (errorNum > 0) {
//数据检验不正确
// downLoadErrorReport(response);
//生成返回对象
resultJson.put("isSuccess", false);
resultJson.put("message", "插入失败,请查看报告文件");
resultJson.put("fileName", fileName);
return resultJson;
} else {
ExcelReader reader = ExcelUtil.getReader(excelFile,0);
parseVarToNum(reader);
insertUploadUser(reader);
insertUploadLibeller(reader);
//生成返回对象
resultJson.put("isSuccess", true);
resultJson.put("message", "插入成功");
resultJson.put("fileName", fileName);
return resultJson;
}
- 读取文件
ExcelReader reader = ExcelUtil.getReader(excelFile,0);
- 将某些汉字转换成id
public void parseVarToNum(ExcelReader reader) {
//将文字类型的cell转换成数字
//列数量
int rowCount = reader.getRowCount();
//被反映人数据转换
for (int b = 1; b < rowCount; b++) {
Cell cellNation = reader.getCell(nation, b);
Cell cellGender = reader.getCell(gender, b);
Cell cellRegion = reader.getCell(region, b);
Cell cellDept = reader.getCell(dept, b);
Cell cellJob = reader.getCell(job, b);
Cell cellDeptType = reader.getCell(deptType, b);
Cell cellRanks = reader.getCell(rank, b);
cellNation.setCellValue(nationList.get(cellNation.getStringCellValue()));
cellGender.setCellValue(genderList.get(cellGender.getStringCellValue()));
cellRegion.setCellValue(regionList.get(cellRegion.getStringCellValue()));
cellDept.setCellValue(deptList.get(cellDept.getStringCellValue()));
cellJob.setCellValue(jobList.get(cellJob.getStringCellValue()));
cellDeptType.setCellValue(deptTypeList.get(cellDeptType.getStringCellValue()));
cellRanks.setCellValue(rankList.get(cellRanks.getStringCellValue()));
}
}
- 做表格头部与实体类属性的映射(通过对表头起别名的方式或者设置表头单元格的值)
方式一:直接做映射(适用于标题头比较简单,没有换行及括号等)
有的话进行字符串比较判断时,容易出错,尤其是标题有换行
//用户对应标题
Map<String, String> userMap = new LinkedHashMap<>();
userMap.put("姓名", "nick_name");
userMap.put("姓名拼音", "username");
userMap.put("所在单位", "dept_id");
userMap.put("性别", "gender");
userMap.put("手机号", "phone");
userMap.put("Eamil", "email");
reader.setHeaderAlias(userMap);
方式二:获取标题单元格,逐一设置
name/nation,表示对应的标题所在的列队坐标
reader.getCell(name, 0).setCellValue("name");
reader.getCell(nation, 0).setCellValue("nation_id");
reader.getCell(gender, 0).setCellValue("gender");
reader.getCell(birth, 0).setCellValue("birth");
reader.getCell(idcard, 0).setCellValue("idcard");
reader.getCell(region, 0).setCellValue("region_id");
reader.getCell(dept, 0).setCellValue("dept");
reader.getCell(job, 0).setCellValue("job_id");
reader.getCell(deptType, 0).setCellValue("dept_type");
reader.getCell(rank, 0).setCellValue("ranks");
- 生成实体类对象,判断后插库
List<User> userList = reader.readAll(User.class);
List<TLibellee> libellerList = reader.readAll(TLibellee.class);
- 注意:进行插库时,最好不要仍空值,数据格式不正确的异常,因为在这我们已经做了表格与实体类的映射,如果仍异常,很难定位到到底是哪个单元格的错误。所以我们在单元格操作时,就把所有可能产生的数据错误处理好。保证映射后的数据是准确的。
单元格操作
//校验数据
String fileName = "C:\\Users\\DELL\\Desktop\\xf-suer.xlsx";
//读文件(用户获取文件数据)
ExcelReader reader = ExcelUtil.getReader(fileName);
//写文件(用于编辑文件数据)
ExcelWriter writer = ExcelUtil.getWriter(fileName);
//获取工作视图
Workbook workbook = writer.getWorkbook();
//获取第一张图表
Sheet sheetAt = workbook.getSheetAt(0);
//获取图表第一行
Row row = sheetAt.getRow(0);
//获取第一行第五个单元格
Cell cell = row.getCell(5);
//设置单元格注释
CellUtil.setComment(cell, "此处信息有误", null, null);
//删除单元格注释
cell.removeCellComment();
// 定义单元格样式 方式一:无误
StyleSet style = writer.getStyleSet();
// 第二个参数表示是否也设置头部单元格背景
style.setBackgroundColor(IndexedColors.RED, false);
//设置单元格的值,以及样式
CellUtil.setCellValue(cell, cell.getStringCellValue(), style, false);
//设置单元格的样式 方式二:FillPatternType为背景的填充类型。
// FillPatternType.SOLID_FOREGROUND为纯色填充(使用他时,不论设置什么背景色,都是黑色。所以未采用这种方式)
//FillPatternType.NO_FILL 默认,表示不填充。
CellStyle cellStyle = workbook.createCellStyle();
cellStyle.setFillPattern(FillPatternType.BIG_SPOTS);
cellStyle.setFillBackgroundColor(IndexedColors.RED.getIndex());
cell.setCellStyle(cellStyle);
//multipartfile转换成excelReater
MultipartFile file = new .....
InputStream inputStream = file.getInputStream();
File file1 = new File(file.getOriginalFilename());
BigExcelWriter bigWriter = ExcelUtil.getBigWriter(file1);
//设置日期类型
CellStyle cellStyle = sheetAt.getColumnStyle(sheetMessage.getBirth());
cellStyle.setDataFormat(sheetAt.getWorkbook().getCreationHelper().createDataFormat().getFormat("m/d/yy"));
cellBirth.setCellStyle(cellStyle);
坑:
excel为文本类型时,不输入值 cell不为空
excel为数字类型时,不输入值 cell为空