From 00b4e9ecc49f48c96d1a63c11bbd918d432f6d8c Mon Sep 17 00:00:00 2001 From: wjie Date: Tue, 24 Aug 2021 16:12:48 +0800 Subject: [PATCH] =?UTF-8?q?:sparkles:=20Introducing=20new=20features.=20cl?= =?UTF-8?q?osed=20#I449GN=20=E7=94=A8=E6=88=B7=E5=AF=BC=E5=85=A5=E5=AF=BC?= =?UTF-8?q?=E5=87=BA=E3=80=81=E6=97=A5=E5=BF=97=E5=AF=BC=E5=87=BA?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- pig-upms/pig-upms-api/pom.xml | 6 + .../pig/admin/api/vo/UserExcelVO.java | 69 ++++++++ pig-upms/pig-upms-biz/pom.xml | 20 ++- .../pig/admin/controller/FileController.java | 36 +++++ .../pig/admin/controller/LogController.java | 14 ++ .../pig/admin/controller/UserController.java | 29 ++++ .../pig/admin/service/SysUserService.java | 18 +++ .../service/impl/SysUserServiceImpl.java | 151 +++++++++++++++--- .../src/main/resources/file/user.xlsx | Bin 0 -> 8886 bytes 9 files changed, 320 insertions(+), 23 deletions(-) create mode 100644 pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java create mode 100644 pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/FileController.java create mode 100644 pig-upms/pig-upms-biz/src/main/resources/file/user.xlsx diff --git a/pig-upms/pig-upms-api/pom.xml b/pig-upms/pig-upms-api/pom.xml index 0a686667..1e098d45 100755 --- a/pig-upms/pig-upms-api/pom.xml +++ b/pig-upms/pig-upms-api/pom.xml @@ -51,5 +51,11 @@ com.pig4cloud pig-common-mybatis + + + com.pig4cloud.excel + excel-spring-boot-starter + 0.5.0 + diff --git a/pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java b/pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java new file mode 100644 index 00000000..5c0c5cf2 --- /dev/null +++ b/pig-upms/pig-upms-api/src/main/java/com/pig4cloud/pig/admin/api/vo/UserExcelVO.java @@ -0,0 +1,69 @@ +package com.pig4cloud.pig.admin.api.vo; + +import com.alibaba.excel.annotation.ExcelProperty; +import com.alibaba.excel.annotation.write.style.ColumnWidth; +import lombok.Data; + +import javax.validation.constraints.NotBlank; +import java.io.Serializable; +import java.time.LocalDateTime; + +/** + * 用户excel 对应的实体 + * + * @author lengleng + * @date 2021/8/4 + */ +@Data +@ColumnWidth(30) +public class UserExcelVO implements Serializable { + + private static final long serialVersionUID = 1L; + + /** + * 主键ID + */ + @ExcelProperty("用户编号") + private Integer userId; + + /** + * 用户名 + */ + @NotBlank(message = "用户名不能为空") + @ExcelProperty("用户名") + private String username; + + /** + * 手机号 + */ + @NotBlank(message = "手机号不能为空") + @ExcelProperty("手机号") + private String phone; + + /** + * 部门名称 + */ + @NotBlank(message = "部门名称不能为空") + @ExcelProperty("部门名称") + private String deptName; + + /** + * 角色列表 + */ + @NotBlank(message = "角色不能为空") + @ExcelProperty("角色") + private String roleNameList; + + /** + * 锁定标记 + */ + @ExcelProperty("锁定标记,0:正常,9:已锁定") + private String lockFlag; + + /** + * 创建时间 + */ + @ExcelProperty(value = "创建时间") + private LocalDateTime createTime; + +} diff --git a/pig-upms/pig-upms-biz/pom.xml b/pig-upms/pig-upms-biz/pom.xml index 6b47761a..7d264364 100644 --- a/pig-upms/pig-upms-biz/pom.xml +++ b/pig-upms/pig-upms-biz/pom.xml @@ -70,7 +70,7 @@ org.springframework.boot spring-boot-starter-undertow - + com.pig4cloud pig-common-test @@ -88,6 +88,24 @@ docker-maven-plugin + + + src/main/resources + true + + **/*.xlsx + **/*.xls + + + + src/main/resources + false + + **/*.xlsx + **/*.xls + + + diff --git a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/FileController.java b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/FileController.java new file mode 100644 index 00000000..2b594947 --- /dev/null +++ b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/FileController.java @@ -0,0 +1,36 @@ +package com.pig4cloud.pig.admin.controller; + +import cn.hutool.core.io.IoUtil; +import lombok.SneakyThrows; +import org.springframework.core.io.ClassPathResource; +import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PathVariable; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RestController; + +import javax.servlet.http.HttpServletResponse; + +/** + * 文件管理 + * + * @author lengleng + * @date 2021/8/23 + */ +@RestController +@RequestMapping("/file") +public class FileController { + + /** + * 获取本地文件 + * @param fileName 文件名称 + * @param response 本地文件 + */ + @SneakyThrows + @GetMapping("/local-file/{fileName}") + public void localFile(@PathVariable String fileName, HttpServletResponse response) { + ClassPathResource resource = new ClassPathResource("file/" + fileName); + response.setContentType("application/octet-stream; charset=UTF-8"); + IoUtil.copy(resource.getInputStream(), response.getOutputStream()); + } + +} diff --git a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/LogController.java b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/LogController.java index 85588eb8..0c236377 100644 --- a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/LogController.java +++ b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/LogController.java @@ -21,12 +21,14 @@ import com.pig4cloud.pig.admin.api.entity.SysLog; import com.pig4cloud.pig.admin.service.SysLogService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.security.annotation.Inner; +import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.annotations.Api; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import java.util.List; /** *

