diff --git a/src/main/java/com/gxwebsoft/common/system/controller/UserController.java b/src/main/java/com/gxwebsoft/common/system/controller/UserController.java index 2bc879b..0928d71 100644 --- a/src/main/java/com/gxwebsoft/common/system/controller/UserController.java +++ b/src/main/java/com/gxwebsoft/common/system/controller/UserController.java @@ -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> importBatch(MultipartFile file) { + public ApiResult importBatch(MultipartFile file) { ImportParams importParams = new ImportParams(); + // 设置标题行,跳过第一行表头 + importParams.setTitleRows(1); + importParams.setHeadRows(1); + try { List 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 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 usernameExists = userService.list(new LambdaQueryWrapper().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 existingUsernames = userService.list(new LambdaQueryWrapper().in(User::getUsername, + list.stream().map(UserImportParam::getUsername).collect(Collectors.toList()))) + .stream().map(User::getUsername).collect(Collectors.toList()); + + // 检查手机号是否已存在(只检查非空手机号) + List phonesToCheck = list.stream() + .map(UserImportParam::getPhone) + .filter(phone -> phone != null && !phone.trim().isEmpty()) + .collect(Collectors.toList()); + + final List existingPhones; + if (!phonesToCheck.isEmpty()) { + existingPhones = userService.list(new LambdaQueryWrapper().in(User::getPhone, phonesToCheck)) + .stream().map(User::getPhone).collect(Collectors.toList()); + } else { + existingPhones = new ArrayList<>(); } - List phoneExists = userService.list(new LambdaQueryWrapper().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 newUsers = list.stream() + .filter(param -> !existingUsernames.contains(param.getUsername()) && + (param.getPhone() == null || param.getPhone().trim().isEmpty() || + !existingPhones.contains(param.getPhone()))) + .collect(Collectors.toList()); + + // 收集跳过的用户(账号已存在或手机号已存在) + List 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 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()); - Role role = roleService.getOne(new QueryWrapper() - .eq("role_name", one.getRoleName()), false); - if (role == null) { - return fail("角色不存在", Collections.singletonList(one.getRoleName())); - } else { - u.setRoles(Collections.singletonList(role)); + u.setEmail(one.getEmail()); + u.setComments(one.getComments()); + + // 设置角色(如果提供) + if (one.getRoleName() != null && !one.getRoleName().trim().isEmpty()) { + Role role = roleService.getOne(new QueryWrapper() + .eq("role_name", one.getRoleName()), false); + if (role != null) { + u.setRoles(Collections.singletonList(role)); + } } - Organization organization = organizationService.getOne(new QueryWrapper() - .eq("organization_full_name", one.getOrganizationName()), false); - if (organization == null) { - return fail("机构不存在", Collections.singletonList(one.getOrganizationName())); - } else { - u.setOrganizationId(organization.getOrganizationId()); + + // 设置机构(如果提供) + if (one.getOrganizationName() != null && !one.getOrganizationName().trim().isEmpty()) { + Organization organization = organizationService.getOne(new QueryWrapper() + .eq("organization_full_name", one.getOrganizationName()), false); + if (organization != null) { + u.setOrganizationId(organization.getOrganizationId()); + } } - DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName()); - if (sex == null) { - return fail("性别不存在", Collections.singletonList(one.getSexName())); - } else { - u.setSex(sex.getDictDataCode()); + + // 设置性别(如果提供) + if (one.getSexName() != null && !one.getSexName().trim().isEmpty()) { + DictionaryData sex = dictionaryDataService.getByDictCodeAndName("sex", one.getSexName()); + if (sex != null) { + u.setSex(sex.getDictDataCode()); + } + } + + // 关键修复:将用户添加到列表中 + users.add(u); + } + + // 保存新用户(逐个保存以处理角色关系) + int successCount = 0; + List failedUsers = new ArrayList<>(); + + for (User user : users) { + try { + // 先保存用户基本信息 + if (userService.save(user)) { + // 如果有角色,再保存角色关系 + if (user.getRoles() != null && !user.getRoles().isEmpty()) { + List 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 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 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')") diff --git a/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java b/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java index 153a783..c73cdc9 100644 --- a/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java +++ b/src/main/java/com/gxwebsoft/common/system/param/UserImportParam.java @@ -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; + }