上一篇地址:【清晨平台记录二】连接数据库实现用户登录
本篇代码地址:Gitee 地址 注意是 project03 分支哦
接口文档地址:Apifox工具编写的接口文档
目录
4.用户模块—设计思路
4.1设计思路
4.1.1逻辑思路
整个平台的用户模块思路图就是上面画的,其中用户分为平台后台用户(即平台管理用户)与平台前台用户(即平台使用用户),平台前台用户又分为前台用户和前台子用户,前台子用户是前台用户的名下的子账号;
所以:
- 用户:无论平台管理用户还是平台使用用户,都会有名下的N个子用户,对于平台而言,平台管理用户只有一个,其名下有N个子用户,平台使用用户有N个,并且每个使用用户其名下也会有N个子用户。
- 角色:无论管理用户还是使用用户,其名下都是各自的N个角色;
- 用户组:无论管理用户还是使用用户,其名下都是各自的N个用户组;
- 导航菜单权限:无论管理用户/子用户还是使用用户/子用户,其名下都是各自的N个导航菜单权限;
对于用户,后文一律统称:
平台后台用户(即平台管理用户),统称:后台用户
平台前台用户,统称:父用户
平台前台子用户,统称:子用户
4.1.2关系图
从图中我们可以看出,用户模块目前主要分为这四类,用户与用户组是多对多,用户与角色是多对多,角色与用户组是多对多,角色与菜单是多对多;
4.2数据库设计
4.2.1表关系
由于这四类都是多对多的关系,我们就直接添加关联表,而不在各个表里面引入关联的id。
其中,
用户表中需要记录子用户的父用户id,如果用户本身就是父用户,则默认记录 0 ;
角色、用户组表都需要记录所属的用户id;
用户、菜单表都是多级别的,都需要记录父级id;
具体表单设计看代码:document/sql
数据库字段的设计规范可以看这篇文章:数据库字段添加:什么时候用int、用varchar、用char 什么时候适合用什么类型
PS:开始之前记得先将代码里面的用户登录模块修改一下,因为我们修改了数据库用户表字段,所以需要修改一下代码,否则会测试不通过哦~主要修改: SysUser 实体类和持久层 mapper xml、LoginUser 里的 dept,修改后的内容看代码~
4.2.2数据表说明
1.菜单表:
CREATE TABLE `sys_menu` (
`menu_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '菜单ID',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父菜单ID,无父菜单默认为0',
`menu_name` varchar(50) NOT NULL COMMENT '菜单名称',
`menu_use_type` char(1) NOT NULL DEFAULT '0' COMMENT '菜单使用类型(0后台管理菜单,1前台管理菜单)',
`sort_num` int(11) DEFAULT '0' COMMENT '显示顺序',
`path` varchar(200) DEFAULT '' COMMENT '路由地址',
`component` varchar(255) DEFAULT NULL COMMENT '组件路径',
`query` varchar(255) DEFAULT NULL COMMENT '路由参数',
`perms` varchar(100) DEFAULT NULL COMMENT '权限标识',
`icon` varchar(100) DEFAULT '#' COMMENT '菜单图标',
`is_frame` char(1) DEFAULT '1' COMMENT '是否为外链(0是 1否)',
`is_cache` char(1) DEFAULT '0' COMMENT '是否缓存(0缓存 1不缓存)',
`menu_type` char(1) DEFAULT '' COMMENT '菜单类型(M目录 C菜单 F按钮)',
`visible` char(1) DEFAULT '0' COMMENT '是否显示(0显示 1隐藏)',
`status` char(1) DEFAULT '0' COMMENT '菜单状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT '' COMMENT '备注',
PRIMARY KEY (`menu_id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8 COMMENT='菜单权限表';
重点看:
- 每个菜单都有上级parentid;
- 菜单使用类型来区分是 后台用户和父用户的;
- 我们不需要存储菜单所属于的用户id,因为我们是根据用户所属角色来获取可使用菜单的,会根据角色在 sys_role_menu 表里获取。
2.用户组表
CREATE TABLE `sys_usergroup` (
`usergroup_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户组id',
`user_id` bigint(20) NOT NULL COMMENT '所属用户ID',
`parent_id` bigint(20) DEFAULT '0' COMMENT '父用户组id',
`ancestors` varchar(50) DEFAULT '' COMMENT '祖级列表,逗号分割',
`usergroup_name` varchar(30) DEFAULT '' COMMENT '用户组名称',
`sort_num` int(11) DEFAULT '0' COMMENT '显示顺序',
`status` char(1) DEFAULT '0' COMMENT '用户组状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT '' COMMENT '备注',
PRIMARY KEY (`usergroup_id`)
) ENGINE=InnoDB AUTO_INCREMENT=11 DEFAULT CHARSET=utf8 COMMENT='用户组表';
3.角色表,角色菜单表,角色用户组表
CREATE TABLE `sys_role` (
`role_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '角色ID',
`user_id` bigint(20) NOT NULL COMMENT '所属用户ID',
`role_name` varchar(30) NOT NULL COMMENT '角色名称',
`sort_num` int(11) DEFAULT '0' COMMENT '显示顺序',
`data_scope` char(1) DEFAULT '1' COMMENT '数据范围(1:全部数据权限 2:自定数据权限 3:本用户组数据权限 4:本用户组及以下数据权限 5:仅本人数据权限)',
`status` char(1) NOT NULL COMMENT '角色状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者username',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者username',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`role_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='角色信息表';
CREATE TABLE `sys_role_menu` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`menu_id` bigint(20) NOT NULL COMMENT '菜单ID',
PRIMARY KEY (`role_id`,`menu_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色和菜单关联表';
CREATE TABLE `sys_role_usergroup` (
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
`usergroup_id` bigint(20) NOT NULL COMMENT '用户组ID',
PRIMARY KEY (`role_id`,`usergroup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='角色和用户组关联表';
4.用户表,用户角色表,用户用户组表
CREATE TABLE `sys_user` (
`user_id` bigint(20) NOT NULL AUTO_INCREMENT COMMENT '用户ID',
`parent_user_id` bigint(20) DEFAULT '0' COMMENT '父用户ID,没有则为 0',
`user_name` varchar(30) NOT NULL COMMENT '用户账号',
`nick_name` varchar(30) NOT NULL COMMENT '用户昵称',
`user_type` varchar(2) DEFAULT '00' COMMENT '用户类型(00后台用户,10前台用户,11前台子用户)',
`email` varchar(50) DEFAULT '' COMMENT '用户邮箱',
`phonenumber` varchar(11) DEFAULT '' COMMENT '手机号码',
`sex` char(1) DEFAULT '0' COMMENT '用户性别(0男 1女 2未知)',
`avatar` varchar(200) DEFAULT '' COMMENT '头像地址',
`password` varchar(100) DEFAULT '' COMMENT '密码',
`login_ip` varchar(128) DEFAULT '' COMMENT '最后登录IP',
`login_date` datetime DEFAULT NULL COMMENT '最后登录时间',
`status` char(1) DEFAULT '0' COMMENT '帐号状态(0正常 1停用)',
`del_flag` char(1) DEFAULT '0' COMMENT '删除标志(0代表存在 2代表删除)',
`create_by` varchar(64) DEFAULT '' COMMENT '创建者',
`create_time` datetime DEFAULT NULL COMMENT '创建时间',
`update_by` varchar(64) DEFAULT '' COMMENT '更新者',
`update_time` datetime DEFAULT NULL COMMENT '更新时间',
`remark` varchar(500) DEFAULT NULL COMMENT '备注',
PRIMARY KEY (`user_id`)
) ENGINE=InnoDB AUTO_INCREMENT=4 DEFAULT CHARSET=utf8 COMMENT='用户信息表';
CREATE TABLE `sys_user_role` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`role_id` bigint(20) NOT NULL COMMENT '角色ID',
PRIMARY KEY (`user_id`,`role_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户和角色关联表';
CREATE TABLE `sys_user_usergroup` (
`user_id` bigint(20) NOT NULL COMMENT '用户ID',
`usergroup_id` bigint(20) NOT NULL COMMENT '用户组ID',
PRIMARY KEY (`user_id`,`usergroup_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8 COMMENT='用户和用户组关联表';
5.用户模块—研发思路
5.1思路
我们可以先开发耦合度低的小模块,从数据关联中可以看出,比如菜单和用户组,这两个可以单独开发,然后再开发角色,角色会关联菜单和用户组,最后在开发用户模块。
整个开发步骤就是先写接口也就是 controller 层,确定接口的入参与出参,然后确定业务层的逻辑调用,都需要调用哪些sql语句,将相同/类似并可扩展的进行分析归类后,就写持久层和 mapper 文件,最后完善 service 层。
其中,
1.由于我们前端打算用 elementUI ,那么很大程度上就需要符合 elementUI 的数据接收规范(前端适配也可以),例如下拉框、级联框、树形控件等,需要和他们使用的数据格式保持一致。于是会出一个统一返回规范。
5.2子模块研发
5.2.1菜单模块
思路步骤:
1.先写出controller层所需要的基本的接口:获取列表、获取一条信息、获取除当前级别及子级别外的列表、新增、修改、软删除;
特别说明:
获取列表:由于是树形表格结构,需要去判断前端框架的格式,如果使用elementUI中的树形表格,只需要返回list格式的data就行,并且每一级别的子级用children字段存储为list,格式为list。所以我们可以给菜单实体类中添加一个List children 字段,专门存放子级菜单。
获取除当前级别及子级别外的列表:这里返回的是树形下拉结构,前端使用@riophae/vue-treeselect,也是只需要返回list格式的data就行,并且每一级别的子级用children字段存储为list,用 id 字段表示选项的值,用 lable 字段表示选项的标签。所以我们可以新建一个类,专门存放转化后的内容,毕竟我们只需要传给前端菜单唯一标识、名字、子级菜单就可以啦!
软删除:将菜单的删除标志修改成已删除,而不是直接在数据库中删除;删除时会将其子级菜单一并进行删除
2.思考service层实现,最基本的顺序是从无到有再到查,我们先保证有数据!那么就先从增删改查的顺序开始!增删改三项暂时没什么特别的步骤,只需要添加service、mapper、entity就可以,我们从entity开始倒着往前推.
记住,我们现在操作的时平台后台的菜单模块,只添加和操作 menu_use_type = 0 的菜单哦~
写增删改的持久层时可以使用mybatis-generator-core来根据数据表快速生成 实体类、持久层接口持久层xml。具体看代码:mybatis-generator-core 连接 mysql 项目【mybtis-generator】-Gitee (只是实现了简单的)。
开始码代码:
//我们在 admin 模块中写表现层
1.表现层
controller:com.qingchen.controller.system.SysMenuController extends BaseController
//我们在 system 模块中写持久层和业务层
// (实体类和持久层mapper、接口可以通过 mybatis-genarator-core 工具包来快速生成)
2.实体类
entity:com.qingchen.system.domain.entity.SysMenu extends BaseEntity
树形实体类:com.qingchen.common.core.domain.TreeSelect implements Serializable
树形DTO转化类:com.qingchen.system.domain.dto.MenuTreeSelect extends TreeSelect
3.持久层
mapper.xml:mapper/system/SysMenuMapper.xml
mbg 生成的代码里面,自动包含增删改查,我们先修改insert、update、delete为我们想要的sql操作;
我们这个模块是只获取菜单使用类型=0,即后台管理菜单的~~~
mapper接口:com.qingchen.system.mapper.SysMenuMapper
4.业务层
service:com.qingchen.system.service.SysMenuService
impl:com.qingchen.system.service.impl.SysUserServiceImpl implements SysMenuService
//业务层写完之后要和表现层的调用对上哦~
重点代码:
// controller 层
@RestController
@RequestMapping("/system/menu")
public class SysMenuController extends BaseController {
/**
* @Description: 获取菜单列表,树形表格结构
* @param menu
* @Return: com.qingchen.common.core.domain.AjaxResult
*/
@GetMapping("/list")
public AjaxResult list(@RequestBody SysMenu menu){
List<SysMenu> menus = sysMenuService.selectMenuList(menu);
menus = sysMenuService.buildMenuList(menus);
return AjaxResult.success(menus);
}
/**
* @Description: 获取树形下拉框的菜单列表,排除掉当前菜单及子级;当修改时,不能将菜单的上级菜单修改成子级及下级菜单,否则会查询不到;
* @param menuId
* @Return: com.qingchen.common.core.domain.AjaxResult
*/
@GetMapping(value = {"/treeselect/exclude/{menuId}", "/treeselect/exclude"})
public AjaxResult treeselect(@PathVariable( value = "menuId" ,required = false) Long menuId , String menuUseType){
SysMenu sysMenu = new SysMenu();
sysMenu.setMenuId(menuId);
sysMenu.setMenuUseType(menuUseType);
List<SysMenu> menus = sysMenuService.selectExceptMenuListByMenuId(sysMenu);
menus = sysMenuService.buildMenuList(menus);
List<TreeSelect> treeselect = sysMenuService.buildTreeSelect(menus);
return AjaxResult.success(treeselect);
}
。。。。。
}
// service 层
@Service
public class SysMenuServiceImpl implements SysMenuService {
/**
* @Description: 构建父子级别的树形结构——采用深度递归
* @param menuList
* @Return: java.util.List<com.qingchen.system.domain.entity.SysMenu>
*/
@Override
public List<SysMenu> buildMenuList(List<SysMenu> menuList){
List<SysMenu> returnList = new ArrayList();
for ( SysMenu sysMenu : menuList) {
if(sysMenu.getParentId() == BaseConstants.NO1_ID_DEFAULT){
recursionFn(menuList,sysMenu);
returnList.add(sysMenu);
}
}
return returnList;
}
@Override
public List<SysMenu> selectExceptMenuListByMenuId(SysMenu record) {
List<SysMenu> menuList = sysMenuMapper.selectExceptMenuListByMenuId(record);
return menuList;
}
/**
* @Description: 将父子级别的树形结构转化成 treeselect 结构
* @param menuList
* @Return: java.util.List<com.qingchen.common.core.domain.TreeSelect>
*/
@Override
public List<TreeSelect> buildTreeSelect(List<SysMenu> menuList) {
if(StringUtils.isNull(menuList)){
return null;
}
return menuList.stream().map(MenuTreeSelect::new).collect(Collectors.toList());
}
/**
* @Description: 递归列表,设置当前顶级菜单的直属子级菜单
* @param list 全部菜单列表
* @param t 当前菜单
* @Return: void
*/
private void recursionFn(List<SysMenu> list, SysMenu t)
{
// 得到子节点列表
List<SysMenu> childList = getChildList(list, t);
t.setChildren(childList);
for (SysMenu tChild : childList)
{
if (hasChild(list, tChild))
{
recursionFn(list, tChild);
}
}
}
/**
* @Description: 从所有的列表中,得到当前菜单的 直属子级 列表
* @param list 全部菜单列表
* @param t 当前菜单
* @Return: java.util.List<SysDept>
*/
private List<SysMenu> getChildList(List<SysMenu> list, SysMenu t)
{
List<SysMenu> tChild = new ArrayList();
Iterator<SysMenu> it = list.iterator();
while (it.hasNext())
{
SysMenu n = it.next();
if (StringUtils.isNotNull(n.getParentId()) && n.getParentId().longValue() == t.getMenuId().longValue())
{
tChild.add(n);
}
}
return tChild;
}
/**
* @Description: 判断是否有子节点
* @param list
* @param t
* @Return: boolean
*/
private boolean hasChild(List<SysMenu> list, SysMenu t)
{
return getChildList(list, t).size() > 0;
}
。。。。。
}
//Treeselect树结构实体类的 menu 继承类
public class MenuTreeSelect extends TreeSelect {
public MenuTreeSelect(SysMenu menu)
{
setId(menu.getMenuId());
setValue(menu.getMenuId());
setLabel(menu.getMenuName());
// 遍历子级来进行转换,如果是 null 就直接赋值 null
if(StringUtils.isNotNull(menu.getChildren())){
setChildren(menu.getChildren().stream().map(MenuTreeSelect::new).collect(Collectors.toList()));
}else {
setChildren(null);
}
}
}
// Treeselect树结构实体类
public class TreeSelect implements Serializable {
private static final long serialVersionUID = 1L;
/** 节点ID */
private Long id;
/** 节点value 等同于 id ,有的前端组件属性名为value/ */
private Long value;
/** 节点名称 */
private String label;
/** 子节点 */
// 如果该属性为空字符串或者为null则都不参与序列化
@JsonInclude(JsonInclude.Include.NON_EMPTY)
private List<TreeSelect> children;
。。。。。
}
根据上面的思路
写完后进行小测试,主要测试这六个接口,具体接口可以看接口文档:Apifox工具编写的接口文档
成功~~~
5.2.2用户组模块
接下来就是用户组模块,这个模块的操作逻辑和菜单一模一样,也是增删改,和查询树形表格、treeselect 下拉结构,除了数据表属性不一样,其余的大多都一致,我们就按照菜单模块的结构来写代码就可以啦~
已经由部门更改为用户组啦
开始码代码:
//我们在 admin 模块中写表现层
1.表现层
controller:com.qingchen.controller.system.SysUsergroupController extends BaseController
//我们在 system 模块中写持久层和业务层
// (实体类和持久层mapper、接口可以通过 mybatis-genarator-core 工具包来快速生成)
2.实体类
entity:com.qingchen.system.domain.entity.SysUsergroup extends BaseEntity
树形DTO转化类:
3.持久层
mapper.xml:mapper/system/SysUsergroupMapper.xml
mbg 生成的代码里面,自动包含增删改查,我们先修改insert、update、delete为我们想要的sql操作
mapper接口:com.qingchen.system.mapper.SysUsergroupMapper
4.业务层
service:com.qingchen.system.service.SysUsergroupService
impl:com.qingchen.system.service.impl.SysUsergroupServiceImpl implements SysUserGroupService
//业务层写完之后要和表现层的调用对上哦~
注意哦,这里的 mapper.xml 文件中查询时,必须只能查询出当前用户或者当前子用户的父用户的用户组,不能查到别人的数据~
所以要加上:
<if test="userId != null and userId != ''">
AND ug.use_id = #{userId,jdbcType=BIGINT}
</if>
5.2.3角色模块
接下来就是角色模块,角色模块是与菜单、用户组、用户都有关联的模块,其中,与菜单、用户组的关联度,只需要新增时绑定,修改时删除后再绑定,删除时关联删除;与用户的绑定就是在新增之后的修改了,我们在完成用户基本模块后,再完善添加;
由于我们用到了关联表,那么也就同样要加上关联表的实体类、mapper接口与文件。
这里还要注意一点,我们的角色是与菜单、用户组分别多对多关联的,那么对于用户正常使用获取一条角色信息时,就需要调用三个接口:
一个是获取角色全部信息,其中包括对应菜单的menuid数组和对应用户组的usergroupid数组;
一个是获取当前用户能查询到的所有菜单列表;
一个是获取当前用户能查询到的所有用户组列表;
这样就能在前端展示时,展示所有的菜单、用户组以及当前角色已经选择的菜单和用户组~就像下面这样:
开始码代码:
//我们在 admin 模块中写表现层
1.表现层
controller:com.qingchen.controller.system.SysRoleController extends BaseController
//我们在 system 模块中写持久层和业务层
// (实体类和持久层mapper、接口可以通过 mybatis-genarator-core 工具包来快速生成)
2.实体类
entity:com.qingchen.system.domain.entity.SysRole extends BaseEntity
com.qingchen.system.domain.entity.SysRoleMenu extends BaseEntity
com.qingchen.system.domain.entity.SysRoleUsergroup extends BaseEntity
3.持久层
mapper.xml:mapper/system/SysRoleMapper.xml
mapper/system/SysRoleMenuMapper.xml
mapper/system/SysRoleUsergroupMapper.xml
mapper接口:com.qingchen.system.mapper.SysRoleMapper
com.qingchen.system.mapper.SysRoleMenuMapper
com.qingchen.system.mapper.SysRoleUsergroupMapper
4.业务层
service:com.qingchen.system.service.SysRoleService
impl:com.qingchen.system.service.impl.SysRoleServiceImpl implements SysRoleService
//业务层写完之后要和表现层的调用对上哦~
注意,要在role实体类里面加上菜单id数组,用户组id数组:
public class SysRole extends BaseEntity {
.....
/**
* 菜单ids,即操作权限
*/
private Long[] sysMenuIds;
/**
* 用户组ids,即数据权限
*/
private Long[] sysUsergroupIds;
}
然后在获取角色信息,新增、修改、删除角色时,都要对关联的表进行相关操作。
5.2.4用户模块
用户模块,是管理平台用户的,可以增删改查等等。
由于我们这里也用到了关联表,也一并将用户角色表,用户用户组表的逻辑加进来。道理和角色模块的一样~
//我们在 admin 模块中写表现层
1.表现层
controller:com.qingchen.controller.system.SysUserController extends BaseController
//我们在 system 模块中写持久层和业务层
// (实体类和持久层mapper、接口可以通过 mybatis-genarator-core 工具包来快速生成)
2.实体类
entity:com.qingchen.common.core.domain.entity.SysUser extends BaseEntity
com.qingchen.system.domain.entity.SysUserRole
com.qingchen.system.domain.entity.SysUserUsergroup
3.持久层
mapper.xml:mapper/system/SysUserMapper.xml
mapper/system/SysUserRoleMapper.xml
mapper/system/SysUserUsergroupMapper.xml
mbg 生成的代码里面,自动包含增删改查,我们先修改insert、update、delete为我们想要的sql操作
mapper接口:com.qingchen.system.mapper.SysUserMapper
com.qingchen.system.mapper.SysUserRoleMapper
com.qingchen.system.mapper.SysUserUsergroupMapper
4.业务层
service:com.qingchen.system.service.SysUserService
impl:com.qingchen.system.service.SysUserService implements SysUserService
//业务层写完之后要和表现层的调用对上哦~
后续模块内容如下~
5.2.5个人信息模块
5.2.6登录成功后用户信息及操作权限
5.2.7数据权限
5.2.8列表分页
5.2.9角色模块添加操作用户的逻辑