部门管理模块
部门是用户的一个属性,即用户属于某个部门,一个部门可以拥有多位员工,部门管理模块用于维护部门档案,用户可以新增、编辑、查询、删除和导出部门档案,架构图如下所示。
部门实体类的字段明细如下:
- 部门名称
- 部门状态
- 排序值
- 父部门 ID
因为部门档案是一个树形结构的模块,所以设置了父部门 ID。我指定顶级部门的父 ID 为 0,初始状态下前端请求父 ID 为 0 的数据,再使用深度优先搜索(dfs)的方法迭代搜索下面的子部门,从而实现部门管理模块的查询和删除功能,其中深度优先搜索(dfs)的删除部门代码如下所示。
// 深度优先搜索(dfs)的删除部门代码 @ApiOperation(value = "迭代删除部门") public void deleteRecursion(String id, String[] ids) { QueryWrapper<User> userQw = new QueryWrapper<>(); userQw.eq("department_id",id); long userCountInDepartment = iUserService.count(userQw); if(userCountInDepartment > 0L){ throw new ZwzException("不能删除包含员工的部门"); } Department department = iDepartmentService.getById(id); Department parentDepartment = null; if(department != null && !ZwzNullUtils.isNull(department.getParentId())){ parentDepartment = iDepartmentService.getById(department.getParentId()); } iDepartmentService.removeById(id); QueryWrapper<DepartmentHeader> dhQw = new QueryWrapper<>(); dhQw.eq("department_id",id); iDepartmentHeaderService.remove(dhQw); if(parentDepartment != null){ QueryWrapper<Department> depQw = new QueryWrapper<>(); depQw.eq("parent_id",parentDepartment.getId()); depQw.orderByAsc("sort_order"); List<Department> childrenDepartmentList = iDepartmentService.list(depQw); if(childrenDepartmentList == null || childrenDepartmentList.size() < 1){ parentDepartment.setIsParent(false); iDepartmentService.saveOrUpdate(parentDepartment); } } QueryWrapper<Department> depQw = new QueryWrapper<>(); depQw.eq("parent_id",id); depQw.orderByAsc("sort_order"); List<Department> departmentList = iDepartmentService.list(depQw); for(Department judgeDepartment : departmentList){ if(!CommonUtil.judgeIds(judgeDepartment.getId(), ids)){ deleteRecursion(judgeDepartment.getId(), ids); } } }
部门管理模块的操作界面如下图所示。
用户可以点击左上角的添加按钮,系统会弹出一个“添加一级部门”界面,添加的部门父 ID 会被指定为 0,也就是顶级的部门。
如果用户在左侧树形组件中选择了某个部门(如选择人力资源部),系统会弹出一个“添加部门”界面,添加的部门父 ID 就是当前树形组件选择部门的 ID,从而实现添加子部门的功能。
用户可以在右上侧的部门编辑界面中完成对部门信息的编辑操作,最后点击保存按钮即可完成数据更新。
用户也可以在树形组件中勾选部门,然后点击顶部的删除按钮,触发部门的删除操作,如下图所示。
删除部门默认为级联状态,若选择了某个部门,其下层的部门也会被级联选择,若用户不需要此功能,需要关闭级联 Switch 开关,如下图所示。
文件管理模块
文件存储是大多数管理系统的必备功能,所以基于 Vue 和 SpringBoot 的通用管理系统对文件管理进行了封装,在其他模块上传的文件都会被集成到这个文件管理模块。在这里用户可以对文件进行上传、下载、预览、删除等操作,其架构图如下图所示。
文件实体类的字段明细如下:
- 上传文件名
- 存储路径
- 存储硬盘
- 文件大小
- 实际文件名
- 文件类型
文件管理模块的操作界面如下所示。
在初次部署系统时,用户需要对文件的存储情况进行配置,也就是告诉系统文件放在哪里,还有就是文件的预览端口,文件存储配置的操作界面如下图所示,通过文件管理模块主界面的配置按钮进入。
当用户将 a.txt 文件上传到系统中,系统首先会在后端接收到这个文件的内容,将文件命名为 UUID 的随机字符串,持久化到数据库,并且将文件存储到用户配置的磁盘路径,文件上传的后端核心代码如下所示。
// 文件上传核心代码 @SystemLog(about = "文件上传", type = LogType.DATA_CENTER,doType = "FILE-06") @RequestMapping(value = "/file", method = RequestMethod.POST) @ApiOperation(value = "文件上传") public Result<Object> upload(@RequestParam(required = false) MultipartFile file,@RequestParam(required = false) String base64) { if(StrUtil.isNotBlank(base64)){ file = Base64DecodeMultipartFile.base64Convert(base64); } String result = null; String fKey = CommonUtil.renamePic(file.getOriginalFilename()); File f = new File(); try { InputStream inputStream = file.getInputStream(); result = zwzFileUtils.inputStreamUpload(inputStream, fKey, file); f.setLocation(0); f.setName(file.getOriginalFilename()); f.setSize(file.getSize()); f.setType(file.getContentType()); f.setFKey(fKey); f.setUrl(result); iFileService.saveOrUpdate(f); } catch (Exception e) { return ResultUtil.error(e.toString()); } OssSettingVo vo = getOssSetting(); return ResultUtil.data(vo.getFileHttp() + vo.getFileView() + "/" + f.getId()); }
当用户需要查询文件的时候,系统根据数据库的数据,匹配到硬盘目录中的文件,回显到前端,回显的后端核心代码如下所示。
// 文件预览核心代码 @SystemLog(about = "预览文件", type = LogType.DATA_CENTER,doType = "FILE-05") @RequestMapping(value = "/view/{id}", method = RequestMethod.GET) @ApiOperation(value = "预览文件") public void view(@PathVariable String id,@RequestParam(required = false) String filename,@RequestParam(required = false, defaultValue = "false") Boolean preview,HttpServletResponse httpServletResponse) throws IOException { File selectFile = iFileService.getById(id); if(selectFile == null){ throw new ZwzException("文件不存在"); } if(ZwzNullUtils.isNull(filename)){ filename = selectFile.getFKey(); } if(!preview){ httpServletResponse.addHeader("Content-Disposition", "attachment; filename=" + URLEncoder.encode(filename, "UTF-8")); } httpServletResponse.setContentLengthLong(selectFile.getSize()); httpServletResponse.setContentType(selectFile.getType()); httpServletResponse.addHeader("Accept-Ranges", "bytes"); if(selectFile.getSize() != null && selectFile.getSize() > 0){ httpServletResponse.addHeader("Content-Range", "bytes " + 0 + "-" + (selectFile.getSize()-1) + "/" + selectFile.getSize()); } zwzFileUtils.view(selectFile.getUrl(), httpServletResponse); }
对于图片格式的文件,系统使用了 viewerjs 依赖,后端会返回图片格式的文件流,支持直接预览,预览的效果如下图所示。
对于其他类型的文件,系统后端会返回文件格式的文件流,浏览器会根据文件类型进行预览或下载,如 PDF 文档会直接触发预览,界面如下所示。
对于不可预览的文件,浏览器会自动触发下载,用户需要下载后使用本地的软件进行预览,如下图所示。
权限管理模块
基于 Vue 和 SpringBoot 的通用管理系统采用了基于角色的访问控制,角色和菜单关联,一个角色可以配置多个菜单权限;然后再将用户和角色关联,一位用户可以赋予多个角色。这样用户就可以根据角色拿到该有的菜单权限,更方便管理者进行权限管控,权限管理模块的内容包括了菜单管理模块和角色管理模块,接下来对这两个模块进行介绍。
菜单管理模块用于维护系统的菜单数据,当开发者编写完成指定模块的代码之后,需要整合到系统的路由系统中,这就需要开发者将模块的名称、路由名称、代码路径配置到菜单管理模块,菜单管理模块的主界面如下所示。
当用户开发完成指定的模块后,需要在菜单管理模块中新建菜单,即点击顶部的添加菜单按钮,系统会弹出添加下单的界面。当用户没有选择左侧树形组件的菜单时,默认添加顶级菜单,若选择了则添加选择菜单的子菜单,相关逻辑和部门管理模块相似,添加菜单的界面如下图所示。
系统页面顶部的为一级菜单,也就是顶级菜单,如下图所示。
系统左侧展示了二级和三级菜单,如下图所示。
假设用户新建的模块路径为 views/demo/demo1/index,如下图所示。
那么用户就需要在前端代码输入组件中输入 demo/demo1/index,完成和前端代码的匹配。另外路径字段和路由英文名字段随意填写,不和现有菜单重复即可。
用户也可以在菜单管理模块编辑菜单的数据,编辑完成后点击“保存菜单”按钮即可,如下图所示。
另外系统还支持根据单个菜单查询权限用户的功能,让用户快速知晓这个菜单能够被哪些人看到,这个功能的后端核心代码如下所示。
// 查询菜单权限拥有者核心代码 @SystemLog(about = "查询菜单权限拥有者", type = LogType.DATA_CENTER,doType = "PERMISSION-01") @ApiOperation(value = "查询菜单权限拥有者") @RequestMapping(value = "/getUserByPermission", method = RequestMethod.GET) public Result<List<UserByPermissionVo>> getUserByPermission(@RequestParam String permissionId){ Permission permission = iPermissionService.getById(permissionId); if(permission == null) { return ResultUtil.error("该菜单已被删除"); } List<UserByPermissionVo> ansList = new ArrayList<>(); // 查询用户 QueryWrapper<RolePermission> qw = new QueryWrapper<>(); qw.eq("permission_id",permissionId); List<RolePermission> rolePermissionList = iRolePermissionService.list(qw); for (RolePermission rp : rolePermissionList) { Role role = iRoleService.getById(rp.getRoleId()); if(role != null) { QueryWrapper<UserRole> urQw = new QueryWrapper<>(); urQw.eq("role_id",role.getId()); List<UserRole> userRoleList = iUserRoleService.list(urQw); for (UserRole ur : userRoleList) { User user = iUserService.getById(ur.getUserId()); if(user != null) { boolean flag = false; for (UserByPermissionVo vo : ansList) { if(Objects.equals(vo.getUserId(),user.getId())) { flag = true; vo.setRoleStr(vo.getRoleStr() + role.getName() + "(" + role.getDescription() + ") "); break; } } if(!flag) { UserByPermissionVo vo = new UserByPermissionVo(); vo.setUserId(user.getId()); vo.setUserName(user.getNickname()); vo.setRoleStr(role.getName()); vo.setCode(user.getUsername()); vo.setMobile(user.getMobile()); ansList.add(vo); } } } } } return new ResultUtil<List<UserByPermissionVo>>().setData(ansList); }
对于角色管理模块而言,存在的意义就是实现基于角色的访问控制,操作界面如下图所示。
用户可以对角色分配权限菜单,分配后有这个角色的用户就可以进入配置的菜单,进行相关的操作,菜单权限分配界面如下图所示,用户分配完成后点击底部的保存菜单权限按钮即可完成更新操作。
用户还可以对角色进行设置默认操作,设置默认角色后,新注册的用户会被赋予该角色,拥有这个角色的菜单权限,默认角色支持有多个。