1000字范文,内容丰富有趣,学习的好帮手!
1000字范文 > 实战 - 迎学后台管理系统开发

实战 - 迎学后台管理系统开发

时间:2021-03-27 22:26:09

相关推荐

实战 - 迎学后台管理系统开发

文章目录

1. 数据库表结构2. 登陆功能3. 首页导航栏展示4. 菜单权限管理1. 获取所有菜单权限数据2. 获取菜单权限树3. 新增菜单权限树 5. 角色管理1. 分页获取角色数据2. 新增角色3. 多条件查询角色数据 6. 部门管理1. 获取所有部门数据2. 获取部门树3. 新增部门 7. 用户管理1. 分页查询用户数据2. 新增用户3. 多条件查询用户列表4. 赋予用户角色5. jwt 自动刷新6. 编辑用户7. 删除用户 8. 菜单权限管理1. 编辑菜单权限2. 删除菜单权限

1. 数据库表结构

通常来说有用户参与的系统一般都要有权限管理,权限管理就是实现对用户访问系统的控制,按照安全规则或者安全策略控制用户可 以访问而且只能访问自己被授权的资源。 权限管理包括用户认证和授权两部分。

用户认证:用户去访问系统,系统要验证用户身份的合法性。最常用的验证的方式:用户名密码方式。

用户授权:简单理解为访问控制,在用户通过认证之后,系统对用户访问资源进行控制,用户具有该资源的访问权限方可访问。

权限控制

基于角色的权限访问控制 RBAC(role-based access control)是以角色为中心进行的访问控制。

基于资源的权限访问控制 RBAC(resource-based access control)是以资源为中心进行的访问控制

基于资源的权限访问控制,资源和权限一对一关系比较常见,很多时候资源和权限在数据库中会被合并在一张表中,只需要为资源分配相应的权限。所以一个具体操作对应的权限,只要直接判断用户是否拥有该权限即可,可扩展性强。如果用户的权限需要改变,只需要对数据库中用户的角色对应的权限进行改变,就可以了。

不过使用基于资源的方式,仍然是需要角色的,用户的权限分配的依据往往是角色(比如:如果我给你admin角色,这个admin 角色拥有所有资源curd的权限,那么你就拥有所有资源的curd权限)。而进行访问控制时,则不依赖角色(比如:你想查看用户考勤时长,那么我可以直接问你你有用户考勤时常权限吗?如果有,你就可以访问。而不关心你是什么角色)。

1. 用户表

