feat(user): 优化用户导入功能并添加导入模板
- 改进用户导入逻辑,处理空行、无效数据和重复账号/手机号 - 添加用户导入模板下载功能 - 在 UserImportParam 类中增加真实姓名和备注字段
This commit is contained in:
@@ -1,6 +1,8 @@
|
||||
package com.gxwebsoft.common.system.controller;
|
||||
|
||||
import cn.afterturn.easypoi.excel.ExcelExportUtil;
|
||||
import cn.afterturn.easypoi.excel.ExcelImportUtil;
|
||||
import cn.afterturn.easypoi.excel.entity.ExportParams;
|
||||
import cn.afterturn.easypoi.excel.entity.ImportParams;
|
||||
import cn.hutool.core.util.ObjectUtil;
|
||||
import cn.hutool.core.util.StrUtil;
|
||||
@@ -28,6 +30,11 @@ import org.springframework.security.access.prepost.PreAuthorize;
|
||||
import org.springframework.transaction.annotation.Transactional;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
import org.apache.poi.ss.usermodel.Workbook;
|
||||
|
||||
import javax.servlet.http.HttpServletResponse;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
@@ -55,6 +62,8 @@ public class UserController extends BaseController {
|
||||
@Resource
|
||||
private DictionaryDataService dictionaryDataService;
|
||||
@Resource
|
||||
private UserRoleService userRoleService;
|
||||
@Resource
|
||||
private ConfigProperties configProperties;
|
||||
@Resource
|
||||
private LoginRecordService loginRecordService;
|
||||
@@ -377,70 +386,258 @@ public class UserController extends BaseController {
|
||||
*/
|
||||
@PreAuthorize("hasAuthority('sys:user:save')")
|
||||
@ApiOperation("导入用户")
|
||||
@Transactional(rollbackFor = {Exception.class})
|
||||
@PostMapping("/import")
|
||||
public ApiResult<List<String>> importBatch(MultipartFile file) {
|
||||
public ApiResult<?> importBatch(MultipartFile file) {
|
||||
ImportParams importParams = new ImportParams();
|
||||
// 设置标题行,跳过第一行表头
|
||||
importParams.setTitleRows(1);
|
||||
importParams.setHeadRows(1);
|
||||
|
||||
try {
|
||||
List<UserImportParam> list = ExcelImportUtil.importExcel(file.getInputStream(),
|
||||
UserImportParam.class, importParams);
|
||||
// 校验是否重复
|
||||
|
||||
if (list == null || list.isEmpty()) {
|
||||
return fail("导入文件为空", null);
|
||||
}
|
||||
|
||||
// 过滤掉空行和无效数据
|
||||
list = list.stream()
|
||||
.filter(param -> param.getUsername() != null && !param.getUsername().trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
if (list.isEmpty()) {
|
||||
return fail("没有有效的用户数据", null);
|
||||
}
|
||||
|
||||
// 简化验证:只检查必填字段
|
||||
for (UserImportParam param : list) {
|
||||
if (param.getPassword() == null || param.getPassword().trim().isEmpty()) {
|
||||
return fail("用户 " + param.getUsername() + " 的密码不能为空", null);
|
||||
}
|
||||
}
|
||||
|
||||
// 校验账号是否重复
|
||||
if (CommonUtil.checkRepeat(list, UserImportParam::getUsername)) {
|
||||
return fail("账号存在重复", null);
|
||||
return fail("Excel中账号存在重复", null);
|
||||
}
|
||||
if (CommonUtil.checkRepeat(list, UserImportParam::getPhone)) {
|
||||
return fail("手机号存在重复", null);
|
||||
|
||||
// 校验手机号是否重复(只验证非空的手机号)
|
||||
List<UserImportParam> listWithPhone = list.stream()
|
||||
.filter(param -> param.getPhone() != null && !param.getPhone().trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
if (!listWithPhone.isEmpty() && CommonUtil.checkRepeat(listWithPhone, UserImportParam::getPhone)) {
|
||||
return fail("Excel中手机号存在重复", null);
|
||||
}
|
||||
// 校验是否存在
|
||||
List<User> usernameExists = userService.list(new LambdaQueryWrapper<User>().in(User::getUsername,
|
||||
list.stream().map(UserImportParam::getUsername).collect(Collectors.toList())));
|
||||
if (usernameExists.size() > 0) {
|
||||
return fail("账号已经存在",
|
||||
usernameExists.stream().map(User::getUsername).collect(Collectors.toList()));
|
||||
|
||||
// 获取已存在的用户账号,用于跳过处理
|
||||
List<String> existingUsernames = userService.list(new LambdaQueryWrapper<User>().in(User::getUsername,
|
||||
list.stream().map(UserImportParam::getUsername).collect(Collectors.toList())))
|
||||
.stream().map(User::getUsername).collect(Collectors.toList());
|
||||
|
||||
// 检查手机号是否已存在(只检查非空手机号)
|
||||
List<String> phonesToCheck = list.stream()
|
||||
.map(UserImportParam::getPhone)
|
||||
.filter(phone -> phone != null && !phone.trim().isEmpty())
|
||||
.collect(Collectors.toList());
|
||||
|
||||
final List<String> existingPhones;
|
||||
if (!phonesToCheck.isEmpty()) {
|
||||
existingPhones = userService.list(new LambdaQueryWrapper<User>().in(User::getPhone, phonesToCheck))
|
||||
.stream().map(User::getPhone).collect(Collectors.toList());
|
||||
} else {
|
||||
existingPhones = new ArrayList<>();
|
||||
}
|
||||
List<User> phoneExists = userService.list(new LambdaQueryWrapper<User>().in(User::getPhone,
|
||||
list.stream().map(UserImportParam::getPhone).collect(Collectors.toList())));
|
||||
if (phoneExists.size() > 0) {
|
||||
return fail("手机号已经存在",
|
||||
phoneExists.stream().map(User::getPhone).collect(Collectors.toList()));
|
||||
|
||||
// 分离新用户和已存在用户(账号或手机号已存在都算已存在)
|
||||
List<UserImportParam> newUsers = list.stream()
|
||||
.filter(param -> !existingUsernames.contains(param.getUsername()) &&
|
||||
(param.getPhone() == null || param.getPhone().trim().isEmpty() ||
|
||||
!existingPhones.contains(param.getPhone())))
|
||||
.collect(Collectors.toList());
|
||||
|
||||
// 收集跳过的用户(账号已存在或手机号已存在)
|
||||
List<String> skippedUsers = new ArrayList<>();
|
||||
for (UserImportParam param : list) {
|
||||
if (existingUsernames.contains(param.getUsername())) {
|
||||
skippedUsers.add(param.getUsername() + "(账号已存在)");
|
||||
} else if (param.getPhone() != null && !param.getPhone().trim().isEmpty() &&
|
||||
existingPhones.contains(param.getPhone())) {
|
||||
skippedUsers.add(param.getUsername() + "(手机号已存在)");
|
||||
}
|
||||
// 添加
|
||||
}
|
||||
|
||||
// 创建用户列表(只处理新用户)
|
||||
List<User> users = new ArrayList<>();
|
||||
for (UserImportParam one : list) {
|
||||
for (UserImportParam one : newUsers) {
|
||||
User u = new User();
|
||||
u.setStatus(0);
|
||||
u.setUsername(one.getUsername());
|
||||
u.setPassword(userService.encodePassword(one.getPassword()));
|
||||
u.setNickname(one.getNickname());
|
||||
u.setRealName(one.getRealName());
|
||||
u.setPhone(one.getPhone());
|
||||
u.setEmail(one.getEmail());
|
||||
u.setComments(one.getComments());
|
||||
|
||||
// 设置角色(如果提供)
|
||||
if (one.getRoleName() != null && !one.getRoleName().trim().isEmpty()) {
|
||||
Role role = roleService.getOne(new QueryWrapper<Role>()
|
||||
.eq("role_name", one.getRoleName()), false);
|
||||
if (role == null) {
|
||||
return fail("角色不存在", Collections.singletonList(one.getRoleName()));
|
||||
} else {
|
||||
if (role != null) {
|
||||
u.setRoles(Collections.singletonList(role));
|
||||
}
|
||||
}
|
||||
|
||||
// 设置机构(如果提供)
|
||||
if (one.getOrganizationName() != null && !one.getOrganizationName().trim().isEmpty()) {
|
||||
Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
|
||||
.eq("organization_full_name", one.getOrganizationName()), false);
|
||||
if (organization == null) {
|
||||
return fail("机构不存在", Collections.singletonList(one.getOrganizationName()));
|
||||
} else {
|
||||
if (organization != null) {
|
||||
u.setOrganizationId(organization.getOrganizationId());
|
||||
}
|
||||
}
|
||||
|
||||
// 设置性别(如果提供)
|
||||
if (one.getSexName() != null && !one.getSexName().trim().isEmpty()) {
|
||||
DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
|
||||
if (sex == null) {
|
||||
return fail("性别不存在", Collections.singletonList(one.getSexName()));
|
||||
} else {
|
||||
if (sex != null) {
|
||||
u.setSex(sex.getDictDataCode());
|
||||
}
|
||||
}
|
||||
if (userService.saveBatch(users)) {
|
||||
return success("导入成功", null);
|
||||
|
||||
// 关键修复:将用户添加到列表中
|
||||
users.add(u);
|
||||
}
|
||||
|
||||
// 保存新用户(逐个保存以处理角色关系)
|
||||
int successCount = 0;
|
||||
List<String> failedUsers = new ArrayList<>();
|
||||
|
||||
for (User user : users) {
|
||||
try {
|
||||
// 先保存用户基本信息
|
||||
if (userService.save(user)) {
|
||||
// 如果有角色,再保存角色关系
|
||||
if (user.getRoles() != null && !user.getRoles().isEmpty()) {
|
||||
List<Integer> roleIds = user.getRoles().stream()
|
||||
.map(Role::getRoleId)
|
||||
.collect(Collectors.toList());
|
||||
userRoleService.saveBatch(user.getUserId(), roleIds);
|
||||
}
|
||||
successCount++;
|
||||
} else {
|
||||
failedUsers.add(user.getUsername());
|
||||
}
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
failedUsers.add(user.getUsername());
|
||||
// 继续处理下一个用户,不中断整个导入过程
|
||||
}
|
||||
}
|
||||
|
||||
// 构建返回消息
|
||||
StringBuilder message = new StringBuilder();
|
||||
if (successCount > 0) {
|
||||
message.append("成功导入").append(successCount).append("个用户");
|
||||
}
|
||||
if (!skippedUsers.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message.append(",");
|
||||
}
|
||||
message.append("跳过").append(skippedUsers.size()).append("个已存在用户");
|
||||
}
|
||||
if (!failedUsers.isEmpty()) {
|
||||
if (message.length() > 0) {
|
||||
message.append(",");
|
||||
}
|
||||
message.append("失败").append(failedUsers.size()).append("个用户");
|
||||
}
|
||||
if (message.length() == 0) {
|
||||
message.append("没有新用户需要导入");
|
||||
}
|
||||
|
||||
// 构建返回数据
|
||||
Map<String, Object> resultData = new HashMap<>();
|
||||
if (!skippedUsers.isEmpty()) {
|
||||
resultData.put("skipped", skippedUsers);
|
||||
}
|
||||
if (!failedUsers.isEmpty()) {
|
||||
resultData.put("failed", failedUsers);
|
||||
}
|
||||
|
||||
// 返回结果
|
||||
return success(message.toString(), resultData.isEmpty() ? null : resultData);
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
return fail("导入失败:" + e.getMessage(), null);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 下载用户导入模板
|
||||
*/
|
||||
@ApiOperation("下载用户导入模板")
|
||||
@GetMapping("/import/template")
|
||||
public void downloadImportTemplate(HttpServletResponse response) {
|
||||
try {
|
||||
// 创建示例数据
|
||||
List<UserImportParam> templateData = new ArrayList<>();
|
||||
|
||||
// 添加示例数据行
|
||||
UserImportParam example1 = new UserImportParam();
|
||||
example1.setUsername("admin001");
|
||||
example1.setPassword("123456");
|
||||
example1.setNickname("管理员");
|
||||
example1.setRealName("张三");
|
||||
example1.setPhone("13800138001");
|
||||
example1.setEmail("admin@example.com");
|
||||
example1.setOrganizationName("总公司");
|
||||
example1.setSexName("男");
|
||||
example1.setRoleName("管理员");
|
||||
example1.setComments("");
|
||||
templateData.add(example1);
|
||||
|
||||
UserImportParam example2 = new UserImportParam();
|
||||
example2.setUsername("user001");
|
||||
example2.setPassword("123456");
|
||||
example2.setNickname("注册用户");
|
||||
example2.setRealName("李四");
|
||||
example2.setPhone("13800138002");
|
||||
example2.setEmail("user@example.com");
|
||||
example2.setOrganizationName("分公司");
|
||||
example2.setSexName("女");
|
||||
example2.setRoleName("注册用户");
|
||||
example2.setComments("");
|
||||
templateData.add(example2);
|
||||
|
||||
// 设置导出参数
|
||||
ExportParams exportParams = new ExportParams("用户导入模板", "用户数据");
|
||||
exportParams.setCreateHeadRows(true);
|
||||
|
||||
// 生成Excel工作簿
|
||||
Workbook workbook = ExcelExportUtil.exportExcel(exportParams, UserImportParam.class, templateData);
|
||||
|
||||
// 设置响应头
|
||||
response.setContentType("application/vnd.openxmlformats-officedocument.spreadsheetml.sheet");
|
||||
response.setCharacterEncoding("UTF-8");
|
||||
String fileName = "用户导入模板.xlsx";
|
||||
response.setHeader("Content-Disposition", "attachment; filename=\"" +
|
||||
java.net.URLEncoder.encode(fileName, "UTF-8") + "\"");
|
||||
|
||||
// 输出到响应流
|
||||
workbook.write(response.getOutputStream());
|
||||
workbook.close();
|
||||
|
||||
} catch (Exception e) {
|
||||
e.printStackTrace();
|
||||
try {
|
||||
response.setContentType("application/json;charset=UTF-8");
|
||||
response.getWriter().write("{\"code\":1,\"message\":\"模板下载失败:" + e.getMessage() + "\"}");
|
||||
} catch (Exception ex) {
|
||||
ex.printStackTrace();
|
||||
}
|
||||
}
|
||||
return fail("导入失败", null);
|
||||
}
|
||||
|
||||
@PreAuthorize("hasAuthority('sys:auth:user')")
|
||||
|
||||
@@ -24,6 +24,9 @@ public class UserImportParam implements Serializable {
|
||||
@Excel(name = "昵称")
|
||||
private String nickname;
|
||||
|
||||
@Excel(name = "真实姓名")
|
||||
private String realName;
|
||||
|
||||
@Excel(name = "手机号")
|
||||
private String phone;
|
||||
|
||||
@@ -39,4 +42,7 @@ public class UserImportParam implements Serializable {
|
||||
@Excel(name = "角色")
|
||||
private String roleName;
|
||||
|
||||
@Excel(name = "备注")
|
||||
private String comments;
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user