@@ -77,4 +79,16 @@ public class LogController { return R.ok(sysLogService.save(sysLog)); } + /** + * 导出excel 表格 + * @param sysLog 查询条件 + * @return EXCEL + */ + @ResponseExcel + @GetMapping("/export") + @PreAuthorize("@pms.hasPermission('sys_log_import_export')") + public List export(SysLogDTO sysLog) { + return sysLogService.getLogList(sysLog); + } + } diff --git a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/UserController.java b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/UserController.java index a08ee866..8fd8fd32 100644 --- a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/UserController.java +++ b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/controller/UserController.java @@ -21,17 +21,22 @@ import com.baomidou.mybatisplus.core.toolkit.Wrappers; import com.baomidou.mybatisplus.extension.plugins.pagination.Page; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.entity.SysUser; +import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.service.SysUserService; import com.pig4cloud.pig.common.core.util.R; import com.pig4cloud.pig.common.log.annotation.SysLog; import com.pig4cloud.pig.common.security.annotation.Inner; import com.pig4cloud.pig.common.security.util.SecurityUtils; +import com.pig4cloud.plugin.excel.annotation.RequestExcel; +import com.pig4cloud.plugin.excel.annotation.ResponseExcel; import io.swagger.annotations.Api; import lombok.RequiredArgsConstructor; import org.springframework.security.access.prepost.PreAuthorize; +import org.springframework.validation.BindingResult; import org.springframework.web.bind.annotation.*; import javax.validation.Valid; +import java.util.List; /** * @author lengleng @@ -163,4 +168,28 @@ public class UserController { return R.ok(userService.listAncestorUsersByUsername(username)); } + /** + * 导出excel 表格 + * @param userDTO 查询条件 + * @return + */ + @ResponseExcel + @GetMapping("/export") + @PreAuthorize("@pms.hasPermission('sys_user_import_export')") + public List export(UserDTO userDTO) { + return userService.listUser(userDTO); + } + + /** + * 导入用户 + * @param excelVOList 用户列表 + * @param bindingResult 错误信息列表 + * @return R + */ + @PostMapping("/import") + @PreAuthorize("@pms.hasPermission('sys_user_import_export')") + public R importUser(@RequestExcel List excelVOList, BindingResult bindingResult) { + return userService.importUser(excelVOList, bindingResult); + } + } diff --git a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java index 518b7fb9..f03d0b3a 100644 --- a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java +++ b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/SysUserService.java @@ -22,7 +22,10 @@ import com.baomidou.mybatisplus.extension.service.IService; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.entity.SysUser; +import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.api.vo.UserVO; +import com.pig4cloud.pig.common.core.util.R; +import org.springframework.validation.BindingResult; import java.util.List; @@ -89,4 +92,19 @@ public interface SysUserService extends IService { */ Boolean saveUser(UserDTO userDto); + /** + * 查询全部的用户 + * @param userDTO 查询条件 + * @return list + */ + List listUser(UserDTO userDTO); + + /** + * excel 导入用户 + * @param excelVOList excel 列表数据 + * @param bindingResult 错误数据 + * @return ok fail + */ + R importUser(List excelVOList, BindingResult bindingResult); + } diff --git a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java index efa82cfc..a8327551 100644 --- a/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java +++ b/pig-upms/pig-upms-biz/src/main/java/com/pig4cloud/pig/admin/service/impl/SysUserServiceImpl.java @@ -16,6 +16,7 @@ package com.pig4cloud.pig.admin.service.impl; +import cn.hutool.core.collection.CollUtil; import cn.hutool.core.util.ArrayUtil; import cn.hutool.core.util.StrUtil; import com.baomidou.mybatisplus.core.metadata.IPage; @@ -25,11 +26,19 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.pig4cloud.pig.admin.api.dto.UserDTO; import com.pig4cloud.pig.admin.api.dto.UserInfo; import com.pig4cloud.pig.admin.api.entity.*; +import com.pig4cloud.pig.admin.api.vo.UserExcelVO; import com.pig4cloud.pig.admin.api.vo.UserVO; +import com.pig4cloud.pig.admin.mapper.SysDeptMapper; +import com.pig4cloud.pig.admin.mapper.SysRoleMapper; import com.pig4cloud.pig.admin.mapper.SysUserMapper; -import com.pig4cloud.pig.admin.service.*; +import com.pig4cloud.pig.admin.mapper.SysUserRoleMapper; +import com.pig4cloud.pig.admin.service.SysMenuService; +import com.pig4cloud.pig.admin.service.SysUserService; import com.pig4cloud.pig.common.core.constant.CacheConstants; import com.pig4cloud.pig.common.core.constant.CommonConstants; +import com.pig4cloud.pig.common.core.constant.enums.MenuTypeEnum; +import com.pig4cloud.pig.common.core.util.R; +import com.pig4cloud.plugin.excel.vo.ErrorMessage; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.BeanUtils; @@ -39,10 +48,12 @@ import org.springframework.security.crypto.password.PasswordEncoder; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.Assert; +import org.springframework.validation.BindingResult; import java.time.LocalDateTime; import java.util.HashSet; import java.util.List; +import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; @@ -57,13 +68,13 @@ public class SysUserServiceImpl extends ServiceImpl impl private static final PasswordEncoder ENCODER = new BCryptPasswordEncoder(); + private final SysRoleMapper sysRoleMapper; + + private final SysDeptMapper sysDeptMapper; + private final SysMenuService sysMenuService; - private final SysRoleService sysRoleService; - - private final SysDeptService sysDeptService; - - private final SysUserRoleService sysUserRoleService; + private final SysUserRoleMapper sysUserRoleMapper; /** * 保存用户信息 @@ -78,13 +89,13 @@ public class SysUserServiceImpl extends ServiceImpl impl sysUser.setDelFlag(CommonConstants.STATUS_NORMAL); sysUser.setPassword(ENCODER.encode(userDto.getPassword())); baseMapper.insert(sysUser); - List userRoleList = userDto.getRole().stream().map(roleId -> { + userDto.getRole().stream().map(roleId -> { SysUserRole userRole = new SysUserRole(); userRole.setUserId(sysUser.getUserId()); userRole.setRoleId(roleId); return userRole; - }).collect(Collectors.toList()); - return sysUserRoleService.saveBatch(userRoleList); + }).forEach(sysUserRoleMapper::insert); + return Boolean.TRUE; } /** @@ -97,18 +108,14 @@ public class SysUserServiceImpl extends ServiceImpl impl UserInfo userInfo = new UserInfo(); userInfo.setSysUser(sysUser); // 设置角色列表 (ID) - List roleIds = sysRoleService.findRolesByUserId(sysUser.getUserId()).stream().map(SysRole::getRoleId) + List roleIds = sysRoleMapper.listRolesByUserId(sysUser.getUserId()).stream().map(SysRole::getRoleId) .collect(Collectors.toList()); userInfo.setRoles(ArrayUtil.toArray(roleIds, Integer.class)); - // 设置权限列表(menu.permission) - Set permissions = new HashSet<>(); - roleIds.forEach(roleId -> { - List permissionList = sysMenuService.findMenuByRoleId(roleId).stream() - .filter(menuVo -> StrUtil.isNotEmpty(menuVo.getPermission())).map(SysMenu::getPermission) - .collect(Collectors.toList()); - permissions.addAll(permissionList); - }); + Set permissions = sysMenuService.findMenuByRoleId(CollUtil.join(roleIds, StrUtil.COMMA)).stream() + .filter(m -> MenuTypeEnum.BUTTON.getType().equals(m.getType())) + .filter(m -> StrUtil.isNotBlank(m.getPermission())).map(SysMenu::getPermission) + .collect(Collectors.toSet()); userInfo.setPermissions(ArrayUtil.toArray(permissions, String.class)); return userInfo; } @@ -142,7 +149,7 @@ public class SysUserServiceImpl extends ServiceImpl impl @Override @CacheEvict(value = CacheConstants.USER_DETAILS, key = "#sysUser.username") public Boolean removeUserById(SysUser sysUser) { - sysUserRoleService.removeRoleByUserId(sysUser.getUserId()); + sysUserRoleMapper.deleteByUserId(sysUser.getUserId()); this.removeById(sysUser.getUserId()); return Boolean.TRUE; } @@ -177,8 +184,8 @@ public class SysUserServiceImpl extends ServiceImpl impl } this.updateById(sysUser); - sysUserRoleService - .remove(Wrappers.update().lambda().eq(SysUserRole::getUserId, userDto.getUserId())); + sysUserRoleMapper + .delete(Wrappers.update().lambda().eq(SysUserRole::getUserId, userDto.getUserId())); userDto.getRole().forEach(roleId -> { SysUserRole userRole = new SysUserRole(); userRole.setUserId(sysUser.getUserId()); @@ -197,7 +204,7 @@ public class SysUserServiceImpl extends ServiceImpl impl public List listAncestorUsersByUsername(String username) { SysUser sysUser = this.getOne(Wrappers.query().lambda().eq(SysUser::getUsername, username)); - SysDept sysDept = sysDeptService.getById(sysUser.getDeptId()); + SysDept sysDept = sysDeptMapper.selectById(sysUser.getDeptId()); if (sysDept == null) { return null; } @@ -206,4 +213,104 @@ public class SysUserServiceImpl extends ServiceImpl impl return this.list(Wrappers.query().lambda().eq(SysUser::getDeptId, parentId)); } + /** + * 查询全部的用户 + * @param userDTO 查询条件 + * @return list + */ + @Override + public List listUser(UserDTO userDTO) { + List voList = baseMapper.selectVoList(userDTO); + // 转换成execl 对象输出 + List userExcelVOList = voList.stream().map(userVO -> { + UserExcelVO excelVO = new UserExcelVO(); + BeanUtils.copyProperties(userVO, excelVO); + String roleNameList = userVO.getRoleList().stream().map(SysRole::getRoleName) + .collect(Collectors.joining(StrUtil.COMMA)); + excelVO.setRoleNameList(roleNameList); + return excelVO; + }).collect(Collectors.toList()); + return userExcelVOList; + } + + /** + * excel 导入用户, 插入正确的 错误的提示行号 + * @param excelVOList excel 列表数据 + * @param bindingResult 错误数据 + * @return ok fail + */ + @Override + public R importUser(List excelVOList, BindingResult bindingResult) { + // 通用校验获取失败的数据 + List errorMessageList = (List) bindingResult.getTarget(); + + // 个性化校验逻辑 + List userList = this.list(); + List deptList = sysDeptMapper.selectList(Wrappers.emptyWrapper()); + List roleList = sysRoleMapper.selectList(Wrappers.emptyWrapper()); + + // 执行数据插入操作 组装 UserDto + for (int i = 0; i < excelVOList.size(); i++) { + UserExcelVO excel = excelVOList.get(i); + Set errorMsg = new HashSet<>(); + // 校验用户名是否存在 + boolean exsitUserName = userList.stream() + .anyMatch(sysUser -> excel.getUsername().equals(sysUser.getUsername())); + + if (exsitUserName) { + errorMsg.add(String.format("%s 用户名已存在", excel.getUsername())); + } + + // 判断输入的部门名称列表是否合法 + Optional deptOptional = deptList.stream() + .filter(dept -> excel.getDeptName().equals(dept.getName())).findFirst(); + if (!deptOptional.isPresent()) { + errorMsg.add(String.format("%s 部门名称不存在", excel.getDeptName())); + } + + // 判断输入的角色名称列表是否合法 + List roleNameList = StrUtil.split(excel.getRoleNameList(), StrUtil.COMMA); + List roleCollList = roleList.stream() + .filter(role -> roleNameList.stream().anyMatch(name -> role.getRoleName().equals(name))) + .collect(Collectors.toList()); + + if (roleCollList.size() != roleNameList.size()) { + errorMsg.add(String.format("%s 角色名称不存在", excel.getRoleNameList())); + } + + // 数据合法情况 + if (CollUtil.isEmpty(errorMsg)) { + insertExcelUser(excel, deptOptional, roleCollList); + } + else { + // 数据不合法情况 + errorMessageList.add(new ErrorMessage((long) (i + 2), errorMsg)); + } + + } + + if (CollUtil.isNotEmpty(errorMessageList)) { + return R.failed(errorMessageList); + } + return R.ok(); + } + + /** + * 插入excel User + */ + private void insertExcelUser(UserExcelVO excel, Optional deptOptional, List roleCollList) { + UserDTO userDTO = new UserDTO(); + userDTO.setUsername(excel.getUsername()); + userDTO.setPhone(excel.getPhone()); + // 批量导入初始密码为手机号 + userDTO.setPassword(userDTO.getPhone()); + // 根据部门名称查询部门ID + userDTO.setDeptId(deptOptional.get().getDeptId()); + // 根据角色名称查询角色ID + List roleIdList = roleCollList.stream().map(SysRole::getRoleId).collect(Collectors.toList()); + userDTO.setRole(roleIdList); + // 插入用户 + this.saveUser(userDTO); + } + } diff --git a/pig-upms/pig-upms-biz/src/main/resources/file/user.xlsx b/pig-upms/pig-upms-biz/src/main/resources/file/user.xlsx new file mode 100644 index 0000000000000000000000000000000000000000..7913bd8e766e194b471db0feebaac7e2ead5a636 GIT binary patch literal 8886 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 w{)q!Ye73Ve0}08IEN0Iul|AO8IIe@vfKPyhe` literal 0 HcmV?d00001