refactor(system): 重构用户导入功能

- 新增 ExcelResult 类用于封装导入结果
- 修改 ExcelUtils 类,移除返回值类型
- 更新 UserController 中的用户导入接口,使用 ExcelResult 返回结果- 重构 UserImportListener 类,优化数据校验和处理逻辑
This commit is contained in:
Theo
2025-01-14 11:03:53 +08:00
parent 025a70b0cd
commit 9ff88c4a74
4 changed files with 155 additions and 75 deletions

View File

@@ -0,0 +1,43 @@
package com.youlai.boot.common.result;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
/**
* Excel导出响应结构体
*
* @author Theo
* @since 2025/1/14 11:46:08
*/
@Data
public class ExcelResult {
/**
* 响应码,来确定是否导入成功
*/
private String code;
/**
* 有效条数
*/
private Integer validCount;
/**
* 无效条数
*/
private Integer invalidCount;
/**
* 错误提示信息
*/
private List<String> messageList;
public ExcelResult() {
this.code = ResultCode.SUCCESS.getCode();
this.validCount = 0;
this.invalidCount = 0;
this.messageList = new ArrayList<>();
}
}

View File

@@ -1,7 +1,7 @@
package com.youlai.boot.common.util;
import com.alibaba.excel.EasyExcel;
import com.youlai.boot.common.base.BaseAnalysisEventListener;
import com.alibaba.excel.event.AnalysisEventListener;
import java.io.InputStream;
@@ -13,8 +13,7 @@ import java.io.InputStream;
*/
public class ExcelUtils {
public static <T> String importExcel(InputStream is, Class clazz, BaseAnalysisEventListener<T> listener) {
public static <T> void importExcel(InputStream is, Class clazz, AnalysisEventListener<T> listener) {
EasyExcel.read(is, clazz, listener).sheet().doRead();
return listener.getMsg();
}
}

View File

@@ -8,6 +8,7 @@ import com.youlai.boot.common.annotation.Log;
import com.youlai.boot.common.annotation.RepeatSubmit;
import com.youlai.boot.common.enums.LogModuleEnum;
import com.youlai.boot.common.model.Option;
import com.youlai.boot.common.result.ExcelResult;
import com.youlai.boot.common.result.PageResult;
import com.youlai.boot.common.result.Result;
import com.youlai.boot.common.util.ExcelUtils;
@@ -152,10 +153,10 @@ public class UserController {
@Operation(summary = "导入用户")
@PostMapping("/import")
@Log(value = "导入用户", module = LogModuleEnum.USER)
public Result<String> importUsers(MultipartFile file) throws IOException {
public Result<ExcelResult> importUsers(MultipartFile file) throws IOException {
UserImportListener listener = new UserImportListener();
String msg = ExcelUtils.importExcel(file.getInputStream(), UserImportDTO.class, listener);
return Result.success(msg);
ExcelUtils.importExcel(file.getInputStream(), UserImportDTO.class, listener);
return Result.success(listener.getExcelResult());
}
@Operation(summary = "导出用户")

View File

@@ -7,19 +7,22 @@ import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.spring.SpringUtil;
import cn.hutool.json.JSONUtil;
import com.alibaba.excel.context.AnalysisContext;
import com.alibaba.excel.event.AnalysisEventListener;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.youlai.boot.common.base.BaseAnalysisEventListener;
import com.youlai.boot.system.enums.DictCodeEnum;
import com.youlai.boot.system.model.entity.*;
import com.youlai.boot.common.base.IBaseEnum;
import com.youlai.boot.common.constant.SystemConstants;
import com.youlai.boot.common.enums.StatusEnum;
import com.youlai.boot.common.result.ExcelResult;
import com.youlai.boot.system.converter.UserConverter;
import com.youlai.boot.system.enums.DictCodeEnum;
import com.youlai.boot.system.model.dto.UserImportDTO;
import com.youlai.boot.system.model.entity.*;
import com.youlai.boot.system.service.*;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.stream.Collectors;
@@ -32,34 +35,45 @@ import java.util.stream.Collectors;
* @since 2022/4/10
*/
@Slf4j
public class UserImportListener extends BaseAnalysisEventListener<UserImportDTO> {
public class UserImportListener extends AnalysisEventListener<UserImportDTO> {
// 有效条数
private int validCount;
// 无效条数
private int invalidCount;
// 导入返回信息
StringBuilder msg = new StringBuilder();
/**
* excel导入结果实体
*/
@Getter
private ExcelResult excelResult;
private final UserService userService;
private final PasswordEncoder passwordEncoder;
private final UserConverter userConverter;
private final RoleService roleService;
private final UserRoleService userRoleService;
private final DeptService deptService;
private final DictDataService dictDataService;
private final List<Role> roleList;
private final List<Dept> deptList;
private final List<DictData> genderDataList;
/**
* 当前行
*/
private Integer currentRow = 1;
/**
* 构造方法
* <p>在构造方法中给需要查询的内容查询好,尽量避免每条数据查询一次</p>
*/
public UserImportListener() {
this.userService = SpringUtil.getBean(UserService.class);
this.passwordEncoder = SpringUtil.getBean(PasswordEncoder.class);
this.roleService = SpringUtil.getBean(RoleService.class);
this.userRoleService = SpringUtil.getBean(UserRoleService.class);
this.deptService = SpringUtil.getBean(DeptService.class);
this.dictDataService = SpringUtil.getBean(DictDataService.class);
this.userConverter = SpringUtil.getBean(UserConverter.class);
this.roleList = SpringUtil.getBean(RoleService.class)
.list(new LambdaQueryWrapper<Role>().eq(Role::getStatus, StatusEnum.ENABLE.getValue())
.select(Role::getId, Role::getCode));
this.deptList = SpringUtil.getBean(DeptService.class)
.list(new LambdaQueryWrapper<Dept>().select(Dept::getId, Dept::getCode));
this.genderDataList = SpringUtil.getBean(DictDataService.class)
.list(new LambdaQueryWrapper<DictData>().eq(DictData::getDictCode, DictCodeEnum.GENDER.getValue()));
this.excelResult = new ExcelResult();
}
/**
@@ -73,78 +87,56 @@ public class UserImportListener extends BaseAnalysisEventListener<UserImportDTO>
@Override
public void invoke(UserImportDTO userImportDTO, AnalysisContext analysisContext) {
log.info("解析到一条用户数据:{}", JSONUtil.toJsonStr(userImportDTO));
// 校验数据
StringBuilder validationMsg = new StringBuilder();
boolean validation = true;
String errorMsg = "" + currentRow + "行数据校验失败:";
String username = userImportDTO.getUsername();
if (StrUtil.isBlank(username)) {
validationMsg.append("用户名为空;");
errorMsg +="用户名为空;";
validation = false;
} else {
long count = userService.count(new LambdaQueryWrapper<User>().eq(User::getUsername, username));
if (count > 0) {
validationMsg.append("用户名已存在;");
errorMsg +="用户名已存在;";
validation = false;
}
}
String nickname = userImportDTO.getNickname();
if (StrUtil.isBlank(nickname)) {
validationMsg.append("用户昵称为空;");
errorMsg +="用户昵称为空;";
validation = false;
}
String mobile = userImportDTO.getMobile();
if (StrUtil.isBlank(mobile)) {
validationMsg.append("手机号码为空;");
errorMsg +="手机号码为空;";
validation = false;
} else {
if (!Validator.isMobile(mobile)) {
validationMsg.append("手机号码不正确;");
errorMsg +="手机号码不正确;";
validation = false;
}
}
if (validationMsg.isEmpty()) {
if (validation) {
// 校验通过,持久化至数据库
User entity = userConverter.toEntity(userImportDTO);
entity.setPassword(passwordEncoder.encode(SystemConstants.DEFAULT_PASSWORD)); // 默认密码
// 性别逆向翻译 根据字典标签得到字典值
String genderLabel = userImportDTO.getGenderLabel();
if (StrUtil.isNotBlank(genderLabel)) {
DictData dictData = dictDataService.getOne(new LambdaQueryWrapper<DictData>()
.eq(DictData::getDictCode, DictCodeEnum.GENDER.getValue())
.eq(DictData::getLabel, genderLabel)
.last("limit 1")
);
if (dictData != null) {
Integer genderValue = Convert.toInt(dictData.getValue(),0);
entity.setGender(genderValue);
}
}
entity.setGender(getGenderValue(genderLabel));
// 角色解析
String roleCodes = userImportDTO.getRoleCodes();
List<Long> roleIds = null;
if (StrUtil.isNotBlank(roleCodes)) {
roleIds = roleService.list(
new LambdaQueryWrapper<Role>()
.in(Role::getCode, (Object) roleCodes.split(","))
.eq(Role::getStatus, StatusEnum.ENABLE.getValue())
.select(Role::getId)
).stream()
.map(Role::getId)
.collect(Collectors.toList());
}
List<Long> roleIds = getRoleIds(roleCodes);
// 部门解析
String deptCode = userImportDTO.getDeptCode();
if (StrUtil.isNotBlank(deptCode)) {
Dept dept = deptService.getOne(new LambdaQueryWrapper<Dept>().eq(Dept::getCode, deptCode)
.select(Dept::getId)
);
if (dept != null) {
entity.setDeptId(dept.getId());
}
}
entity.setDeptId(getDeptId(deptCode));
boolean saveResult = userService.save(entity);
if (saveResult) {
validCount++;
excelResult.setValidCount(excelResult.getValidCount() + 1);
// 保存用户角色关联
if (CollectionUtil.isNotEmpty(roleIds)) {
List<UserRole> userRoles = roleIds.stream()
@@ -153,16 +145,67 @@ public class UserImportListener extends BaseAnalysisEventListener<UserImportDTO>
userRoleService.saveBatch(userRoles);
}
} else {
invalidCount++;
msg.append("").append(validCount + invalidCount).append("数据保存失败;<br/>");
excelResult.setInvalidCount(excelResult.getInvalidCount() + 1);
errorMsg += "数据保存失败;";
excelResult.getMessageList().add(errorMsg);
}
} else {
invalidCount++;
msg.append("").append(validCount + invalidCount).append("行数据校验失败:").append(validationMsg).append("<br/>");
excelResult.setInvalidCount(excelResult.getInvalidCount() + 1);
excelResult.getMessageList().add(errorMsg);
}
currentRow++;
}
/**
* 根据角色编码获取角色ID
*
* @param roleCodes 角色编码 逗号分隔
* @return 角色ID集合
*/
private List<Long> getRoleIds(String roleCodes) {
if (StrUtil.isNotBlank(roleCodes)) {
String[] split = roleCodes.split(",");
if (split.length > 0) {
List<Long> roleIds = new ArrayList<>();
for (String roleCode : split) {
this.roleList.stream().filter(r -> r.getCode().equals(roleCode))
.findFirst().ifPresent(role -> roleIds.add(role.getId()));
}
return roleIds.stream().distinct().toList();
}
}
return Collections.emptyList();
}
/**
* 根据部门编码获取部门ID
*
* @param deptCode 部门编码
* @return 部门ID
*/
private Long getDeptId(String deptCode) {
if (StrUtil.isNotBlank(deptCode)) {
return this.deptList.stream().filter(r -> r.getCode().equals(deptCode))
.findFirst().map(Dept::getId).orElse(null);
}
return null;
}
/**
* 根据性别标签获取性别值
*
* @param genderLabel 性别标签
* @return 性别值
*/
private Integer getGenderValue(String genderLabel) {
if (StrUtil.isNotBlank(genderLabel)) {
return this.genderDataList.stream().filter(r -> r.getLabel().equals(genderLabel))
.findFirst().map(DictData::getValue).map(Convert::toInt).orElse(null);
}
return null;
}
/**
* 所有数据解析完成会来调用
*/
@@ -171,10 +214,4 @@ public class UserImportListener extends BaseAnalysisEventListener<UserImportDTO>
log.info("所有数据解析完成!");
}
@Override
public String getMsg() {
// 总结信息
return StrUtil.format("导入用户结束:成功{}条,失败{}条;<br/>{}", validCount, invalidCount, msg);
}
}