feat(user): 优化用户导入功能并添加导入模板

- 改进用户导入逻辑,处理空行、无效数据和重复账号/手机号
- 添加用户导入模板下载功能
- 在 UserImportParam 类中增加真实姓名和备注字段
This commit is contained in:
2025-09-02 20:58:11 +08:00
parent 8529a826d7
commit a14c06449e
2 changed files with 242 additions and 39 deletions

View File

@@ -1,6 +1,8 @@
package com.gxwebsoft.common.system.controller; package com.gxwebsoft.common.system.controller;
import cn.afterturn.easypoi.excel.ExcelExportUtil;
import cn.afterturn.easypoi.excel.ExcelImportUtil; import cn.afterturn.easypoi.excel.ExcelImportUtil;
import cn.afterturn.easypoi.excel.entity.ExportParams;
import cn.afterturn.easypoi.excel.entity.ImportParams; import cn.afterturn.easypoi.excel.entity.ImportParams;
import cn.hutool.core.util.ObjectUtil; import cn.hutool.core.util.ObjectUtil;
import cn.hutool.core.util.StrUtil; 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.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile; 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.annotation.Resource;
import javax.servlet.http.HttpServletRequest; import javax.servlet.http.HttpServletRequest;
@@ -55,6 +62,8 @@ public class UserController extends BaseController {
@Resource @Resource
private DictionaryDataService dictionaryDataService; private DictionaryDataService dictionaryDataService;
@Resource @Resource
private UserRoleService userRoleService;
@Resource
private ConfigProperties configProperties; private ConfigProperties configProperties;
@Resource @Resource
private LoginRecordService loginRecordService; private LoginRecordService loginRecordService;
@@ -377,70 +386,258 @@ public class UserController extends BaseController {
*/ */
@PreAuthorize("hasAuthority('sys:user:save')") @PreAuthorize("hasAuthority('sys:user:save')")
@ApiOperation("导入用户") @ApiOperation("导入用户")
@Transactional(rollbackFor = {Exception.class})
@PostMapping("/import") @PostMapping("/import")
public ApiResult<List<String>> importBatch(MultipartFile file) { public ApiResult<?> importBatch(MultipartFile file) {
ImportParams importParams = new ImportParams(); ImportParams importParams = new ImportParams();
// 设置标题行,跳过第一行表头
importParams.setTitleRows(1);
importParams.setHeadRows(1);
try { try {
List<UserImportParam> list = ExcelImportUtil.importExcel(file.getInputStream(), List<UserImportParam> list = ExcelImportUtil.importExcel(file.getInputStream(),
UserImportParam.class, importParams); 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)) { 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()))); List<String> existingUsernames = userService.list(new LambdaQueryWrapper<User>().in(User::getUsername,
if (usernameExists.size() > 0) { list.stream().map(UserImportParam::getUsername).collect(Collectors.toList())))
return fail("账号已经存在", .stream().map(User::getUsername).collect(Collectors.toList());
usernameExists.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) { List<UserImportParam> newUsers = list.stream()
return fail("手机号已经存在", .filter(param -> !existingUsernames.contains(param.getUsername()) &&
phoneExists.stream().map(User::getPhone).collect(Collectors.toList())); (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<>(); List<User> users = new ArrayList<>();
for (UserImportParam one : list) { for (UserImportParam one : newUsers) {
User u = new User(); User u = new User();
u.setStatus(0); u.setStatus(0);
u.setUsername(one.getUsername()); u.setUsername(one.getUsername());
u.setPassword(userService.encodePassword(one.getPassword())); u.setPassword(userService.encodePassword(one.getPassword()));
u.setNickname(one.getNickname()); u.setNickname(one.getNickname());
u.setRealName(one.getRealName());
u.setPhone(one.getPhone()); u.setPhone(one.getPhone());
Role role = roleService.getOne(new QueryWrapper<Role>() u.setEmail(one.getEmail());
.eq("role_name", one.getRoleName()), false); u.setComments(one.getComments());
if (role == null) {
return fail("角色不存在", Collections.singletonList(one.getRoleName())); // 设置角色(如果提供)
} else { if (one.getRoleName() != null && !one.getRoleName().trim().isEmpty()) {
u.setRoles(Collections.singletonList(role)); Role role = roleService.getOne(new QueryWrapper<Role>()
.eq("role_name", one.getRoleName()), false);
if (role != null) {
u.setRoles(Collections.singletonList(role));
}
} }
Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
.eq("organization_full_name", one.getOrganizationName()), false); // 设置机构(如果提供)
if (organization == null) { if (one.getOrganizationName() != null && !one.getOrganizationName().trim().isEmpty()) {
return fail("机构不存在", Collections.singletonList(one.getOrganizationName())); Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
} else { .eq("organization_full_name", one.getOrganizationName()), false);
u.setOrganizationId(organization.getOrganizationId()); if (organization != null) {
u.setOrganizationId(organization.getOrganizationId());
}
} }
DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
if (sex == null) { // 设置性别(如果提供)
return fail("性别不存在", Collections.singletonList(one.getSexName())); if (one.getSexName() != null && !one.getSexName().trim().isEmpty()) {
} else { DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
u.setSex(sex.getDictDataCode()); if (sex != null) {
u.setSex(sex.getDictDataCode());
}
}
// 关键修复:将用户添加到列表中
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());
// 继续处理下一个用户,不中断整个导入过程
} }
} }
if (userService.saveBatch(users)) {
return success("导入成功", null); // 构建返回消息
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) { } catch (Exception e) {
e.printStackTrace(); 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')") @PreAuthorize("hasAuthority('sys:auth:user')")

View File

@@ -24,6 +24,9 @@ public class UserImportParam implements Serializable {
@Excel(name = "昵称") @Excel(name = "昵称")
private String nickname; private String nickname;
@Excel(name = "真实姓名")
private String realName;
@Excel(name = "手机号") @Excel(name = "手机号")
private String phone; private String phone;
@@ -39,4 +42,7 @@ public class UserImportParam implements Serializable {
@Excel(name = "角色") @Excel(name = "角色")
private String roleName; private String roleName;
@Excel(name = "备注")
private String comments;
} }