use company_frame;DROP TABLE IF EXISTS `sys_user`;CREATE TABLE `sys_user` (`id` varchar(64) NOT NULL COMMENT '用户id',`username` varchar(50) NOT NULL COMMENT '账户名称',`salt` varchar(20) DEFAULT NULL COMMENT '加密盐值',`password` varchar(200) NOT NULL COMMENT '用户密码密文',`phone` varchar(20) DEFAULT NULL COMMENT '手机号码',`dept_id` varchar(64) DEFAULT NULL COMMENT '部门id',`real_name` varchar(60) DEFAULT NULL COMMENT '真实名称',`nick_name` varchar(60) DEFAULT NULL COMMENT '昵称',`email` varchar(50) DEFAULT NULL COMMENT '邮箱(唯一)',`status` tinyint(4) DEFAULT '1' COMMENT '账户状态(1.正常 2.锁定 )',`sex` tinyint(4) DEFAULT '1' COMMENT '性别(1.男 2.女)',`deleted` tinyint(4) DEFAULT '1' COMMENT '是否删除(1未删除;0已删除)',`create_id` varchar(64) DEFAULT NULL COMMENT '创建人',`update_id` varchar(64) DEFAULT NULL COMMENT '更新人',`create_where` tinyint(4) DEFAULT '1' COMMENT '创建来源(1.web 2.android 3.ios )',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL,PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

2. 角色表

CREATE TABLE `sys_role` (`id` varchar(64) NOT NULL COMMENT '主键',`name` varchar(255) DEFAULT NULL COMMENT '角色名称',`description` varchar(300) DEFAULT NULL,`status` tinyint(4) DEFAULT '1' COMMENT '状态(1:正常0:弃用)',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '1' COMMENT '是否删除(1未删除;0已删除)',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

3. 用户角色关联表

CREATE TABLE `sys_user_role` (`id` varchar(64) NOT NULL COMMENT '主键',`user_id` varchar(64) DEFAULT NULL COMMENT '用户id',`role_id` varchar(64) DEFAULT NULL COMMENT '角色id',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

4. 菜单权限表

CREATE TABLE `sys_permission` (`id` varchar(64) NOT NULL COMMENT '主键',`code` varchar(64) DEFAULT NULL COMMENT '菜单权限编码',`name` varchar(300) DEFAULT NULL COMMENT '菜单权限名称',`perms` varchar(500) DEFAULT NULL COMMENT '授权(如:sys:user:add)',`url` varchar(100) DEFAULT NULL COMMENT '访问地址URL',`method` varchar(10) DEFAULT NULL COMMENT '资源请求类型',`pid` varchar(64) DEFAULT NULL COMMENT '父级菜单权限名称',`order_num` int(11) DEFAULT '0' COMMENT '排序',`type` tinyint(4) DEFAULT NULL COMMENT '菜单权限类型(1:目录;2:菜单;3:按钮)',`status` tinyint(4) DEFAULT '1' COMMENT '状态1:正常 0:禁用',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '1' COMMENT '是否删除(1未删除;0已删除)',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

5. 角色权限关联表

CREATE TABLE `sys_role_permission` (`id` varchar(64) NOT NULL COMMENT '主键',`role_id` varchar(64) DEFAULT NULL COMMENT '角色id',`permission_id` varchar(64) DEFAULT NULL COMMENT '菜单权限id',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

6. 部门表

CREATE TABLE `sys_dept` (`id` varchar(64) NOT NULL COMMENT '主键',`dept_no` varchar(18) DEFAULT NULL COMMENT '部门编号',`name` varchar(300) DEFAULT NULL COMMENT '部门名称',`pid` varchar(64) NOT NULL COMMENT '父级id',`status` tinyint(4) DEFAULT '1' COMMENT '状态(1:正常;0:弃用)',`relation_code` varchar(3000) DEFAULT NULL COMMENT '为了维护更深层级关系(规则:父级关系编码+自己的编码)',`dept_manager_id` varchar(64) DEFAULT NULL COMMENT '部门经理user_id',`manager_name` varchar(255) DEFAULT NULL COMMENT '部门经理名称',`phone` varchar(20) DEFAULT NULL COMMENT '部门经理联系电话',`create_time` datetime DEFAULT NULL COMMENT '创建时间',`update_time` datetime DEFAULT NULL COMMENT '更新时间',`deleted` tinyint(4) DEFAULT '1' COMMENT '是否删除(1未删除;0已删除)',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8;

7. 操作日志表

CREATE TABLE `sys_log` (`id` varchar(64) NOT NULL COMMENT '主键',`user_id` varchar(64) DEFAULT NULL COMMENT '用户id',`username` varchar(50) DEFAULT NULL COMMENT '用户名',`operation` varchar(50) DEFAULT NULL COMMENT '用户操作',`time` int(11) DEFAULT NULL COMMENT '响应时间',`method` varchar(200) DEFAULT NULL COMMENT '请求方法',`params` varchar(5000) DEFAULT NULL COMMENT '请求参数',`ip` varchar(64) DEFAULT NULL COMMENT 'IP地址',`create_time` datetime DEFAULT NULL COMMENT '创建时间',PRIMARY KEY (`id`)) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='系统日志';

使用代码生成器生成基本增删改查,创建试图跳转控制器

@Api(tags = "视图",description = "负责返回视图")@Controller@RequestMapping("/index")public class IndexController {@GetMapping("/404")@ApiOperation(value = "跳转404错误页面")public String error404(){return "error/404";}}

2. 登陆功能

① 跳转到登录页面的视图方法:404.html、login.html

@Api(tags = "视图",description = "负责返回视图")@Controller@RequestMapping("/index")public class IndexController {@GetMapping("/404")@ApiOperation(value = "跳转404错误页面")public String error404(){return "error/404";}@GetMapping("/login")@ApiOperation(value = "跳转登录界面")public String logout(){return "login";}}

访问跳转到登录页面的视图方法,发现需要认证token,可以设置shiro中的白名单

② shiro 设置跳转视图页面白名单

filterChainDefinitionMap.put("/index/**", "anon");

③ shiro 配置静态文件白名单

filterChainDefinitionMap.put("/images/**", "anon");filterChainDefinitionMap.put("/js/**", "anon");filterChainDefinitionMap.put("/layui/**", "anon");filterChainDefinitionMap.put("/css/**", "anon");

④ 访问跳转到登录页面的视图方法:http://localhost:8081/index/login

用户1:admin/666666 用户2:dev123/666666

⑤ 登录后跳转到首页和主页的视图方法:home.html、main.html

@Api(tags = "视图",description = "负责返回视图")@Controller@RequestMapping("/index")public class IndexController {@GetMapping("/home")@ApiOperation(value = "跳转首页页面")public String home(){return "home";}@GetMapping("/main")@ApiOperation(value = "跳转主页页面")public String main(){return "main";}}

3. 首页导航栏展示

需求分析:

导航栏分为顶栏导航栏和左侧导航栏 。

顶栏菜单栏:部门名称、登录用户

左侧菜单栏:树形结构、节点菜单名称,菜单路径名称

① 顶栏响应数据包含:部门名称,用户名称

@Datapublic class UserInfoRespVO {@ApiModelProperty(value = "用户id")private String id;@ApiModelProperty(value = "用户名称")private String username;@ApiModelProperty(value = "部门id")private String deptId;@ApiModelProperty(value = "所属部门名称")private String deptName;}

② 左侧菜单栏响应数据:树形结构菜单权限名称,子菜单,

@Datapublic class PermissionRespNodeVO {@ApiModelProperty(value = "主键id")private String id;@ApiModelProperty(value = "菜单权限名称")private String title;@ApiModelProperty(value = "子集集合")private List<?> children;@ApiModelProperty(value = "跳转地址")private String url;}

③ 包含了用户信息和菜单权限信息,当要响应的信息涉及多张表时,可以VO里面封装VO

@Datapublic class HomeRespVO {@ApiModelProperty(value = "用户信息")private UserInfoRespVO userInfoVO;@ApiModelProperty(value = "首页菜单导航数据")private List<PermissionRespNodeVO> menus;}

接口开发:

请求用户信息,初始化首页数据获取该用户拥有的目录菜单,初始化左侧导航栏

1、Controller层

@RestController@RequestMapping("/api")@Api(tags = "首页模块",description = "首页相关模块")public class HomeController {@Autowiredprivate HomeService homeService;@GetMapping("/home")@ApiOperation(value = "获取首页数据接口")public DataResult<HomeRespVO> getHome(HttpServletRequest request){// 获取登录用户的accessToken,必须通过登录后才能访问该接口String accessToken=request.getHeader(Constant.ACCESS_TOKEN);// 根据请求token获取userIdString userId= JwtTokenUtil.getUserId(accessToken);DataResult result =DataResult.success();HomeRespVO homeRespVO = homeService.getHome(userId);result.setData(homeRespVO);return result;}}

2、 Service层

@Servicepublic class HomeServiceImpl implements HomeService {@Autowiredprivate SysUserDao sysUserDao;@Overridepublic HomeRespVO getHome(String userId) {// 响应voHomeRespVO homeRespVO=new HomeRespVO();// mock菜单权限数据String home="[{\"children\":[{\"children\":[{\"children\":[{\"children\":[{\"children\":[],\"id\":\"6\",\"title\":\"五级类目5-6\",\"url\":\"string\"}],\"id\":\"5\",\"title\":\"四级类目4-5\",\"url\":\"string\"}],\"id\":\"4\",\"title\":\"三级类目3-4\",\"url\":\"string\"}],\"id\":\"3\",\"title\":\"二级类目2-3\",\"url\":\"string\"}],\"id\":\"1\",\"title\":\"类目1\",\"url\":\"string\"},{\"children\":[],\"id\":\"2\",\"title\":\"类目2\",\"url\":\"string\"}]";List<PermissionRespNodeVO> list = JSON.parseArray(home, PermissionRespNodeVO.class);homeRespVO.setMenus(list);// 根据用户id获取用户信息:用户id,用户名称,部门idSysUser sysUser = sysUserDao.queryById(userId);UserInfoRespVO vo=new UserInfoRespVO();if(sysUser!=null){BeanUtils.copyProperties(sysUser,vo);// mock部门名称vo.setDeptName("迎学教育总公司");}homeRespVO.setUserInfoVO(vo);return homeRespVO;}}

3、dao层

<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE mapper PUBLIC "-////DTD Mapper 3.0//EN" "/dtd/mybatis-3-mapper.dtd"><mapper namespace="com.yingxue.lesson.dao.SysUserDao"><sql id="selectFields">id, username, salt, password, phone, dept_id, real_name, nick_name, email, status, sex, deleted, create_id, update_id, create_where, create_time, update_time</sql><!--查询单个--><select id="queryById" resultMap="SysUserMap">select <include refid="selectFields"></include> from sys_user where id = #{id}</select></mapper>

4、测试

类目1(1级菜单)->二级类目(2级菜单)->三级类目(3级菜单)->四级类目(4级菜单)->五级类目(5级菜单)

4. 菜单权限管理

点击菜单权限管理,跳转到菜单权限管理页面视图:menu.html

@Api(tags = "视图",description = "负责返回视图")@Controller@RequestMapping("/index")public class IndexController {@GetMapping("/menus")@ApiOperation(value = "跳转菜单权限管理页面")public String menus(){return "menus/menu";}}

查询所有菜单权限数据

查询菜单权限详情

新增菜单权限

编辑菜单权限

删除菜单权限

修改菜单权限状态

获取菜单权限树

1. 获取所有菜单权限数据

需求分析:

响应数据比数据库中查询到的多了一个父级名称,而我们的实体类中只有pid,因此在查询到实体类数据后,需要根据pid查询到父级名称后返回给前端。

响应类:在实体类SysPermission中添加一个父级名称pidName字段

@Datapublic class SysPermission implements Serializable {private static final long serialVersionUID = 716190147781782510L;// 省略..../*** 添加字段:菜单权限父级名称,该字段数据库中不存在,因此使用了 @Transient*/@JsonInclude(value = JsonInclude.Include.NON_NULL)@Transientprivate String pidName;}

接口开发 :

① dao层

<sql id="selectFields">id, code, name, perms, url, method, pid, order_num, type, status, create_time, update_time, deleted</sql><!--查询所有的菜单权限,过滤掉已经删除的数据,deleted=1表示未删除--><select id="queryAll" resultMap="SysPermissionMap">select <include refid="selectFields"></include> from sys_permission where deleted=1</select>

② Service层

@Overridepublic List<SysPermission> selectAll() {// 查询所有菜单权限数据List<SysPermission> permissionList = sysPermissionDao.queryAll();// 根据pid查询出每个菜单数据的pidNameList<SysPermission> sysPermissionList = permissionList.stream().map(sysPermission -> {SysPermission parent = sysPermissionDao.queryById(sysPermission.getPid());if (Objects.isNull(parent)) {sysPermission.setPidName(parent.getName());}return sysPermission;}).collect(Collectors.toList());// 返回响应数据列表return sysPermissionList;}

③ shiro 加上 treetable 白名单

filterChainDefinitionMap.put("/treetable-lay/**", "anon");

2. 获取菜单权限树

需求分析:

在新增菜单权限页面中,需要选择所属菜单,因此我们需要获取菜单权限树;在菜单权限树中需要有一个默认顶级菜单,这个不是人为手动添加的,而是系统默认的;我们的菜单权限类型有3种,目录,菜单,按钮。目录的上一层是目录,菜单的上一层是目录,按钮的上一层是菜单,我们只递归到菜单即可。

设计接口时,首先需要确定请求数据和响应数据,这里需要确定响应数据,封装响应实体类。

@Datapublic class PermissionRespNodeVO {@ApiModelProperty(value = "主键id")private String id;@ApiModelProperty(value = "跳转地址")private String url;@ApiModelProperty(value = "菜单权限名称")private String title;@JsonInclude(value = JsonInclude.Include.NON_EMPTY)@ApiModelProperty(value = "子集集合")private List<PermissionRespNodeVO> children;@ApiModelProperty(value = "默认展开")private boolean spread=true;@ApiModelProperty(value = "节点是否选中")private boolean checked;}

接口开发 :

① Service层

/*** 新增默认顶级菜单*/@PostConstructpublic void init(){List<SysPermission> sysPermissionList = sysPermissionDao.queryAll();if(CollectionUtils.isEmpty(sysPermissionList)){SysPermission sysPermission = new SysPermission();sysPermission.setId("0");sysPermission.setPid("-1");sysPermission.setName("默认顶级菜单");sysPermissionDao.insert(sysPermission);}}/*** 获取菜单权限树** @return List<PermissionRespNodeVO>*/@Overridepublic List<PermissionRespNodeVO> selectAllMenuByTree() {List<PermissionRespNodeVO> result = new ArrayList<>();// 查询出所有菜单List<SysPermission> sysPermissionList = sysPermissionDao.queryAll();// 设置“默认顶级菜单”的子节点SysPermission sysPermission = sysPermissionDao.queryByPid("-1");PermissionRespNodeVO permissionRespNodeVO = new PermissionRespNodeVO();BeanUtils.copyProperties(sysPermission,permissionRespNodeVO);permissionRespNodeVO.setTitle(sysPermission.getName());permissionRespNodeVO.setChildren(getChildren(sysPermission.getId(),sysPermissionList));result.add(permissionRespNodeVO);return result;}/*** 递归获取默认顶级菜单的子树** @param sysPermissionList* @return List<PermissionRespNodeVO>*/private List<PermissionRespNodeVO> getChildren(String id,List<SysPermission> sysPermissionList) {List<PermissionRespNodeVO> result = new ArrayList<>();if(CollectionUtils.isEmpty(sysPermissionList)){return result;}for(SysPermission sysPermission:sysPermissionList){if(sysPermission.getPid().equals(id) && sysPermission.getType() != 3){PermissionRespNodeVO permissionRespNodeVO = new PermissionRespNodeVO();BeanUtils.copyProperties(sysPermission,permissionRespNodeVO);permissionRespNodeVO.setTitle(sysPermission.getName());// 设置子节点permissionRespNodeVO.setChildren(getChildren(sysPermission.getId(),sysPermissionList));result.add(permissionRespNodeVO);}}return result;}

② Controller层

@GetMapping("/permission/tree")@ApiOperation(value = "菜单权限树接口-只递归查询到菜单接口")public DataResult<List<PermissionRespNodeVO>> getAllPermissionTreeExBtn(){DataResult result=DataResult.success();result.setData(sysPermissionService.selectAllMenuByTree());return result;}

③ 测试:获取菜单权限树

{"code": 0,"msg": "操作成功","data": [{"id": "0","url": null,"title": "默认顶级菜单","children": [{"id": "346df872-8964-4455-8afd-ffa6308fb18a","url": "","title": "组织管理","children": [{"id": "290c0240-0914-487c-b4e9-6635bf5ebfec","url": "/index/menus","title": "菜单权限管理","spread": true,"checked": false},{"id": "8f393e44-b585-4875-866d-71f88fea9659","url": "/index/depts","title": "部门管理","spread": true,"checked": false}],"spread": true,"checked": false}],"spread": true,"checked": false}]}

3. 新增菜单权限树

需求分析 :

点击添加,跳转到新增菜单权限页面

当新增的是目录时:

当新增的是菜单时:

当新增的是按钮时:

需求分析:

当类型是目录的时候,父类必须为目录类型 ;

当类型是菜单的时候,父级必须为目录类型,并且菜单的接口URL不能为空;

当类型是按钮的时候,父类必须为菜单类型,并且菜单的接口URL,授权标识,请求方式和授权标识不能为空;

菜单和按钮的URL是为了做接口跳转,授权标识是为了做接口的权限校验;

请求Vo类的封装:按照新增菜单权限页面封装请求Vo类

@Datapublic class PermissionAddReqVO {@ApiModelProperty(value = "菜单权限类型(1:目录;2:菜单;3:按钮)")@NotNull(message = "菜单权限类型不能为空")private Integer type;@ApiModelProperty(value = "菜单权限名称")@NotBlank(message = "菜单权限名称不能为空")private String name;@ApiModelProperty(value = "父级id")@NotNull(message = "所属菜单不能为空")private String pid;@ApiModelProperty(value = "接口地址")private String url;@ApiModelProperty(value = "请求方式 和url 配合使用 (我们用 路径匹配的方式做权限管理的时候用到)")private String method;@ApiModelProperty(value = "编码(前后端分离 前段对按钮显示隐藏控制 btn-permission-search 代表 菜单权限管理的列表查询按钮)")private String code;@ApiModelProperty(value = "排序码")private Integer orderNum;@ApiModelProperty(value = "状态1:正常 0:禁用")private Integer status;@ApiModelProperty(value = "菜单权限标识,shiro 适配restful")private String perms;}

接口开发 :

/*** 新增菜单权限** @param permissionAddReqVO 请求vo* @return SysPermission*/@Overridepublic SysPermission addPermission(PermissionAddReqVO permissionAddReqVO) {// 请求参数校验checkForm(permissionAddReqVO);SysPermission sysPermission = new SysPermission();BeanUtils.copyProperties(permissionAddReqVO,sysPermission);sysPermission.setId(UUID.randomUUID().toString());sysPermission.setCreateTime(new Date());sysPermissionDao.insertSelective(sysPermission);return sysPermission;}/*** 当类型是目录的时候,父类必须为目录类型 ;* 当类型是菜单的时候,父级必须为目录类型,并且菜单的接口URL不能为空;* 当类型是按钮的时候,父类必须为菜单类型,并且菜单的接口URL,授权标识,请求方式和授权标识不能为空;** @param permissionAddReqVO*/private void checkForm(PermissionAddReqVO permissionAddReqVO) {SysPermission parentPermission = sysPermissionDao.queryById(permissionAddReqVO.getPid());if(Objects.isNull(parentPermission)){throw new BusinessException(BaseResponseCode.OPERATION_ERROR);}switch (permissionAddReqVO.getType()){case 1:// 当类型是目录的时候,父类必须为目录类型 ;if(parentPermission.getType()!=1){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_CATALOG_ERROR);}break;case 2:// 当类型是菜单的时候,父级必须为目录类型,并且菜单的接口URL不能为空;if(parentPermission.getType()!=1){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_CATALOG_ERROR);}if(StringUtils.isEmpty(permissionAddReqVO.getMethod())){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_BTN_ERROR);}break;case 3:// 当类型是按钮的时候,父类必须为菜单类型,并且菜单的接口URL,授权标识,请求方式和授权标识不能为空;if(parentPermission.getType()!=2){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_BTN_ERROR);}if(StringUtils.isEmpty(permissionAddReqVO.getPerms())){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_URL_PERMS_NULL);}if(StringUtils.isEmpty(permissionAddReqVO.getUrl())){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_URL_NOT_NULL);}if(StringUtils.isEmpty(permissionAddReqVO.getMethod())){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_URL_METHOD_NULL);}if(StringUtils.isEmpty(permissionAddReqVO.getCode())){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_URL_CODE_NULL);}break;}}

② Controller层

@PostMapping("/permission")@ApiOperation(value = "新增菜单权限接口")public DataResult<SysPermission> addPermission(@RequestBody @Valid PermissionAddReqVO vo){DataResult result=DataResult.success();result.setData(sysPermissionService.addPermission(vo));return result;}

5. 角色管理

1. 分页获取角色数据

需求分析 :

分页查询所有角色数据

请求类封装:

@Datapublic class RolePageReqVO {@ApiModelProperty(value = "第几页")private int pageNum=1;@ApiModelProperty(value = "当前页的数量")private int pageSize;}

接口开发 :

① Controller

@PostMapping("/roles")@ApiOperation(value = "分页获取角色数据接口")public DataResult<PageVO<SysRole>> pageInfo(@RequestBody RolePageReqVO vo){DataResult result =DataResult.success();PageVO<SysRole> sysRolePageVO = sysRoleService.pageInfo(vo);result.setData(sysRolePageVO);return result;}

② Service

/*** 分页查询所有数据* @param vo* @return*/@Overridepublic PageVO<SysRole> pageInfo(RolePageReqVO vo) {PageHelper.startPage(vo.getPageNum(),vo.getPageSize());List<SysRole> sysRoleList = sysRoleDao.queryAll(vo);PageVO<SysRole> pageVO = PageUtils.getPageVO(sysRoleList);return pageVO;}

③ dao

<!--分页查询所有数据--><select id="queryAll" resultMap="SysRoleMap" parameterType="com.yingxue.lesson.vo.req.RolePageReqVO">select <include refid="selectFields"></include> from sys_role where deleted=1</select>

④ 测试

{"code": 0,"msg": "操作成功","data": {"totalRows": 2,"totalPages": 1,"pageNum": 1,"pageSize": 2,"curPageSize": 2,"list": [{"id": "b125dbb8-459e-409f-bd1f-d17966568eda","name": "超级管理员","description": "我是超级管理员","status": 1,"createTime": "-01-06T15:37:45.000+0000","updateTime": "-04-05T23:16:20.000+0000","deleted": 1},{"id": "fc674bde-29ee-4160-bd97-00761357f019","name": "test","description": "我是test","status": 1,"createTime": "-01-07T13:19:04.000+0000","updateTime": "-04-12T09:36:21.000+0000","deleted": 1}]}}

2. 新增角色

需求分析 :

需要选择关联权限:

需求分析:

新增角色时,需要填写角色名称,角色状态,备注等信息需要选择角色关联的菜单权限,而这个菜单权限树和我们新增菜单权限时的菜单权限树是不同的: 在新增菜单权限时,菜单树仅仅递归到菜单,并且包含顶级默认菜单,可以回看获取菜单权限树接口;在新增角色时,菜单树递归到按钮,并且不包含顶级默认菜单; 角色表是和权限表通过角色权限表关联的,因此不仅需要保存角色信息,还需要保存角色权限关联信息;

新增角色请求类封装:

@Datapublic class AddRoleReqVO {@ApiModelProperty(value = "角色名称")@NotBlank(message = "角色名称不能为空")private String name;@ApiModelProperty(value = "描述")private String description;@ApiModelProperty(value = "状态(1:正常0:弃用)")private Integer status;@ApiModelProperty(value = "拥有的权限id集合")private List<String> permissions;}

接口开发 - 获取菜单权限:

① Controller层

@GetMapping("/permission/tree/all")@ApiOperation(value = "菜单权限树接口-递归查询所有接口")public DataResult<List<PermissionRespNodeVO>> getAllPermissionTree(){DataResult result=DataResult.success();List<PermissionRespNodeVO> permissionRespNodeVOList = sysPermissionService.selectAllTree();result.setData(permissionRespNodeVOList);return result;}

② Service层

/*** 获取菜单权限树--递归到按钮* @return*/@Overridepublic List<PermissionRespNodeVO> selectAllTree() {// 查询出所有菜单List<SysPermission> sysPermissionList = sysPermissionDao.queryAll();// 查询顶级默认菜单SysPermission sysPermission = sysPermissionDao.queryByPid("-1");return getAllChildren(sysPermission.getId(), sysPermissionList);}/*** 递归获取子树 --- 递归到按钮* @param id* @param sysPermissionList* @return*/private List<PermissionRespNodeVO> getAllChildren(String id, List<SysPermission> sysPermissionList) {List<PermissionRespNodeVO> result = new ArrayList<>();for(SysPermission sysPermission:sysPermissionList){if(sysPermission.getPid().equals(id)){PermissionRespNodeVO permissionRespNodeVO = new PermissionRespNodeVO();BeanUtils.copyProperties(sysPermission,permissionRespNodeVO);permissionRespNodeVO.setTitle(sysPermission.getName());// 设置子节点permissionRespNodeVO.setChildren(getAllChildren(sysPermission.getId(),sysPermissionList));result.add(permissionRespNodeVO);}}return result;}

③ 测试

接口开发 - 新增角色:

① Controller层

@PostMapping("/role")@ApiOperation(value = "新增角色接口")public DataResult<SysRole> addRole(@RequestBody @Valid AddRoleReqVO vo){DataResult result =DataResult.success();SysRole sysRole = sysRoleService.addRole(vo);result.setData(sysRole);return result;}

② Service层

@Service("sysRoleService")public class SysRoleServiceImpl implements SysRoleService {@Resourceprivate SysRoleDao sysRoleDao;@Resourceprivate SysRolePermissionService sysRolePermissionService;/*** 新增角色* @param addRoleReqVO* @return*/@Transactional(rollbackFor = Exception.class)@Overridepublic SysRole addRole(AddRoleReqVO addRoleReqVO) {// 保存角色表信息SysRole sysRole = new SysRole();BeanUtils.copyProperties(addRoleReqVO,sysRole);sysRole.setId(UUID.randomUUID().toString());sysRole.setCreateTime(new Date());sysRole.setUpdateTime(new Date());sysRoleDao.insertSelective(sysRole);// 保存角色和权限关联关系表List<String> permissionList = addRoleReqVO.getPermissions();if(!CollectionUtils.isEmpty(permissionList)){RolePermissionOperationReqVO operationReqVO = new RolePermissionOperationReqVO();operationReqVO.setRoleId(sysRole.getId());operationReqVO.setPermissionIds(permissionList);sysRolePermissionService.addRolePermission(operationReqVO);}return sysRole;}}

@Datapublic class RolePermissionOperationReqVO {@ApiModelProperty(value = "角色id")private String roleId;@ApiModelProperty(value = "菜单权限集合")private List<String> permissionIds;}

@Service("sysRolePermissionService")public class SysRolePermissionServiceImpl implements SysRolePermissionService {@Resourceprivate SysRolePermissionDao sysRolePermissionDao;/*** 新增角色权限关联数据* @param operationReqVO*/@Overridepublic void addRolePermission(RolePermissionOperationReqVO operationReqVO) {List<String> permissionIds = operationReqVO.getPermissionIds();if(CollectionUtils.isEmpty(permissionIds)){return;}List<SysRolePermission> rolePermissionList = permissionIds.stream().map(permissionId -> {SysRolePermission sysRolePermission = new SysRolePermission();sysRolePermission.setId(UUID.randomUUID().toString());sysRolePermission.setRoleId(operationReqVO.getRoleId());sysRolePermission.setPermissionId(permissionId);sysRolePermission.setCreateTime(new Date());return sysRolePermission;}).collect(Collectors.toList());sysRolePermissionDao.insertBatch(rolePermissionList);}}

③ dao层

int insertBatch(@Param("entities") List<SysRolePermission> entities);

<insert id="insertBatch" keyProperty="id" useGeneratedKeys="true">insert into sys_role_permission(id, role_id, permission_id, create_time)values<foreach collection="entities" item="entity" separator=",">(#{entity.id}, #{entity.roleId}, #{entity.permissionId}, #{entity.createTime})</foreach></insert>

3. 多条件查询角色数据

需求分析 :

需求分析:

分页查询根据角色id查询根据角色名称查询根据开始时间到结束时间查询根据角色状态查询

请求类封装:

@Datapublic class RolePageReqVO {@ApiModelProperty(value = "第几页")private int pageNum=1;@ApiModelProperty(value = "当前页的数量")private int pageSize;@ApiModelProperty(value = "角色id")private String roleId;@ApiModelProperty(value = "角色名称")private String roleName;@ApiModelProperty(value = "角色状态")private Integer status;@ApiModelProperty(value = "开始时间")private String startTime;@ApiModelProperty(value = "结束时间")private String endTime;}

接口开发 :

@PostMapping("/roles")@ApiOperation(value = "分页获取角色数据接口")public DataResult<PageVO<SysRole>> pageInfo(@RequestBody RolePageReqVO vo){DataResult result =DataResult.success();PageVO<SysRole> sysRolePageVO = sysRoleService.pageInfo(vo);result.setData(sysRolePageVO);return result;}

@Overridepublic PageVO<SysRole> pageInfo(RolePageReqVO vo) {PageHelper.startPage(vo.getPageNum(),vo.getPageSize());List<SysRole> sysRoleList = sysRoleDao.queryAll(vo);PageVO<SysRole> pageVO = PageUtils.getPageVO(sysRoleList);return pageVO;}

<!--分页查询所有数据--><select id="queryAll" resultMap="SysRoleMap" parameterType="com.yingxue.lesson.vo.req.RolePageReqVO">select <include refid="selectFields"></include> from sys_role <where>deleted=1<if test="roleName !=null and roleName != ''">and name like concat('%',#{roleName},'%')</if><if test="roleId !=null and roleId !=''">and id=#{roleId}</if><if test="status !=null and status !='' or status==0">and status=#{status}</if><if test="startTime !=null and startTime !=''">and create_time >= #{startTime}</if><if test="endTime !=null and endTime !=''">and create_time <= #{endTime}</if></where></select>

6. 部门管理

1. 获取所有部门数据

需求分析 :

获取所有部门数据;

部门表中仅有pid,因此需要根据pid查询出上级部门名称;

封装响应类:响应类比原始表实体类中数据多了一个上级部门名称

@Datapublic class SysDept implements Serializable {// 省略.../*** 上级部门名称*/@Transient@JsonInclude(value = JsonInclude.Include.NON_NULL)private String pidName;}

① Controller层

@GetMapping("/depts")@ApiOperation(value = "查询所有部门数据接口")public DataResult<List<SysDept>> getAllDept(){DataResult result =DataResult.success();List<SysDept> deptList = sysDeptService.selectAll();result.setData(deptList);return result;}

② Service层

@Overridepublic List<SysDept> selectAll() {List<SysDept> sysDeptList = sysDeptDao.queryAll();// 查询上级部门名称for (SysDept sysDept : sysDeptList) {SysDept dept = sysDeptDao.queryById(sysDept.getPid());if(dept!=null){sysDept.setPidName(dept.getName());} }return sysDeptList;}

③ 测试

2. 获取部门树

需求分析:

获取部门树形结构;id在选择所属部门时,需要传给后端,因为响应数据中需要包含id;”默认顶级部门“是内置节点;

@Datapublic class DeptRespNodeVO {@ApiModelProperty(value = "部门id")private String id;@ApiModelProperty(value = "部门名称")private String title;@ApiModelProperty("是否展开 默认true")private boolean spread=true;@JsonInclude(value = JsonInclude.Include.NON_EMPTY)@ApiModelProperty(value = "子集叶子节点")private List<DeptRespNodeVO> children;}

接口开发 :

① Controller层

@GetMapping("/dept/tree")@ApiOperation(value = "部门树形结构列表接口")public DataResult<List<DeptRespNodeVO>> getDeptTree(@RequestParam(required = false) String deptId){DataResult result=DataResult.success();List<DeptRespNodeVO> deptRespNodeVOList = sysDeptService.deptTreeList();result.setData(deptRespNodeVOList);return result;}

② Service层

@PostConstructpublic void init(){List<SysDept> sysDeptList = sysDeptDao.queryAll();if(CollectionUtils.isEmpty(sysDeptList)){SysDept sysDept = new SysDept();sysDept.setPid("-1");sysDept.setId("0");sysDept.setName("默认顶级部门");// 部门编号Long deptCount = redisTemplate.opsForValue().increment(Constant.DEPT_CODE_KEY, 1);String deptCode = CodeUtil.deptCode(String.valueOf(deptCount),7,"0");sysDept.setDeptNo(deptCode);// 部门层级关系sysDept.setRelationCode(deptCode);sysDeptDao.insertSelective(sysDept);}}/*** 获取部门树* @return*/@Overridepublic List<DeptRespNodeVO> deptTreeList() {List<DeptRespNodeVO> result = new ArrayList<>();// 查询所有部门List<SysDept> sysDeptList = sysDeptDao.queryAll();// 查询顶级父级部门SysDept sysDept = sysDeptDao.queryById("0");DeptRespNodeVO deptRespNodeVO = new DeptRespNodeVO();BeanUtils.copyProperties(sysDept,deptRespNodeVO);deptRespNodeVO.setTitle(sysDept.getName());// 设置顶级父级部门的子节点deptRespNodeVO.setChildren(getChildren(sysDept.getId(),sysDeptList));result.add(deptRespNodeVO);return result;}/*** 递归获取子树* @param id* @param sysDeptList* @return*/private List<DeptRespNodeVO> getChildren(String id, List<SysDept> sysDeptList) {List<DeptRespNodeVO> result = new ArrayList<>();for(SysDept sysDept:sysDeptList){if(sysDept.getPid().equals(id)){DeptRespNodeVO deptRespNodeVO = new DeptRespNodeVO();BeanUtils.copyProperties(sysDept,deptRespNodeVO);deptRespNodeVO.setTitle(sysDept.getName());deptRespNodeVO.setChildren(getChildren(sysDept.getId(),sysDeptList));result.add(deptRespNodeVO);}}return result;}

③ 测试

{"code": 0,"msg": "操作成功","data": [{"id": "0","title": "默认顶级部门","spread": true,"children": [{"id": "06e8066d-fbb9-4904-b71a-e75fc06b19a6","title": "迎学教育总公司","spread": true,"children": [{"id": "7610b9c4-9348-4102-afc3-0e598216ccb6","title": "迎学教育深圳分部","spread": true}]}]}]}

3. 新增部门

需求分析 :

需求分析:

新增部门时需要维持部门的层级关系,即relationCode字段;deptCode用来设置relationCode;relationCode = 父级部门的deptCode+当前部门的deptCode;

请求体实体类封装:

@Datapublic class DeptAddReqVO {@ApiModelProperty(value = "部门名称")@NotBlank(message = "部门名称不能为空")private String name;@ApiModelProperty(value = "父级id 一级为 0")@NotBlank(message = "父级id不能为空")private String pid;@ApiModelProperty(value = "部门经理名称")private String managerName;@ApiModelProperty(value = "部门经理电话")private String phone;@ApiModelProperty(value = "机构状态(1:正常;0:弃用)")private Integer status;}

① Controller层

@PostMapping("/dept")@ApiOperation(value = "新增部门数据接口")public DataResult<SysDept> addDept(@RequestBody @Valid DeptAddReqVO vo){DataResult result=DataResult.success();SysDept sysDept = sysDeptService.addDept(vo);result.setData(sysDept);return result;}

② Service层

/*** 新增部门* @param deptAddReqVO* @return*/@Overridepublic SysDept addDept(DeptAddReqVO deptAddReqVO) {// 部门基本信息SysDept sysDept = new SysDept();BeanUtils.copyProperties(deptAddReqVO,sysDept);sysDept.setId(UUID.randomUUID().toString());sysDept.setCreateTime(new Date());// 部门层级关系// deptCode是为了维护relationCode,即维护层级关系// incr命令将key中储存的数字值增1。如果key不存在,那么key的值会先被初始化为0,然后再执行incr操作。Long deptCount = redisTemplate.opsForValue().increment(Constant.DEPT_CODE_KEY, 1);String deptCode = CodeUtil.deptCode(String.valueOf(deptCount),7,"0");// relationCode=父级部门的deptCode+当前部门的deptCode// 获取父级部门的deptCodeSysDept parent = sysDeptDao.queryById(sysDept.getPid());if(parent==null){log.info("父级部门不存在{}",sysDept.getPid());throw new BusinessException(BaseResponseCode.DATA_ERROR);}String relationCode = parent.getRelationCode()+deptCode;sysDept.setDeptNo(deptCode);sysDept.setRelationCode(relationCode);sysDeptDao.insertSelective(sysDept);return sysDept;}

工具类:

public class CodeUtil {private static final String DEPT_TPYE="YXD";private static final String PERMISSION_TPYE="YXP";/*** 右补位,左对齐* @pparam oriStr 原字符串* @param len 目标字符串长度* @param alexin 补位字符* @return 目标字符串* 以alexin 做为补位*/public static String padRight(String oriStr,int len,String alexin){// oriStr=1// YXDString str = "";int strlen = oriStr.length();if(strlen < len){for(int i=0;i<len-strlen;i++){// 000000str=str+alexin;}}// 0000001str=str+oriStr;return str;}/*** 获取机构编码 YXD0000001* @return java.lang.String* @throws*/public static String deptCode(String oriStr,int len,String alexin){// YXD0000001return DEPT_TPYE+padRight(oriStr, len, alexin);}}

7. 用户管理

1. 分页查询用户数据

① Controller层

@PostMapping("/users")@ApiOperation(value = "分页获取用户列表接口")public DataResult<PageVO<SysUser>> pageInfo(@RequestBody UserPageReqVO vo){DataResult<PageVO<SysUser>> result=DataResult.success();result.setData(sysUserService.pageInfo(vo));return result;}

② Service层

@Overridepublic PageVO<SysUser> pageInfo(UserPageReqVO vo) {PageHelper.startPage(vo.getPageNum(),vo.getPageSize());List<SysUser> sysUsers = sysUserDao.selectAll(vo);return PageUtils.getPageVO(sysUsers);}

2. 新增用户

需求分析 :

请求实体类封装:

@Datapublic class UserAddReqVO {@ApiModelProperty(value = "账号")@NotBlank(message = "账号不能为空")private String username;@ApiModelProperty(value = "密码")@NotBlank(message = "密码不能为空")private String password;@ApiModelProperty(value = "手机号")private String phone;@ApiModelProperty(value = "所属部门")@NotBlank(message = "所属部门不能为空")private String deptId;@ApiModelProperty(value = "创建来源(1.web 2.android 3.ios )")private String createWhere;@ApiModelProperty(value = "账户状态(1.正常 2.锁定 )")private Integer status;}

① Controller层

@PostMapping("/user")@ApiOperation(value = "新增用户接口")public DataResult addUser(@RequestBody @Valid UserAddReqVO vo){DataResult result=DataResult.success();sysUserService.addUser(vo);return result;}

② Service层

/*** 新增用户* @param userAddReqVO*/@Overridepublic void addUser(UserAddReqVO userAddReqVO) {SysUser sysUser = new SysUser();BeanUtils.copyProperties(userAddReqVO,sysUser);sysUser.setId(UUID.randomUUID().toString());// 对密码加密后保存String salt=PasswordUtils.getSalt();String ecdPwd=PasswordUtils.encode(userAddReqVO.getPassword(),salt);sysUser.setSalt(salt);sysUser.setPassword(ecdPwd);sysUser.setCreateTime(new Date());sysUserDao.insertSelective(sysUser);}

3. 多条件查询用户列表

需求分析:

封装请求体

@Datapublic class UserPageReqVO {@ApiModelProperty(value = "当前第几页")private Integer pageNum=1;@ApiModelProperty(value = "当前页数量")private Integer pageSize=10;@ApiModelProperty(value = "用户id")private String userId;@ApiModelProperty(value = "账号")private String username;@ApiModelProperty(value = "昵称")private String nickName;@ApiModelProperty(value = "账户状态(1.正常 2.锁定 ")private Integer status;@ApiModelProperty(value = "开始时间")private String startTime;@ApiModelProperty(value = "结束时间")private String endTime;}

① Controller层

@PostMapping("/users")@ApiOperation(value = "分页获取用户列表接口")public DataResult<PageVO<SysUser>> pageInfo(@RequestBody UserPageReqVO vo){DataResult<PageVO<SysUser>> result=DataResult.success();result.setData(sysUserService.pageInfo(vo));return result;}

② Service层

@Overridepublic PageVO<SysUser> pageInfo(UserPageReqVO vo) {PageHelper.startPage(vo.getPageNum(),vo.getPageSize());List<SysUser> sysUsers = sysUserDao.selectAll(vo);return PageUtils.getPageVO(sysUsers);}

③ Dao层

<select id="selectAll" resultType="com.yingxue.lesson.entity.SysUser">select <include refid="select_fields"></include>from sys_user<where>deleted=1<if test="userId !=null and userId != ''">and id=#{userId}</if><if test="username !=null and username != ''">and username like concat('%',#{username},'%')</if><if test="nickName !=null and nickName != ''">and nick_name like concat('%',#{nickName},'%')</if><if test="status !=null and status != '' or status==0">and status =#{status}</if><if test="startTime !=null and startTime != ''">and create_time &gt;= #{startTime}</if><if test="endTime !=null and endTime != ''">and create_time &lt;= #{endTime}</if></where></select>

④ 测试

4. 赋予用户角色

这种穿梭框需求如何分析接口?将来再次遇到这种穿梭框需求怎么分析?

需求分析:

① 点击赋予角色按钮,进入赋予角色穿梭框页面,此时需要回显页面原有数据,该接口响应数据包括两部分:

右侧查询当前用户拥有的角色列表;左侧查询所有的角色列表;

响应结果:

@Datapublic class UserOwnRoleRespVO {@ApiModelProperty(value = "拥有角色集合")private List<String> ownRoles;@ApiModelProperty(value = "所有角色列表")private List<SysRole> allRole;}

② 当赋予用户角色后,需要保存用户拥有的角色信息,即保存用户角色关联数据:

获取前端数据,封装用户和角色关联的数据;先把以前的关联数据删除;再插入现在关联的数据;把用户标记起来然后主动去刷新;

请求数据:

@Datapublic class UserOwnRoleReqVO {@ApiModelProperty(value = "用户id")@NotBlank(message = "用户id不能为空")private String userId;@ApiModelProperty("赋予用户的角色id集合")private List<String> roleIds;}

1. 进入赋予角色页面回显数据 - 接口开发:

@RestController@RequestMapping("api")public class SysUserController {@GetMapping("/user/roles/{userId}")@ApiOperation(value = "查询用户拥有的角色数据接口")public DataResult<UserOwnRoleRespVO> getUserOwnRole(@PathVariable("userId") String userId){DataResult result=DataResult.success();result.setData(sysUserService.getUserOwnRole(userId));return result;}}

@Service("sysUserService")public class SysUserServiceImpl implements SysUserService {@Overridepublic UserOwnRoleRespVO getUserOwnRole(String userId) {UserOwnRoleRespVO respVO=new UserOwnRoleRespVO();// 用户拥有的角色respVO.setOwnRoles(sysUserRoleService.getRoleIdsByUserId(userId));// 用户所有的角色respVO.setAllRole(sysRoleService.selectAll());return respVO;}}

① 查询用户拥有的角色列表:

@Repositorypublic interface SysUserRoleDao {List<String> getRoleIdsByUserId(String userId);}

<select id="getRoleIdsByUserId" resultType="java.lang.String">select role_id from sys_user_rolewhere user_id=#{userId}</select>

② 查询用户所有的角色列表:

@Service("sysRoleService")public class SysRoleServiceImpl implements SysRoleService {@Overridepublic List<SysRole> selectAll() {// 分页查询数据,当参数为null时,就是查询所有return sysRoleDao.queryAll(new RolePageReqVO());}}

<!--分页查询所有数据--><select id="queryAll" resultMap="SysRoleMap" parameterType="com.yingxue.lesson.vo.req.RolePageReqVO">select <include refid="selectFields"></include> from sys_role<where>deleted=1<if test="roleName !=null and roleName != ''">and name like concat('%',#{roleName},'%')</if><if test="roleId !=null and roleId !=''">and id=#{roleId}</if><if test="status !=null and status !='' or status==0">and status=#{status}</if><if test="startTime !=null and startTime !=''">and create_time &gt;= #{startTime}</if><if test="endTime !=null and endTime !=''">and create_time &lt;= #{endTime}</if></where></select>

2. 保存用户拥有的角色信息 - 接口开发:

@PutMapping("/user/roles")@ApiOperation(value = "保存用户拥有的角色信息接口")public DataResult saveUserOwnRole(@RequestBody @Valid UserOwnRoleReqVO vo){DataResult result=DataResult.success();userService.setUserOwnRole(vo);return result;}

@Service("sysUserService")public class SysUserServiceImpl implements SysUserService {@Overridepublic void setUserOwnRole(UserOwnRoleReqVO vo) {sysUserRoleService.addUserRoleInfo(vo);/*** 标记用户 要主动去刷新* JWT_REFRESH_KEY 主动去刷新 token key(适用场景 比如修改了用户的角色/权限去刷新token)*/redisService.set(Constant.JWT_REFRESH_KEY+vo.getUserId(),vo.getUserId(),tokenSettings.getAccessTokenExpireTime().toMillis(),TimeUnit.MILLISECONDS);/*** 清除用户授权数据缓存*/redisService.delete(Constant.IDENTIFY_CACHE_KEY+vo.getUserId());}}

@Service("sysUserRoleService")public class SysUserRoleServiceImpl implements SysUserRoleService {@Resourceprivate SysUserRoleDao sysUserRoleDao;@Override@Transactional(rollbackFor = Exception.class)public void addUserRoleInfo(UserOwnRoleReqVO vo) {// 删除他们关联数据sysUserRoleDao.removeRoleByUserId(vo.getUserId());if(vo.getRoleIds()==null||vo.getRoleIds().isEmpty()){return;}// 保存用户拥有的角色List<SysUserRole> list=new ArrayList<>();for (String roleId: vo.getRoleIds()) {SysUserRole sysUserRole=new SysUserRole();sysUserRole.setId(UUID.randomUUID().toString());sysUserRole.setCreateTime(new Date());sysUserRole.setUserId(vo.getUserId());sysUserRole.setRoleId(roleId);list.add(sysUserRole);}int i = sysUserRoleDao.batchInsertUserRole(list);if(i==0){throw new BusinessException(BaseResponseCode.OPERATION_ERROR);}}}

<!-- @Description:删除该用户关联的角色--><delete id="removeRoleByUserId">delete from sys_user_role where user_id=#{userId}</delete>

<!-- @Description:批量插入用户角色关联数据--><insert id="batchInsertUserRole" parameterType="com.yingxue.lesson.entity.SysUserRole">INSERT into sys_user_role(id,user_id,role_id,create_time)values<foreach collection="list" item="item" index="index" separator=",">(#{item.id},#{item.userId},#{item.roleId},#{item.createTime})</foreach></insert>

测试:

进入赋予角色页面,可以看到所有角色和用户拥有的角色:

5. jwt 自动刷新

jwt 刷新有两种情况要考虑?

一种是管理员修改了该用户的角色/权限(需要主动去刷新)。另一种是 jwt 过期要刷新。 刷新成功后生成新的token自动再刷新当前请求接口。

@GetMapping("/user/token")@ApiOperation(value = "jwt token 刷新接口")public DataResult<String> refreshToken(HttpServletRequest request){String refreshToken=request.getHeader(Constant.REFRESH_TOKEN);String newAccessToken = sysUserService.refreshToken(refreshToken);DataResult result=DataResult.success();result.setData(newAccessToken);return result;}

@Service("sysUserService")@Slf4jpublic class SysUserServiceImpl implements SysUserService {@Resourceprivate SysUserDao sysUserDao;@Overridepublic String refreshToken(String refreshToken) {// refreshToken是否过期,它是否被加如了黑名if(!JwtTokenUtil.validateToken(refreshToken)|| redisService.hasKey(Constant.JWT_REFRESH_TOKEN_BLACKLIST+refreshToken)){throw new BusinessException(BaseResponseCode.TOKEN_ERROR);}String userId=JwtTokenUtil.getUserId(refreshToken);String username=JwtTokenUtil.getUserName(refreshToken);log.info("userId={}",userId);Map<String,Object> claims=new HashMap<>();// 重新获取用户的角色claims.put(Constant.ROLES_INFOS_KEY,getRolesByUserId(userId));// 重新获取用户的权限信息claims.put(Constant.PERMISSIONS_INFOS_KEY,getPermissionsByUserId(userId));// 重新获取用户的姓名claims.put(Constant.JWT_USER_NAME,username);// 给客户端签发一个新的access_tokenString newAccessToken= JwtTokenUtil.getAccessToken(userId,claims);return newAccessToken;}}

@Slf4jpublic class CustomHashedCredentialsMatcher extends HashedCredentialsMatcher {@Autowiredprivate RedisService redisService;@Overridepublic boolean doCredentialsMatch(AuthenticationToken token, AuthenticationInfo info) {CustomUsernamePasswordToken customUsernamePasswordToken= (CustomUsernamePasswordToken) token;String accessToken= (String) customUsernamePasswordToken.getCredentials();String userId= JwtTokenUtil.getUserId(accessToken);log.info("doCredentialsMatch....userId={}",userId);//判断用户是否被删除if(redisService.hasKey(Constant.DELETED_USER_KEY+userId)){throw new BusinessException(BaseResponseCode.ACCOUNT_HAS_DELETED_ERROR);}//判断是否被锁定if(redisService.hasKey(Constant.ACCOUNT_LOCK_KEY+userId)){throw new BusinessException(BaseResponseCode.ACCOUNT_LOCK);}/*** 判断用户是否退出登录*/if(redisService.hasKey(Constant.JWT_ACCESS_TOKEN_BLACKLIST+accessToken)){throw new BusinessException(BaseResponseCode.TOKEN_ERROR);}//校验tokenif(!JwtTokenUtil.validateToken(accessToken)){throw new BusinessException(BaseResponseCode.TOKEN_PAST_DUE);}/*** 判断用户是否被标记了*/if(redisService.hasKey(Constant.JWT_REFRESH_KEY+userId)){/*** 判断用户是否已经刷新过*/if(redisService.getExpire(Constant.JWT_REFRESH_KEY+userId, TimeUnit.MILLISECONDS)>JwtTokenUtil.getRemainingTime(accessToken)){throw new BusinessException(BaseResponseCode.TOKEN_PAST_DUE);}}return true;}}

6. 编辑用户

请求数据:

@Datapublic class UserUpdateReqVO {@ApiModelProperty(value = "用户id")@NotBlank(message = "用户id不能为空")private String id;@ApiModelProperty(value = "账号")private String username;@ApiModelProperty(value = "密码")private String password;@ApiModelProperty(value = "手机号")private String phone;@ApiModelProperty(value = "账户状态(1.正常 2.锁定 )")private Integer status;@ApiModelProperty(value = "所属部门")private String deptId;}

接口开发:

@PutMapping("/user")@ApiOperation(value ="列表修改用户信息接口")public DataResult updateUserInfo(@RequestBody @Valid UserUpdateReqVO vo, HttpServletRequest request){String accessToken=request.getHeader(Constant.ACCESS_TOKEN);String userId= JwtTokenUtil.getUserId(accessToken);DataResult result=DataResult.success();sysUserService.updateUserInfo(vo,userId);return result;}

@Overridepublic void updateUserInfo(UserUpdateReqVO vo, String operationId) {SysUser sysUser=new SysUser();BeanUtils.copyProperties(vo,sysUser);sysUser.setUpdateTime(new Date());sysUser.setUpdateId(operationId);// 设置密码if(StringUtils.isEmpty(vo.getPassword())){sysUser.setPassword(null);}else {String salt=PasswordUtils.getSalt();String endPwd=PasswordUtils.encode(vo.getPassword(),salt);sysUser.setSalt(salt);sysUser.setPassword(endPwd);}int i = sysUserDao.updateByPrimaryKeySelective(sysUser);if(i!=1){throw new BusinessException(BaseResponseCode.OPERATION_ERROR);}if(vo.getStatus()==2){// 用户被锁定redisService.set(Constant.ACCOUNT_LOCK_KEY+vo.getId(),vo.getId());}else {// 解除锁定redisService.delete(Constant.ACCOUNT_LOCK_KEY+vo.getId());}}

<update id="updateByPrimaryKeySelective" parameterType="com.yingxue.lesson.entity.SysUser">update sys_user<set><if test="username != null">username = #{username,jdbcType=VARCHAR},</if><if test="salt != null">salt = #{salt,jdbcType=VARCHAR},</if><if test="password != null">`password` = #{password,jdbcType=VARCHAR},</if><if test="phone != null">phone = #{phone,jdbcType=VARCHAR},</if><if test="deptId != null">dept_id = #{deptId,jdbcType=VARCHAR},</if><if test="realName != null">real_name = #{realName,jdbcType=VARCHAR},</if><if test="nickName != null">nick_name = #{nickName,jdbcType=VARCHAR},</if><if test="email != null">email = #{email,jdbcType=VARCHAR},</if><if test="status != null">`status` = #{status,jdbcType=TINYINT},</if><if test="sex != null">sex = #{sex,jdbcType=TINYINT},</if><if test="deleted != null">deleted = #{deleted,jdbcType=TINYINT},</if><if test="createId != null">create_id = #{createId,jdbcType=VARCHAR},</if><if test="updateId != null">update_id = #{updateId,jdbcType=VARCHAR},</if><if test="createWhere != null">create_where = #{createWhere,jdbcType=TINYINT},</if><if test="createTime != null">create_time = #{createTime,jdbcType=TIMESTAMP},</if><if test="updateTime != null">update_time = #{updateTime,jdbcType=TIMESTAMP},</if></set>where id = #{id,jdbcType=VARCHAR}</update>

7. 删除用户

@DeleteMapping("/user")@ApiOperation(value = "批量/删除用户接口")public DataResult deletedUsers(@RequestBody @ApiParam(value = "用户id集合") List<String> list, HttpServletRequest request){String accessToken=request.getHeader(Constant.ACCESS_TOKEN);String operationId=JwtTokenUtil.getUserId(accessToken);sysUserService.deletedUsers(list,operationId);DataResult result=DataResult.success();return result;}

@Overridepublic void deletedUsers(List<String> list, String operationId) {SysUser sysUser=new SysUser();sysUser.setUpdateId(operationId);sysUser.setUpdateTime(new Date());int i = sysUserDao.deletedUsers(sysUser, list);if(i==0){throw new BusinessException(BaseResponseCode.OPERATION_ERROR);}for (String userId: list) {redisService.set(Constant.DELETED_USER_KEY+userId,userId,tokenSettings.getRefreshTokenExpireAppTime().toMillis(),TimeUnit.MILLISECONDS);/*** 清楚用户授权数据缓存*/redisService.delete(Constant.IDENTIFY_CACHE_KEY+userId);}}

<!-- @Description:批量/删除用户--><update id="deletedUsers" parameterType="com.yingxue.lesson.entity.SysUser">update sys_user<set>deleted=0,<if test="sysUser.updateId !=null and sysUser.updateId !=''">update_id=#{sysUser.updateId},</if><if test="sysUser.updateTime !=null">update_time=#{sysUser.updateTime,jdbcType=TIMESTAMP},</if></set>where id in<foreach collection="list" open="(" close=")" separator="," item="item">#{item}</foreach></update>

8. 菜单权限管理

1. 编辑菜单权限

请求数据:

@Datapublic class PermissionUpdateReqVO {@ApiModelProperty(value = "id")@NotBlank(message = "id 不能为空")private String id;@ApiModelProperty(value = "状态1:正常 0:禁用")private Integer status;@ApiModelProperty(value = "菜单权限名称")private String name;@ApiModelProperty(value = "菜单权限标识,shiro 适配restful")private String perms;@ApiModelProperty(value = "接口地址")private String url;@ApiModelProperty(value = "请求方式 和url 配合使用 (我们用 路径匹配的方式做权限管理的时候用到)")private String method;@ApiModelProperty(value = "父级id")private String pid;@ApiModelProperty(value = "排序码")private Integer orderNum;@ApiModelProperty(value = "菜单权限类型(1:目录;2:菜单;3:按钮)")private Integer type;@ApiModelProperty(value = "编码(前后端分离 前段对按钮显示隐藏控制 btn-permission-search 代表 菜单权限管理的列表查询按钮)")private String code;}

@PutMapping("/permission")@ApiOperation(value = "编辑菜单权限接口")public DataResult updatePermission(@RequestBody @Valid PermissionUpdateReqVO vo){sysPermissionService.updatePermission(vo);DataResult result=DataResult.success();return result;}

@Overridepublic void updatePermission(PermissionUpdateReqVO vo) {//校验数据SysPermission update=new SysPermission();BeanUtils.copyProperties(vo,update);verifyForm(update);SysPermission sysPermission = sysPermissionDao.selectByPrimaryKey(vo.getId());if(sysPermission==null){log.info("传入的id在数据库中不存在");throw new BusinessException(BaseResponseCode.DATA_ERROR);}if(!sysPermission.getPid().equals(vo.getPid())|| !sysPermission.getStatus().equals(vo.getStatus())){//所属菜单发生了变化或者权限状态发生了变化要校验该权限是否存在子集List<SysPermission> sysPermissions = sysPermissionDao.selectChild(vo.getId());if(!sysPermissions.isEmpty()){throw new BusinessException(BaseResponseCode.OPERATION_MENU_PERMISSION_UPDATE);}}update.setUpdateTime(new Date());int i = sysPermissionDao.updateByPrimaryKeySelective(update);if(i!=1){throw new BusinessException(BaseResponseCode.OPERATION_ERROR);}// 判断授权标识符是否发生了变化(权限标识符发生了变化,或者权限状态发生了变化)if(!sysPermission.getPerms().equals(vo.getPerms())|| !sysPermission.getStatus().equals(vo.getStatus())){List<String> roleIdsByPermissionId = sysRolePermissionService.getRoleIdsByPermissionId(vo.getId());if(!roleIdsByPermissionId.isEmpty()){List<String> userIdsByRoleIds = sysUserRoleService.getUserIdsByRoleIds(roleIdsByPermissionId);if(!userIdsByRoleIds.isEmpty()){for (String userId:userIdsByRoleIds) {redisService.set(Constant.JWT_REFRESH_KEY+userId,userId,tokenSettings.getAccessTokenExpireTime().toMillis(), TimeUnit.MILLISECONDS);/*** 清楚用户授权数据缓存*/redisService.delete(Constant.IDENTIFY_CACHE_KEY+userId);}}}}}

2. 删除菜单权限

本内容不代表本网观点和政治立场,如有侵犯你的权益请联系我们处理。
网友评论
网友评论仅供其表达个人看法,并不表明网站立场。