BLZO管理后台开发指南
# BLZO管理后台开发指南
本文旨在帮助开发者:
- 理解blzo-ex-authj权限框架的原理
- 基于BLZO脚手架自带管理后台对业务功能进行开发
- 基于blzo-ex-authj开发自己的管理后台(如果你不愿意使用BLZO自带的后台的话..)
# blzo-ex-authj 原理
# 权限管理的现状
提到权限管理,其实市面上以及有相当多的权限框架了,比如:shiro 、 spring security 等等。
而提到拥有权限管理的管理后台脚手架,这就更加多了,在码云上搜索脚手架,基本上都有这些功能。
但在这个什么都有的年代,我最终还是又造了一遍轮子,站在巨人的肩膀上,我希望这个轮子能够更进一步的减少开发者与使用者的工作。
# 痛点一: 权限实体该由谁维护
何为权限实体?权限管理系统中,到底能管理哪些权限?
一般来说 管理权限实体有2个方案:
- 做成权限配置文件,比如shiro就可以使用权限配置文件来管理角色和uri。
- 做成权限实体表,然后功能发布了,由使用者或者开发者登陆系统配置。
但是这两种方案带来的维护成本都不小,方案一很大程度上就限制了用户管理权限的自由性,而方案二,接口新增和变更都需要同步去维护权限实体表,这些工作,会在高速迭代的产品中愈发显现。
authj权限框架则提出了一个新的模式,系统中有哪些权限实体,是由这个系统中已有的功能决定的,程序启动时,authj会扫描系统中已有的功能,以此生成权限实体!
# 痛点二: 权限管理的粒度
最常见的权限管理模型是 角色权限模型。 除了权限实体外,额外引入角色概念,指定每个角色能干哪些事情,并为每个管理员设置1个或多个角色身份,以此来实现细粒度的权限管理。
然而,这样的权限管理模型,每当需要进行权限变更的时候,都需要由拥有"角色管理"权限的账户来进行操作。这样随着运营后台使用人数的增加,势必会使拥有"角色管理"权限的账户频繁的操作以应对各种的权限分配需求。
就好比是,我是一个运营小组长,我想在我的有权范围管理我的2个下属的权限,这样的需求,需要去求助运营总监、或者行政总监才能完成!
authj权限框架并没有使用角色权限模型,而是提出了另一个模型: 权限组叠加。
基于这个模型,用户可以很方便的将自己拥有的权限赋予给其他人,而不用求助于顶级管理员或者开发者。
# authj的工作原理
authj的工作原理主要在以下3点:
- 权限实体自动扫描
- 使用权限组实现权限传递
- 登陆时为session授权
# 权限实体自动扫描
权限实体(也简称"权限")代表了具体的功能点,可以是某一个页面(如:商品列表页),也可以是某一个按钮/功能(如:添加商品)。
在spring boot的代码中,其实就是controller层的方法,要么对应接口、要么对应页面。
使用authj权限框架,需要在controller层的方法上增加**@Authj**注解,管理后台项目启动时,会根据controller的包配置,扫描到所有的@Authj注解,以此来生成权限实体。
# 权限组与权限传递
每一个管理员都可以创建和管理自己的权限组(前提是该管理员有 "创建权限组" 的权限)。
权限组的管理分为 授权管理 和 成员管理。
管理员可以通过授权功能,将自己拥有的权限授权给权限组,通过添加成员功能,为自己的权限组添加成员。
当你处在一个权限组里,你就拥有这个这个权限组里的所有有效权限,我把这个过程称为:从权限组继承权限。
你可以把自己拥有的所有有效权限(无论是从谁的组里继承来的),授权给自己创建的其他权限组,并将其传递给其他的管理员。
系统中 userId=0 的用户 为 超级管理员,不难看出所有用户的权限都是继承自超级管理员的。
这里提到了 有效权限 ,权限组里拥有的权限必须和权限组的创建者所拥有的权限取交集,剩下的这些才能是有效权限。
换句话说,就是,如果权限组的创建者本身都没有这个权限,那么由他传递出去的该权限也是无效的。
以上就是权限组与权限传递的规则。
# 授权
授权操作是在用户登陆的时候进行的,authj会根据具登陆用户继承权限的关系,为登陆的session授权。
登陆用户加入的所有组的有效权限取并集就是该session被授权的权限。
最后,authj的拦截器会在每次请求时,会检查请求对应的权限实体,当前session是否有权访问,以此进行鉴权操作。
# 数据字典
authj 共有6张表,这些表的结构都很简单,其中的日志表会保存所有的页面接口请求记录。组织表的作用会在后面的内容中讲解。
- admins 管理员
- groups 权限组
- group_admin 权限组-管理员关联表
- group_auth 权限组-权限实体关联表
- logs 操作日志表
- organizes 组织表
2
3
4
5
6
每个管理员可以创建多个权限组,以及加入多个权限组,每个权限组可以包含多个权限实体
# admins表
DROP TABLE IF EXISTS `admins`;
CREATE TABLE `admins` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '管理员Id',
`nick_name` varchar(64) NOT NULL COMMENT '用户昵称',
`username` varchar(50) NOT NULL COMMENT '登陆名',
`password` varchar(32) NOT NULL COMMENT '密码(加盐MD5)',
`salt` varchar(64) NOT NULL COMMENT '盐',
`google_secret` varchar(255) DEFAULT NULL COMMENT 'google验证码secret',
`phone` varchar(20) DEFAULT NULL COMMENT '手机号',
`email` varchar(64) DEFAULT NULL COMMENT '邮箱',
`status` int(2) NOT NULL DEFAULT '0' COMMENT '状态(暂未实装) 0:初始状态 1:正常使用 -1:冻结',
`last_ip` varchar(20) DEFAULT NULL COMMENT '最后登录的IP',
`last_time` datetime DEFAULT NULL COMMENT '最后登录的时间',
`remark` varchar(255) DEFAULT '' COMMENT '备注',
`layer` varchar(8192) DEFAULT NULL COMMENT '菜单配置',
`organize_id` int(11) DEFAULT NULL COMMENT '组织id',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `username_KEY` (`username`) USING BTREE,
KEY `phone_KEY` (`phone`) USING BTREE,
KEY `email_KEY` (`email`) USING BTREE,
KEY `status_KEY` (`status`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='管理员表';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
# groups、group_admin、group_auth 表
DROP TABLE IF EXISTS `groups`;
CREATE TABLE `groups` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增ID',
`name` varchar(32) NOT NULL COMMENT '管理员组名称',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`create_admin_id` int(11) NOT NULL COMMENT '创建者ID',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
PRIMARY KEY (`id`),
KEY `create_KEY` (`create_admin_id`) USING BTREE,
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组';
DROP TABLE IF EXISTS `group_admin`;
CREATE TABLE `group_admin` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '组ID',
`group_id` int(11) NOT NULL,
`admin_id` int(11) NOT NULL,
`create_admin_id` int(11) NOT NULL COMMENT '创建人ID',
`remark` varchar(255) NOT NULL DEFAULT '' COMMENT '备注',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `group_id_admin_id_UNIQUE` (`group_id`,`admin_id`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='权限组-管理员关联表';
DROP TABLE IF EXISTS `group_auth`;
CREATE TABLE `group_auth` (
`id` int(11) NOT NULL AUTO_INCREMENT COMMENT '自增id',
`group_id` int(11) NOT NULL,
`uri` varchar(255) NOT NULL,
`create_admin_id` int(11) NOT NULL COMMENT '创建人ID',
`remark` varchar(255) DEFAULT '' COMMENT '备注',
`create_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP,
`update_time` timestamp NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
PRIMARY KEY (`id`),
UNIQUE KEY `group_id_auth_uri_UNIQUE` (`group_id`,`uri`) USING BTREE
) ENGINE=InnoDB AUTO_INCREMENT=386 DEFAULT CHARSET=utf8mb4 COMMENT='权限组-权限实体关联表';
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
# 管理后台开发指南
# 技术栈
# blzo 脚手架 / blzo-ex-authj 权限组件
spring boot 2.x + mybatis
# 页面渲染:thymeleaf 模板引擎
thymeleaf 是spring boot官方推荐的模板引擎
Thymeleaf 官方文档thymeleaf.org
Thymeleaf 教程waylau
# 前端主题模板
建议把这个模板clone下来,它可以给你在开发时带来诸多参考
BucketAdminthemehub
# 管理后台项目结构
src/main
|- java
| |- com.jdkhome.blzo.manage
| |- configuration // 配置包,多数据源配置或者一些自定义的bean放在这里
| |- controller // controller层代码
| | |- CommonControllerAdvice.java // 所有@Controller执行之前,会先执行这里
| | |- ...
| |
| |- pojo // 无逻辑对象包
| |- ManageApplication.java // 启动类,可以在这里增加一些启动后自动执行的方法
|
|- resource
|- static // 静态资源文件
| |- common // 放logo等其他图片
| |- manage
| |- custom // 自定义的js文件目录
| | |- common // 公共js
| | | |- base.js // 封装ajax请求
| | | |- request.js // 接口声明js
| | | |- search.js // 搜索功能实现
| | |
| | |- page // 自定义页面的js
| |
| |- js // 管理后台的一些js插件建议放在这里
| |- ...
|
|- templates // thymeleaf 模板文件目录
| |- error // 404、500页面
| |- manage // 管理后台的thymeleaf模板目录
| |- common // 功能模板组件,比如菜单、分页器等
| |- page // 自定义页面的模板目录
| |- ...
|
|- application.yml // 主配置文件
|- application-dev.yml // 开发环境配置文件
|- application-test.yml // 测试环境配置文件
|- application-pro.yml // 生产环境配置文件
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
# 快速开始
现在我们开始在BLZO管理后台中开发你的第一个页面和接口!
# controller层 接口和页面的控制器
DemoApiController.java demo接口controller
DemoPageController.java demo页面controller
# 前端页面和js
demo.htmldemo页面html模板
在request.js中声明你的接口
// DEMO API
Request.apiDemoApi = function (context, data, event, callbacks) {
Base.doPost('/api/manage/demo/my_api', context, data, event, callbacks);
};
2
3
4
demo.js demo页面对应的js文件
# 启动!
使用超级管理员账户登录BLZO管理后台
在"未分组"菜单组中 即可找到demo页面。
# 特性使用
# 打印日志
任何接口,加上@Api注解即可自动打印日志
# 设置权限实体
任何接口,加入@Authj注解即可完成权限实体的设置
Authj目前有4个属性
- value 权限实体的名称,如果不设置则会从@Api注解中获取
- auth 是否需要鉴权 如果不设置,则默认为true
- menu 是否能够作为菜单 如果不设置,则默认为false
- common 是否为公共权限 如果不设置,则默认为false
公共权限: 只要登录就有权限
# 列表页常见需求
数据筛选,blzo管理后台的列表数据筛选是通过页面控制器改变请求参数实现的
在页面的Controller方法中设置你要筛选的条件,并在html中初始化Search对象
<script>
window.onload = function () {
var search = new window.controller.Search();
search.init();
};
</script>
2
3
4
5
6
分页器,blzo使用mybatis-pagehelper插件实现分页
在需要分页的页面控制器,Model中传入"PageInfo",在页面中引入分页器模板组件即可实现分页
<div th:replace="manage/common/paginate"></div>
你可以参考 管理员列表页 来获取这些功能的示例
# 在任何地方获取管理员Id
注入AuthjManager,然后在你需要的地方使用getUserId()方法
@Autowired
AuthjManager authjManager;
...
Integer userId = authjManager.getUserId();
...
2
3
4
5
# 获取更多页面示例
请参考前端主题模板
BucketAdminthemehub
# 数据权限开发
在authj中,使用组织的方式实现数据权限。组织功能是可选的,并不会强制使用。
# 业务场景
使用组织功能之前,你需要明确你的项目是否真的需要它,组织功能是有侵入业务的。
一个典型的使用场景是:
管理后台被两个不同地区的业务团队所使用,两个地区运营的业务相同,但是我们希望A地区的运营,只能看到A地区的数据,B地区的运营,只能看到B地区的数据。
在这样的场景下,你有两种处理方案:
- 为A、B地区各部署一套服务,两套服务数据分库互不干扰
- 使用authj的"组织"功能 为每一个希望有数据权限的表增加"组织Id字段"
两个方案各有各的好处,方案1不会侵入业务,方案2则在管理上更具统一性。
# 组织功能的现有能力
系统默认userId=0的用户为超级管理员,同样,组织Id=0的组织为总组织。
每个用户都只能加入一个组织,不同组织的管理员,互相不可见。属于总组织的用户可以管理所有组织。
# 开发自己的管理后台
blzo自带的管理后台能够适用大多数场景,但正如所有脚手架一样,快捷的同时也会在一定程度上产生约束。
如果你的产品(或是客户),希望能够定制管理后台的其他样式;又或是你希望用其他技术栈去实现体验更好的管理后台(比如vue);那么你可能需要重新开发一个新的管理后台。
本节默认你已经通读了前面的内容,并且已经阅读了blzo-ex-authj的代码。
# 管理员登陆
使用 authjManager.grant(userId) 方法,为当前登录的session进行授权。
@Autowired
AuthjManager authjManager;
...
// 对当前登陆的session进行授权
authjManager.grant(userId);
// 对当前登录的session移除授权
authjManager.delGrant();
...
2
3
4
5
6
7
8
# 授权实体
授权实体中包含了当前session的所有权限 和 菜单配置
@Autowired
AuthjManager authjManager;
...
// 获取当前登陆用户的授权实体
UserAuthjConfBean userAuthjConfBean = authjManager.getUserAuthjConfBean();
...
2
3
4
5
6