diff --git a/ruoyi-ui/src/api/system/user.js b/ruoyi-ui/src/api/system/user.js index b739d0ad0..7676e2bcb 100644 --- a/ruoyi-ui/src/api/system/user.js +++ b/ruoyi-ui/src/api/system/user.js @@ -122,7 +122,8 @@ export function uploadAvatar(data) { // 下载用户导入模板 export function importTemplate() { return request({ - url: '/system/user/importTemplate', - method: 'get' + url: '/system/user/get-import-template', + method: 'get', + responseType: 'blob' }) } diff --git a/ruoyi-ui/src/views/system/user/index.vue b/ruoyi-ui/src/views/system/user/index.vue index b9c6dcc06..af2856b34 100644 --- a/ruoyi-ui/src/views/system/user/index.vue +++ b/ruoyi-ui/src/views/system/user/index.vue @@ -391,7 +391,7 @@ export default { // 设置上传的请求头部 headers: { Authorization: "Bearer " + getToken() }, // 上传的地址 - url: process.env.VUE_APP_BASE_API + "/system/user/importData" + url: process.env.VUE_APP_BASE_API + '/api/' + "/system/user/import" }, // 查询参数 queryParams: { @@ -669,7 +669,7 @@ export default { /** 下载模板操作 */ importTemplate() { importTemplate().then(response => { - this.download(response.msg); + this.downloadExcel(response, '用户导入模板.xls'); }); }, // 文件上传中处理 @@ -681,7 +681,21 @@ export default { this.upload.open = false; this.upload.isUploading = false; this.$refs.upload.clearFiles(); - this.$alert(response.msg, "导入结果", { dangerouslyUseHTMLString: true }); + // 拼接提示语 + let data = response.data; + let text = '创建成功数量:' + data.createUsernames.length; + for (const username of data.createUsernames) { + text += '
    ' + username; + } + text += '
更新成功数量:' + data.updateUsernames.length; + for (const username of data.updateUsernames) { + text += '
    ' + username; + } + text += '
更新失败数量:' + Object.keys(data.failureUsernames).length; + for (const username in data.failureUsernames) { + text += '
    ' + username + ':' + data.failureUsernames[username]; + } + this.$alert(text, "导入结果", { dangerouslyUseHTMLString: true }); this.getList(); }, // 提交上传文件 diff --git a/src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java b/src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java index dec24db48..83c43ca2e 100644 --- a/src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java +++ b/src/main/java/cn/iocoder/dashboard/common/exception/ServiceException.java @@ -47,6 +47,7 @@ public final class ServiceException extends RuntimeException { return this; } + @Override public String getMessage() { return message; } diff --git a/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java b/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java index 19d6763c1..5ff255ddd 100644 --- a/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java +++ b/src/main/java/cn/iocoder/dashboard/common/exception/util/ServiceExceptionUtil.java @@ -103,11 +103,11 @@ public class ServiceExceptionUtil { if (i == 0) { return messagePattern; } else { - sbuf.append(messagePattern.substring(i, messagePattern.length())); + sbuf.append(messagePattern.substring(i)); return sbuf.toString(); } } else { - sbuf.append(messagePattern.substring(i, j)); + sbuf.append(messagePattern, i, j); sbuf.append(params[l]); i = j + 2; } @@ -115,7 +115,7 @@ public class ServiceExceptionUtil { if (messagePattern.indexOf("{}", i) != -1) { LOGGER.error("[doFormat][参数过少:错误码({})|错误内容({})|参数({})", code, messagePattern, params); } - sbuf.append(messagePattern.substring(i, messagePattern.length())); + sbuf.append(messagePattern.substring(i)); return sbuf.toString(); } diff --git a/src/main/java/cn/iocoder/dashboard/framework/excel/core/util/ExcelUtils.java b/src/main/java/cn/iocoder/dashboard/framework/excel/core/util/ExcelUtils.java index 34a84c28f..a673e383c 100644 --- a/src/main/java/cn/iocoder/dashboard/framework/excel/core/util/ExcelUtils.java +++ b/src/main/java/cn/iocoder/dashboard/framework/excel/core/util/ExcelUtils.java @@ -2,6 +2,7 @@ package cn.iocoder.dashboard.framework.excel.core.util; import com.alibaba.excel.EasyExcel; import com.alibaba.excel.write.style.column.LongestMatchColumnWidthStyleStrategy; +import org.springframework.web.multipart.MultipartFile; import javax.servlet.http.HttpServletResponse; import java.io.IOException; @@ -38,4 +39,10 @@ public class ExcelUtils { response.setContentType("application/vnd.ms-excel;charset=UTF-8"); } + public static List raed(MultipartFile file, Class head) throws IOException { + return EasyExcel.read(file.getInputStream(), head, null) + .autoCloseStream(false) // 不要自动关闭,交给 Servlet 自己处理 + .doReadAllSync(); + } + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java index 0d3088659..37cdb69de 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/SysUserController.java @@ -1,6 +1,7 @@ package cn.iocoder.dashboard.modules.system.controller.user; import cn.hutool.core.collection.CollUtil; +import cn.iocoder.dashboard.common.enums.CommonStatusEnum; import cn.iocoder.dashboard.common.pojo.CommonResult; import cn.iocoder.dashboard.common.pojo.PageResult; import cn.iocoder.dashboard.framework.excel.core.util.ExcelUtils; @@ -8,23 +9,23 @@ import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; import cn.iocoder.dashboard.modules.system.convert.user.SysUserConvert; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; +import cn.iocoder.dashboard.modules.system.enums.common.SysSexEnum; import cn.iocoder.dashboard.modules.system.service.dept.SysDeptService; import cn.iocoder.dashboard.modules.system.service.user.SysUserService; import cn.iocoder.dashboard.util.collection.CollectionUtils; import cn.iocoder.dashboard.util.collection.MapUtils; import io.swagger.annotations.Api; import io.swagger.annotations.ApiImplicitParam; +import io.swagger.annotations.ApiImplicitParams; import io.swagger.annotations.ApiOperation; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; +import org.springframework.web.multipart.MultipartFile; import javax.annotation.Resource; import javax.servlet.http.HttpServletResponse; import java.io.IOException; -import java.util.ArrayList; -import java.util.Collection; -import java.util.List; -import java.util.Map; +import java.util.*; import static cn.iocoder.dashboard.common.pojo.CommonResult.success; @@ -145,35 +146,35 @@ public class SysUserController { SysUserExcelVO.class, excelUsers); } -// @Log(title = "用户管理", businessType = BusinessType.EXPORT) -// @PreAuthorize("@ss.hasPermi('system:user:export')") -// @GetMapping("/export") -// public AjaxResult export(SysUser user) -// { -// List list = userService.selectUserList(user); -// ExcelUtil util = new ExcelUtil(SysUser.class); -// return util.exportExcel(list, "用户数据"); -// } -// + @ApiOperation("获得导入用户模板") + @GetMapping("/get-import-template") + public void importTemplate(HttpServletResponse response) throws IOException { + // 手动创建导出 demo + List list = Arrays.asList( + SysUserImportExcelVO.builder().username("yudao").deptId(1L).email("yudao@iocoder.cn").mobile("15601691300") + .nickname("芋道").status(CommonStatusEnum.ENABLE.getStatus()).sex(SysSexEnum.MALE.getSEX()).build(), + SysUserImportExcelVO.builder().username("yuanma").deptId(2L).email("yuanma@iocoder.cn").mobile("15601701300") + .nickname("源码").status(CommonStatusEnum.DISABLE.getStatus()).sex(SysSexEnum.FEMALE.getSEX()).build() + ); + + // 输出 + ExcelUtils.write(response, "用户导入模板.xls", "用户列表", + SysUserImportExcelVO.class, list); + + } + + @ApiOperation("导入用户") + @ApiImplicitParams({ + @ApiImplicitParam(name = "path", value = "Excel 文件", required = true, dataTypeClass = MultipartFile.class), + @ApiImplicitParam(name = "update-support", value = "是否支持更新,默认为 false", example = "true", dataTypeClass = Long.class) + }) + @PostMapping("/import") // @Log(title = "用户管理", businessType = BusinessType.IMPORT) // @PreAuthorize("@ss.hasPermi('system:user:import')") -// @PostMapping("/importData") -// public AjaxResult importData(MultipartFile file, boolean updateSupport) throws Exception -// { -// ExcelUtil util = new ExcelUtil(SysUser.class); -// List userList = util.importExcel(file.getInputStream()); -// LoginUser loginUser = tokenService.getLoginUser(ServletUtils.getRequest()); -// String operName = loginUser.getUsername(); -// String message = userService.importUser(userList, updateSupport, operName); -// return AjaxResult.success(message); -// } -// -// @GetMapping("/importTemplate") -// public AjaxResult importTemplate() -// { -// ExcelUtil util = new ExcelUtil(SysUser.class); -// return util.importTemplateExcel("用户数据"); -// } -// + public CommonResult importExcel(@RequestParam("file") MultipartFile file, + @RequestParam(value = "updateSupport", required = false, defaultValue = "false") Boolean updateSupport) throws Exception { + List list = ExcelUtils.raed(file, SysUserImportExcelVO.class); + return success(userService.importUsers(list, updateSupport)); + } } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportExcelVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportExcelVO.java index a90ad49e6..d03ad0ed6 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportExcelVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportExcelVO.java @@ -4,12 +4,18 @@ import cn.iocoder.dashboard.framework.excel.core.annotations.DictFormat; import cn.iocoder.dashboard.framework.excel.core.convert.DictConvert; import cn.iocoder.dashboard.modules.system.enums.dict.DictTypeEnum; import com.alibaba.excel.annotation.ExcelProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; import lombok.Data; +import lombok.NoArgsConstructor; /** * 用户 Excel 导入 VO */ @Data +@Builder +@AllArgsConstructor +@NoArgsConstructor public class SysUserImportExcelVO { @ExcelProperty("登录名称") @@ -29,7 +35,7 @@ public class SysUserImportExcelVO { @ExcelProperty(value = "用户性别", converter = DictConvert.class) @DictFormat(DictTypeEnum.SYS_USER_SEX) - private String sex; + private Integer sex; @ExcelProperty(value = "账号状态", converter = DictConvert.class) @DictFormat(DictTypeEnum.SYS_COMMON_STATUS) diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportRespVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportRespVO.java new file mode 100644 index 000000000..551eeb150 --- /dev/null +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserImportRespVO.java @@ -0,0 +1,25 @@ +package cn.iocoder.dashboard.modules.system.controller.user.vo.user; + +import io.swagger.annotations.ApiModel; +import io.swagger.annotations.ApiModelProperty; +import lombok.Builder; +import lombok.Data; + +import java.util.List; +import java.util.Map; + +@ApiModel("用户导入 Response VO") +@Data +@Builder +public class SysUserImportRespVO { + + @ApiModelProperty(value = "创建成功的用户名数组", required = true) + private List createUsernames; + + @ApiModelProperty(value = "更新成功的用户名数组", required = true) + private List updateUsernames; + + @ApiModelProperty(value = "导入失败的用户集合", required = true, notes = "key 为用户名,value 为失败原因") + private Map failureUsernames; + +} diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserRespVO.java b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserRespVO.java index aa995fb92..fa2335139 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserRespVO.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/controller/user/vo/user/SysUserRespVO.java @@ -2,10 +2,7 @@ package cn.iocoder.dashboard.modules.system.controller.user.vo.user; import io.swagger.annotations.ApiModel; import io.swagger.annotations.ApiModelProperty; -import lombok.AllArgsConstructor; -import lombok.Data; -import lombok.EqualsAndHashCode; -import lombok.NoArgsConstructor; +import lombok.*; import java.util.Date; diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/convert/user/SysUserConvert.java b/src/main/java/cn/iocoder/dashboard/modules/system/convert/user/SysUserConvert.java index ea5b87f79..0ba3e5973 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/convert/user/SysUserConvert.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/convert/user/SysUserConvert.java @@ -1,10 +1,7 @@ package cn.iocoder.dashboard.modules.system.convert.user; import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExcelVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageItemRespVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -30,4 +27,6 @@ public interface SysUserConvert { SysUserExcelVO convert02(SysUserDO bean); + SysUserDO convert(SysUserImportExcelVO bean); + } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java b/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java index 4edbf5d98..0bd0a4a56 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/enums/SysErrorCodeConstants.java @@ -37,6 +37,7 @@ public interface SysErrorCodeConstants { ErrorCode USER_MOBILE_EXISTS = new ErrorCode(1002004001, "手机号已经存在"); ErrorCode USER_EMAIL_EXISTS = new ErrorCode(1002004002, "邮箱已经存在"); ErrorCode USER_NOT_EXISTS = new ErrorCode(1002004003, "用户不存在"); + ErrorCode USER_IMPORT_LIST_IS_EMPTY = new ErrorCode(1002004004, "导入用户数据不能为空!"); // ========== 部门模块 1002005000 ========== ErrorCode DEPT_NAME_DUPLICATE = new ErrorCode(1002004001, "已经存在该名字的部门"); diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java index 897474864..771ffeb9f 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserService.java @@ -1,10 +1,7 @@ package cn.iocoder.dashboard.modules.system.service.user; import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.user.SysUserDO; import java.util.List; @@ -86,6 +83,15 @@ public interface SysUserService { */ void updateUserStatus(Long id, Integer status); + /** + * 批量导入用户 + * + * @param importUsers 导入用户列表 + * @param isUpdateSupport 是否支持更新 + * @return 导入结果 + */ + SysUserImportRespVO importUsers(List importUsers, boolean isUpdateSupport); + // // /** // * 修改用户基本信息 @@ -121,14 +127,4 @@ public interface SysUserService { // */ // public int resetUserPwd(String userName, String password); -// -// /** -// * 导入用户数据 -// * -// * @param userList 用户数据列表 -// * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 -// * @param operName 操作用户 -// * @return 结果 -// */ -// public String importUser(List userList, Boolean isUpdateSupport, String operName); } diff --git a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java index ad1be16f3..11b541177 100644 --- a/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java +++ b/src/main/java/cn/iocoder/dashboard/modules/system/service/user/SysUserServiceImpl.java @@ -3,12 +3,10 @@ package cn.iocoder.dashboard.modules.system.service.user; import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.StrUtil; import cn.iocoder.dashboard.common.enums.CommonStatusEnum; +import cn.iocoder.dashboard.common.exception.ServiceException; import cn.iocoder.dashboard.common.exception.util.ServiceExceptionUtil; import cn.iocoder.dashboard.common.pojo.PageResult; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserCreateReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserExportReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserPageReqVO; -import cn.iocoder.dashboard.modules.system.controller.user.vo.user.SysUserUpdateReqVO; +import cn.iocoder.dashboard.modules.system.controller.user.vo.user.*; import cn.iocoder.dashboard.modules.system.convert.user.SysUserConvert; import cn.iocoder.dashboard.modules.system.dal.mysql.dao.user.SysUserMapper; import cn.iocoder.dashboard.modules.system.dal.mysql.dataobject.dept.SysDeptDO; @@ -21,12 +19,10 @@ import cn.iocoder.dashboard.util.collection.CollectionUtils; import lombok.extern.slf4j.Slf4j; import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; +import org.springframework.transaction.annotation.Transactional; import javax.annotation.Resource; -import java.util.Collections; -import java.util.List; -import java.util.Map; -import java.util.Set; +import java.util.*; import static cn.iocoder.dashboard.modules.system.enums.SysErrorCodeConstants.*; @@ -268,71 +264,42 @@ public class SysUserServiceImpl implements SysUserService { }); } -// /** -// * 导入用户数据 -// * -// * @param userList 用户数据列表 -// * @param isUpdateSupport 是否更新支持,如果已存在,则进行更新数据 -// * @param operName 操作用户 -// * @return 结果 -// */ -// @Override -// public String importUser(List userList, Boolean isUpdateSupport, String operName) -// { -// if (StringUtils.isNull(userList) || userList.size() == 0) -// { -// throw new CustomException("导入用户数据不能为空!"); -// } -// int successNum = 0; -// int failureNum = 0; -// StringBuilder successMsg = new StringBuilder(); -// StringBuilder failureMsg = new StringBuilder(); -// String password = configService.selectConfigByKey("sys.user.initPassword"); -// for (SysUser user : userList) -// { -// try -// { -// // 验证是否存在这个用户 -// SysUser u = userMapper.selectUserByUserName(user.getUserName()); -// if (StringUtils.isNull(u)) -// { -// user.setPassword(SecurityUtils.encryptPassword(password)); -// user.setCreateBy(operName); -// this.insertUser(user); -// successNum++; -// successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 导入成功"); -// } -// else if (isUpdateSupport) -// { -// user.setUpdateBy(operName); -// this.updateUser(user); -// successNum++; -// successMsg.append("
" + successNum + "、账号 " + user.getUserName() + " 更新成功"); -// } -// else -// { -// failureNum++; -// failureMsg.append("
" + failureNum + "、账号 " + user.getUserName() + " 已存在"); -// } -// } -// catch (Exception e) -// { -// failureNum++; -// String msg = "
" + failureNum + "、账号 " + user.getUserName() + " 导入失败:"; -// failureMsg.append(msg + e.getMessage()); -// log.error(msg, e); -// } -// } -// if (failureNum > 0) -// { -// failureMsg.insert(0, "很抱歉,导入失败!共 " + failureNum + " 条数据格式不正确,错误如下:"); -// throw new CustomException(failureMsg.toString()); -// } -// else -// { -// successMsg.insert(0, "恭喜您,数据已全部导入成功!共 " + successNum + " 条,数据如下:"); -// } -// return successMsg.toString(); -// } + @Override + @Transactional // 添加事务,异常则回滚所有导入 + public SysUserImportRespVO importUsers(List importUsers, boolean isUpdateSupport) { + if (CollUtil.isEmpty(importUsers)) { + throw ServiceExceptionUtil.exception(USER_IMPORT_LIST_IS_EMPTY); + } + SysUserImportRespVO respVO = SysUserImportRespVO.builder().createUsernames(new ArrayList<>()) + .updateUsernames(new ArrayList<>()).failureUsernames(new LinkedHashMap<>()).build(); + importUsers.forEach(importUser -> { + // 校验,判断是否有不符合的原因 + try { + checkCreateOrUpdate(null, null, importUser.getMobile(), importUser.getEmail(), + importUser.getDeptId(), null); + } catch (ServiceException ex) { + respVO.getFailureUsernames().put(importUser.getUsername(), ex.getMessage()); + return; + } + // 判断如果不存在,在进行插入 + SysUserDO existUser = userMapper.selectByUsername(importUser.getUsername()); + if (existUser == null) { + // TODO 芋艿:初始密码 + userMapper.insert(SysUserConvert.INSTANCE.convert(importUser)); + respVO.getCreateUsernames().add(importUser.getUsername()); + return; + } + // 如果存在,判断是否允许更新 + if (!isUpdateSupport) { + respVO.getFailureUsernames().put(importUser.getUsername(), USER_USERNAME_EXISTS.getMessage()); + return; + } + SysUserDO updateUser = SysUserConvert.INSTANCE.convert(importUser); + updateUser.setId(existUser.getId()); + userMapper.updateById(updateUser); + respVO.getUpdateUsernames().add(importUser.getUsername()); + }); + return respVO; + } }