一份热乎的 SpringBoot 前后端分离后台管理系统分析!分模块开发、RBAC权限控制...
我这个人没事就喜欢逛 Github,然后时不时还喜欢推荐一些优秀的开源项目给各位小伙伴(详见「专辑」开源项目推荐)。毫不夸张地说,被我推荐的开源项目都还不错。就想把最好的东西都分享出来,或许这就是我对你们的爱吧!
有很多读者都希望我出一些详细介绍 Java 实战类项目的文章,毕竟项目实战经验还是挺重要的,不论是对于找工作还是提高个人工程能力。我自己也发现很多读者不怎么会学习开源项目,不知道如何把开源项目的一些精华为自己所用。
《Spring Boot 搭建的一个在线文件预览系统!支持 ppt、doc 等多种类型文件预览》 这篇实战类型开源项目的分析是我前段时间写的,这篇文章中我手把手带着你们研究了系统核心功能的代码。这篇文章的话,我想的是从分析项目具有的亮点的角度来学习开源项目。这两篇文章结合起来,大家应该就清楚如何学习实战类型的开源项目了。
我们这里分析的是 eladmin[1] 这个基于 Spring Boot + Spring Security +JPA +Vue 的前后端分离的后台管理系统。在《Github 点赞接近 100k 的 SpringBoot 学习教程+实战推荐!》这篇文章中我就推荐过这个项目。
开源工具库
这部分简单分析一下项目使用到的一些比较有用的开源工具库:MapStruct(Java 对象映射框架)、OSHI(基于 JNA 的操作系统和硬件信息库)、Hutool(Java 工具类库)。
另外,我在之前分享的《不要重复造轮子?提高生产效率!3 个常用的开源工具库分享》这篇文章也提到过 OSHI 和 Hutool 这两个非常好用的开源库。
MapStruct
MapStruct[2] 不仅能够在 bean 之间复制属性,还能够在不同类型之间自动转换。
@Mapper
public interface CarMapper {
@Mapping(source = "make", target = "manufacturer")
@Mapping(source = "numberOfSeats", target = "seatCount")
CarDto carToCarDto(Car car);
@Mapping(source = "name", target = "fullName")
PersonDto personToPersonDto(Person person);
}
相比于其他常见映射框架比如 Dozer、Orika、ModelMapper、JMapper,MapStruct 的性能更好。想还想要继续详细了解话,可以查看这两篇文章:
《Bean 映射工具之 Apache BeanUtils VS Spring BeanUtils》[3] MapStruct 官网使用指南教程[4]
OSHI
OSHI[5] 是一款为 Java 语言提供的基于 JNA 的(本机)操作系统和硬件信息库。
通过 OSHI ,我们不需要安装任何其他本机库,就能查看内存和 CPU 使用率、磁盘和分区使用情况、设备、传感器等信息。
OSHI 旨在提供一种跨平台的实现来检索系统信息,支持 Windows、Linux、MacOS、Unix 等主流操作系统。
使用 oshi 你可以轻松制作出项目常用的系统监控功能,如下图所示:
Hutool
Hutool[6] 一个非常实用的 Java 工具类库,对文件、流、加密解密、转码、正则、线程、XML 等 JDK 方法进行了封装。
小技巧
这部分内容会简单分析一下从这个开源项目中看到的一些亮点以及小技巧。
优化表命名
根据项目不同的 Module 作为表名的前缀,这样看起来更加直观。
巧用 AOP 简化代码
AOP(Aspect-Oriented Programming:面向切面编程) 能够将那些与业务无关,却为业务模块所共同调用的逻辑或责任(例如事务处理、日志管理、权限控制等)封装起来,便于减少系统的重复代码,降低模块间的耦合度,提高系统可拓展性和可维护性。
这个项目中就大量使用了 AOP 思想。简单举两个例子吧!
日志记录
利用 AOP 方式记录日志,只需要在 controller
的方法上使用 @Log("")
注解,就可以将用户操作记录到数据库,源码可查看 eladmin-logging
这个 Module。。
@Log("新增用户")
@PostMapping(value = "/users")
public ResponseEntity create(@Validated @RequestBody User resources){
checkLevel(resources);
return new ResponseEntity(userService.create(resources),HttpStatus.CREATED);
}
Redis 限流
利用 AOP 方式对接口进行限流,只需要在 controller
的方法上使用 @Limit("")
注解即可,源码可查看 eladmin-common
这个 Module。
/**
* 测试限流注解,下面配置说明该接口 60秒内最多只能访问 10次,保存到redis的键名为 limit_test,
*/
@AnonymousGetMapping
@ApiOperation("测试")
@Limit(key = "test", period = 60, count = 10, name = "testLimit", prefix = "limit")
public int test() {
return ATOMIC_INTEGER.incrementAndGet();
}
基于 RBAC 的权限模块设计
系统权限控制采用 RBAC 基于角色的权限访问控制(Role-Based Access Control) 思想(一种最常见的权限管理思想,在一些对于全权限控制要求比较严格的系统会使用到)。
什么是 RBAC 呢?
简单地说:一个用户可以拥有若干角色,每一个角色有可以被分配若干权限这样,就构造成“用户-角色-权限” 的授权模型。在这种模型中,用户与角色、角色与权限之间构成了多对多的关系,如下图
在 RBAC 中,权限与角色相关联,用户通过成为适当角色的成员而得到这些角色的权限。这就极大地简化了权限的管理。
本系统的权限设计相关的表如下(一共 5 张表,2 张用户建立表之间的联系):
通过这个权限模型,我们可以创建不同的角色并为不同的角色分配不同的权限范围(菜单)。
自定义权限验证方式
Spring Security
提供了Spring EL
表达式,允许我们在定义接口的方法上面添加注解来实现权限控制。比如下面的接口表示用户拥有 admin
、menu:edit
权限中的任意一个就能能访问update
接口。
@Log(description = "修改菜单")
@PutMapping(value = "/menus")
@PreAuthorize("hasAnyRole('admin','menu:edit')")
public ResponseEntity update(@Validated @RequestBody Menu resources){
// 略
}
但是,由于每个接口都需要给超级管理员放行,所以单纯使用这种注解的方式每次都需要重复的添加 admin 权限、
因此我们可以加入自定义权限验证方式,在验证的时候默认给拥有 admin 权限的用户放行。
源码:
// eladmin-common -> me.zhengjie.config.ElPermissionConfig
@Service(value = "el")
public class ElPermissionConfig {
public Boolean check(String ...permissions){
// 获取当前用户的所有权限
List<String> elPermissions = SecurityUtils.getCurrentUser().getAuthorities().stream().map(GrantedAuthority::getAuthority).collect(Collectors.toList());
// 判断当前用户的所有权限是否包含接口上定义的权限
return elPermissions.contains("admin") || Arrays.stream(permissions).anyMatch(elPermissions::contains);
}
}
使用方式:
@ApiOperation("查询任务执行日志")
@GetMapping(value = "/logs")
@PreAuthorize("@el.check('timing:list')")
public ResponseEntity<Object> queryJobLog(JobQueryCriteria criteria, Pageable pageable){
return new ResponseEntity<>(quartzJobService.queryAllLog(criteria,pageable), HttpStatus.OK);
}
对应的数据库内容如下:
审计功能
我在我的原创文章《接近 8000 字的 Spring/SpringBoot 常用注解总结!安排!》中也提到过了审计功能,这个在项目中使用的还是比较多的。
在这个项目中,继承了 BaseEntity
的类会自动写入创建时间、修改时间、创建人、更新人这些数据。
@Getter
@Setter
@MappedSuperclass
@EntityListeners(AuditingEntityListener.class)
public class BaseEntity implements Serializable {
@CreatedBy
@Column(name = "create_by", updatable = false)
@ApiModelProperty(value = "创建人", hidden = true)
private String createBy;
@LastModifiedBy
@Column(name = "update_by")
@ApiModelProperty(value = "更新人", hidden = true)
private String updatedBy;
@CreationTimestamp
@Column(name = "create_time", updatable = false)
@ApiModelProperty(value = "创建时间", hidden = true)
private Timestamp createTime;
@UpdateTimestamp
@Column(name = "update_time")
@ApiModelProperty(value = "更新时间", hidden = true)
private Timestamp updateTime;
/* 分组校验 */
public @interface Create {}
/* 分组校验 */
public @interface Update {}
@Override
public String toString() {
ToStringBuilder builder = new ToStringBuilder(this);
Field[] fields = this.getClass().getDeclaredFields();
try {
for (Field f : fields) {
f.setAccessible(true);
builder.append(f.getName(), f.get(this)).append("\n");
}
} catch (Exception e) {
builder.append("toString builder encounter an error");
}
return builder.toString();
}
}
简单介绍一下上面涉及到的一些注解:
@CreationTimestamp
: 创建对象的时候自动生成时间戳。@UpdateTimestamp
:创建对象的时候自动生成时间戳。@CreatedBy
:表示该字段为创建人,在这个实体被 insert 的时候会设置值,@LastModifiedBy
同理。
审计功能对应的配置类:
/**
* @description : 设置审计
* @author : Dong ZhaoYang
* @date : 2019/10/28
*/
@Component("auditorAware")
public class AuditorConfig implements AuditorAware<String> {
/**
* 返回操作员标志信息
*
* @return /
*/
@Override
public Optional<String> getCurrentAuditor() {
try {
// 这里应根据实际业务情况获取具体信息
return Optional.of(SecurityUtils.getCurrentUsername());
}catch (Exception ignored){}
// 用户定时任务,或者无Token调用的情况
return Optional.of("System");
}
}
可优化点
可以考虑使用阿里开源的 easyexcel 来做 Excel 的导入导出,避免 OOM。 可以参考 《实际项目中我们是这样做异常处理的》[7] 对项目全局异常处理部分进行优化。 一些重复代码可以抽取出来,比如 eladmin-common
模块中的BaseDTO.java
和BaseEntity.java
的toString()
方法。项目的 json 解析库用到了 fastjson。实际上,我更推荐使用 Spring 默认的 JSON 解析库 Jackson。这两者中, Fastjson 的代码质量以及设计更差,并且,经常被爆出有安全漏洞(设计问题)。虽然 Fastjson 在速度方面稍稍取胜,但是,速度方面的优势不太明显,影响不大。 可以使用 Caffeine 来做本地缓存。
后记
我发现很多人对于开源项目的态度就是:克隆下来就简单玩玩功能就算了。
我觉得这样实际不太好。你把项目克隆下来之后,首先肯定是自己体验一下系统的核心功能。体验了核心功能之后,你可以分析分析项目代码,检查一下有没有 bug,看看有没有可以优化的代码/模块,思考一下有没有需要完善的功能模块......。我在自己分析、调试这个开源项目的时候,就发现了一个小 bug 并提交了相应的 issue (目前已经被采纳)。
这篇文章是周日的时候写的,不过,由于晚上有点事情就留了点尾巴。
本来我是打算昨晚继续完善一下这篇文章的,但是,昨晚跑步回来我整个人都晕晕的,眼中的世界自带模糊特效。于是乎,今天起了个早完善了这篇文章。由于时间问题,这个项目还有很多的亮点都没有提到,小伙伴可以自己按照我的思路或者自己的路子去分析研究一下。
参考资料
eladmin: https://github.com/elunez/eladmin
[2]MapStruct: https://github.com/mapstruct/mapstruct
[3]《Bean 映射工具之 Apache BeanUtils VS Spring BeanUtils》: https://github.com/Snailclimb/springboot-guide/blob/master/docs/advanced/Performance-of-Java-Mapping-Frameworks.md
[4]MapStruct 官网使用指南教程: https://mapstruct.org/documentation/reference-guide/
[5]oshi: https://github.com/oshi/oshi
[6]Hutool: https://github.com/looly/hutool
[7]《实际项目中我们是这样做异常处理的》: https://github.com/Snailclimb/springboot-guide/blob/master/docs/advanced/springboot-handle-exception-plus.md
最近写的一些干货,每篇都很用心,欢迎各位小伙伴阅读/点赞/分享:
我是Guide哥,Java后端开发,会一点前端知识,喜欢烹饪,自由的少年。一个三观比主角还正的技术人。我们下期再见!