feat(user): 优化用户导入功能并添加导入模板
- 改进用户导入逻辑,处理空行、无效数据和重复账号/手机号 - 添加用户导入模板下载功能 - 在 UserImportParam 类中增加真实姓名和备注字段
This commit is contained in:
@@ -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());
|
||||||
|
u.setEmail(one.getEmail());
|
||||||
|
u.setComments(one.getComments());
|
||||||
|
|
||||||
|
// 设置角色(如果提供)
|
||||||
|
if (one.getRoleName() != null && !one.getRoleName().trim().isEmpty()) {
|
||||||
Role role = roleService.getOne(new QueryWrapper<Role>()
|
Role role = roleService.getOne(new QueryWrapper<Role>()
|
||||||
.eq("role_name", one.getRoleName()), false);
|
.eq("role_name", one.getRoleName()), false);
|
||||||
if (role == null) {
|
if (role != null) {
|
||||||
return fail("角色不存在", Collections.singletonList(one.getRoleName()));
|
|
||||||
} else {
|
|
||||||
u.setRoles(Collections.singletonList(role));
|
u.setRoles(Collections.singletonList(role));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置机构(如果提供)
|
||||||
|
if (one.getOrganizationName() != null && !one.getOrganizationName().trim().isEmpty()) {
|
||||||
Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
|
Organization organization = organizationService.getOne(new QueryWrapper<Organization>()
|
||||||
.eq("organization_full_name", one.getOrganizationName()), false);
|
.eq("organization_full_name", one.getOrganizationName()), false);
|
||||||
if (organization == null) {
|
if (organization != null) {
|
||||||
return fail("机构不存在", Collections.singletonList(one.getOrganizationName()));
|
|
||||||
} else {
|
|
||||||
u.setOrganizationId(organization.getOrganizationId());
|
u.setOrganizationId(organization.getOrganizationId());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 设置性别(如果提供)
|
||||||
|
if (one.getSexName() != null && !one.getSexName().trim().isEmpty()) {
|
||||||
DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
|
DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName());
|
||||||
if (sex == null) {
|
if (sex != null) {
|
||||||
return fail("性别不存在", Collections.singletonList(one.getSexName()));
|
|
||||||
} else {
|
|
||||||
u.setSex(sex.getDictDataCode());
|
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) {
|
} catch (Exception e) {
|
||||||
e.printStackTrace();
|
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')")
|
@PreAuthorize("hasAuthority('sys:auth:user')")
|
||||||
|
|||||||
@@ -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;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user