现在很多 Web 项目,包括普通的前后端分离项目,微服务项目,都基于 Spring Boot 搭建,项目中一般需要包含一些通用的特性或功能,如统一异常处理、切面日志功能、权限管理功能等。写一个自己的脚手架,对一些基本规范、基础功能进行沉淀,基于此可只需要关注业务实现,提高开发效率。
目前整个脚手架包含三部分:
- 基础组件:对单个项目(服务)的一些基本特性、功能等进行封装,便于复用,或达到常说的“开箱即用”
- Spring Boot 单体项目脚手架:基于基础组件,提供常见的一些基础通用功能实现的单体项目框架,如权限管理,提供前后端分离的后端 Spring Boot 项目与前端 Vue 项目
- Spring Cloud(包含 Spring Cloud Alibaba)微服务项目框架:基于基础组件,提供微服务框架的基础组件与功能实现,如网关(Spring Cloud Gateway),服务注册中心与配置管理(Nacos),服务限流降级(Sentinel),统一权限管理(Spring Security Oauth2)等。
本项目还在持续完善中,本篇为基础组件介绍。
基础组件
基础组件提供
- jboost-common:基础工具类,常量类
- jboost-dependencies:统一依赖版本管理
- jboost-generator:代码生成,根据数据库表自动生成各层源代码
- jboost-parent:父项目,集成了数据库、redis、统一异常处理、统一响应封装、切面日志等功能
- jboost-starters:常用功能 starter
- jboost-starter-alimq:阿里 RocketMq 消息队列
- jboost-starter-alioss:阿里云对象服务,上传图片、视频等
- jboost-starter-alisms:阿里短信服务
- jboost-starter-error:统一异常处理
- jboost-starter-limiter:基于 Redis 的分布式锁,基于 Guava RateLimiter(令牌桶算法)的限速,及基于 Redis Lua 的限量(时间窗口内限制访问量)实现
- jboost-starter-logging:切面日志功能
- jboost-starter-web:日期格式化、跨域、添加请求ID、请求响应封装、Swagger集成等
下载安装
1 | git clone https://github.com/ronwxy/jboost-base.git |
jboost-common
基础类库,包括工具类,常量类等
Bean 转换
BaseConverter: Bean 转换,主要用于 entity 与 dto 之间的转换,使用 MapStruct 框架。
针对需要转换的 Bean,创建相应接口,示例
1 | (componentModel = BaseConverter.SPRING,uses = {},unmappedTargetPolicy = ReportingPolicy.IGNORE) |
异常定义
- BizException: 业务异常基类
- ClientSideException: 客户端异常,如参数错误
- UnauthorizedException: 鉴权失败异常,如token过期
- ForbiddenException: 访问受限异常,如访问资源未授权
- ServerSideException: 服务端异常,服务端内部操作异常,如数据库访问出错
- ExceptionUtil: 异常工具类
业务中处理异常有两种形式。
- 根据条件抛出异常,交由统一异常处理机制处理(如记录日志,返回客户端错误提示)
1 | User existUser = findUserByName(name); |
- 捕获异常后重新抛出异常,交由统一异常处理机制处理(如记录日志,返回客户端错误提示)
1 | try { |
消息队列抽象
使用消息队列的地方可以基于该接口与消息对象调用,屏蔽具体消息中间件信息
- MqMessage:消息对象,自身包含 topic, tag, key 等信息
- IMqProducer:消息生产者接口,使用具体消息中间件时,生产者服务实现该接口
其它工具与常量类
安全相关:
- EncryptUtil:RSA非对称加密(公钥/私钥)工具类
- JwtUser:用于 Spring Security 认证的 user bean
- JwtUtil:Jwt 工具类
- SecurityUtil:Security 工具类,可获取当前登录用户的相关信息
其它:
- EasyPoiUtil:文件上传工具类
- FileUtil:文件操作工具类
- LocalDateTimeUtil:日期工具类
- MyBatisUtil:可用于 mybatis xml 文件条件判断的工具类
- ResponseWrapper:客户端响应封装
- VerifyCodeUtil:图形验证码工具类
- WebUtil:Web工具类,设置req-id;获取客户端IP;输出响应
依赖管理
jboost-dependencies:依赖版本管理,管理常用依赖库的版本,其它继承或以 dependencyManagement
引入 jboost-dependencies 的项目引用依赖时可省略版本号
1 | <dependencyManagement> |
代码生成
jboost-generator: 代码生成工具,采用 mybatis-plus-generator 实现,配置详见 resource/generator.yaml
文件示例
针对每个数据库表,可生成如下代码结构
1 | --controller |
父项目
jboost-parent:父项目,集成了数据库、redis、统一异常处理、统一响应封装、切面日志等功能
starter
jboost-starters
jboost-starter-alimq
阿里 RocketMq 消息队列使用示例
项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-alimq</artifactId>
</dependency>application.yaml 文件中添加配置:
1
2
3
4
5
6
7
8
9aliyun:
rocketmq:
accessKey: xxxx
secretKey: xxxx
namesrvAddr: http://xxx.mqrest.cn-hangzhou.aliyuncs.com
sendMsgTimeoutMillis: 3000
#topic 所属实例ID
instanceId: xxxx
groupId: xxxx使用生产者发送消息(RocketMqAutoConfiguration 已配置了本地开发测试环境走HTTP协议 RocketMqRemoteProducer,云端生产环境走TCP协议 RocketMqInternalProducer)
1 | "${socket.mq.topic}") ( |
- 消息消费者
jboost-starter-alioss
阿里云对象服务,上传图片、视频等
项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-alioss</artifactId>
</dependency>application.yaml 文件中添加配置:
1
2
3
4
5
6
7aliyun:
oss:
accessKeyId: xxxx
accessKeySecret: xxxx
bucket: xxxx
domain: http://xxx.oss-cn-hangzhou.aliyuncs.com/
endpoint: https://oss-cn-hangzhou.aliyuncs.com使用 AliOssProvider 来上传文件,或对文件路径进行签名(针对需要授权访问的资源)
1 |
|
更多方法参考 cn.jboost.base.starter.alioss.AliOssProvider
jboost-starter-alisms
阿里短信服务
- 项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-alisms</artifactId>
</dependency> - application.yaml 中添加配置
1 | aliyun: |
- 使用
1 |
|
jboost-starter-error
统一异常处理
- 项目 pom.xml 中引入依赖:
1 | <dependency> |
- 使用 @RestControllerAdvice + @ExceptionHandler 统一处理抛出的异常
- BizException:以 400 返回异常消息
- AccessDeniedException:以 403 返回异常消息
- IllegalArgumentException,IllegalStateException:以 400 返回异常消息
- Exception: 以 500 返回异常消息
- 打印异常栈
- 针对 profile “default”,”local”,”dev”, 打印异常栈;
- 针对 profile “test”, “formal”, “prod”,不打印异常栈。
org.springframework.boot.autoconfigure.web.servlet.error.BasicErrorController
现在通过 request 传 trace=true 参数的方式来控制是否打印异常栈,后续可做相应调整
jboost-starter-limiter
提供基于 Redis 的分布式锁,基于 Guava RateLimiter(令牌桶算法) 的限速,及基于 Redis Lua 的限量实现
项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-limiter</artifactId>
</dependency>分布式锁
在方法上添加注解 @DistributedLockable,对方法进行分布式环境下的同步,
1 | "", prefix="disLock:", expire=5) (key= |
- key:redis 使用 prefix+key 来作为缓存key
- prefix:redis key 前缀,默认为
disLock:
- expire:过期时间,默认为10s
- 限流
限速:使用 @RateLimit 注解严格控制访问速率,在一次访问后,必须经过设定的时间间隔才能进行下一次访问
1 | "/rate") ( |
上例表示以限制访问速度为5秒1次,一次访问后,直到5s之后才能再次访问。
支持的属性配置:
- key: redis 使用 prefix+key 来作为缓存 key
- prefix:redis key 前缀, 默认为 “rateLimit:”;
- expire:表示令牌桶模型 RedisPermits redis key 的过期时间/秒,默认为 60;
- rate:permitsPerSecond 值,表示以每秒rate的速率放置令牌,默认为 1.0;
- burst:maxBurstSeconds 值,表示最多保留burst秒的令牌,默认为 1.0;
- timeout:取令牌的超时时间,秒,默认为 0,表示不等待立即返回;
- limitType: 默认 LimitType.METHOD;
LimitType 主要用于控制 key 的值,支持类型如下,
- IP:根据客户端IP限流
- USER:根据用户限流,用户已经登录,通过
SecurityUtil.getUserId()
获取当前用户ID - METHOD:根据方法名全局限流
- CUSTOM:自定义,需要设置 key 的值,自定义 key 支持表达式,如
#{id}
,#{user.id}
限量:使用 @CountLimit 注解来控制在一个时间窗口内,允许访问的次数,在允许次数内对访问速率不限制
1 | "/count") ( |
上例表示在10s内限制访问2次,至于这两次以什么样的时间间隔访问不做限制。
支持的属性配置:
- key:redis 使用 prefix+key 来作为缓存 key
- prefix:key 前缀,默认 “countLimit:”;
- limit:period 时间段内限制访问的次数,默认1;
- period:表示时间段/秒,默认1;
- limitType: 默认 LimitType.METHOD;
非注解形式限流:也可以使用如下形式对某段代码进行限流控制
1 | "/rate2") ( |
更多详细内容参考 一个轻量级的基于RateLimiter的分布式限流实现
jboost-starter-logging
切面日志功能
对用注解 cn.jboost.base.starter.logging.annotation.Log
修饰的类方法进行日志记录
项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-logging</artifactId>
</dependency>在方法或类上(对类内所有方法有效,一般用于 Controller 类上)添加注解 @Log,
1
2
3
4
5
6import cn.jboost.base.starter.logging.annotation.Log;
public class UserController {
//...
}日志打印
添加注解 @Log 后,当方法被调用时,默认将打印调用与返回日志,如1
2jboost-boot - [2021-01-09 09:41:21] [http-nio-8000-exec-1] INFO [5ff909c1a94b6d1f4c62f021 - ] c.j.b.auth.controller.AuthController - call: getCaptcha()
jboost-boot - [2021-01-09 09:41:21] [http-nio-8000-exec-1] INFO [5ff909c1a94b6d1f4c62f021 - 216] c.j.b.auth.controller.AuthController - return: getCaptcha():VerifyCodeUtil.VerifyCode(code=xqho, uuid=9d8ffc7e9e324ed2a5a9566d9a7f115d)其它配置
@Log 注解提供了两个属性配置
- logPoint:可配置在什么位置打印日志,有 LogPoint.IN(调用时打印), LogPoint.OUT(返回时打印), LogPoint.BOTH(调用与返回时都打印)
- logException:是否对异常进行日志记录,默认为true
可对日志输出的实现进行定制化,默认使用 cn.jboost.base.starter.logging.provider.Slf4jLogProvider
,采用项目中slf4j的框架进行输出,如logback,log4j2等。
如果要自定义日志输出,则可提供一个 cn.jboost.base.starter.logging.provider.ILogProvider
接口的实现类,然后配置
1 | aoplog: |
jboost-starter-web
日期格式化、跨域、添加请求ID、请求响应封装、Swagger集成等
项目 pom.xml 中引入依赖:
1
2
3
4<dependency>
<groupId>cn.jboost.boot</groupId>
<artifactId>jboost-starter-web</artifactId>
</dependency>日期格式化(序列化与反序列化时)
- LocalDateTime:
yyyy-MM-dd HH:mm:ss
- LocalDate:
yyyy-MM-dd
- LocalTime:
HH:mm:ss
- 跨域
默认开启,如要关闭,配置
1 | web: |
- 添加请求ID
默认开启,如要关闭,配置
1 | web: |
开启后,会在每一个请求的 Header 中添加 key 为 Req-Id
, value 为 uuid 的 header,下游可通过 WebUtil.getRequestId()
获取(例如将其存入MDC中,在日志打印时输出请求ID,将日志进行串联)
- 请求响应封装
默认开启,如要关闭,配置(建议默认开启,如果关闭,还需要在异常处理中将响应格式进行处理)
1 | web: |
开启后,对请求响应按照 cn.jboost.base.common.util.ResponseWrapper
的结构进行封装
- swagger集成
默认关闭,如要开启,配置
1 | swagger: |
建议只在开发环境(application-dev.yaml)中开启
- 过滤器中异常处理
主要是对响应结果进行封装统一: cn.jboost.base.starter.web.ExceptionHandlerFilter
总结
基础组件是后续单体应用框架、微服务项目框架的基础。本文对基础组件目前提供的功能、特性进行了介绍,因水平有限,疏漏、错误之处难免,欢迎指正。
基础组件项目源码: https://github.com/ronwxy/jboost-base.git, 如对你有帮助,可以大方地 star 一下。