heifer
heifer copied to clipboard
一站式 saas平台开发
1. How to Use
Add maven dependency
<dependencyManagement>
<dependencies>
<dependency>
<groupId>plus.wcj</groupId>
<artifactId>heifer-dependencies</artifactId>
<version>${revision}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
2. Features
heifer是一套为一站式 saas平台开发,集成了多租户
,数据权限
,功能权限
等一些常见的功能并对Spring框架进行增强。
Heifer
is a set of common features developed for a one-stop SAAS platform, integrating multi tenant
, data permissions
,functional permissions
, and enhancing the Spring framework.
2.1. heifer boot
集成了 Spring MVC和validation
2.1.1. ExceptionHandler
提供全局拦截器, 对异常进行全局拦截器
异常返回格式
{
"code": "",
"message": "",
"data": {},
"@comment": {
"code": "/** 业务错误码 */",
"message": "/** 信息描述 */",
"data": "/** 返回参数 */"
}
}
2.1.2. ResponseBodyAdvice
全局统一返回
Controller的类或者方法中使用了 @ResponseBodyResult
就会进行装箱
{
"code": "",
"message": "",
"data": {},
"@comment": {
"code": "/** 业务错误码 */",
"message": "/** 信息描述 */",
"data": "/** 返回参数 */"
}
}
2.1.3. RestTemplate
提供ResponseBodyResult
自动拆箱,会判断是否启用ResponseBodyResult
2.1.4. validation
添加快速失败
2.1.5. 异常
plus.wcj.heifer.metadata.exception.ResultException是定制的异常类, 支持国际化, 枚举类, 占位符
2.1.5.1. 自定义异常枚举
自定义异常枚举需要实现plus.wcj.heifer.metadata.exception.ResultStatus
例子
public enum plus.wcj.heifer.boot.common.exception.ResultStatusEnum implements ResultStatus {
/** 请求成功 */
SUCCESS(HttpStatus.OK, "200", "OK"),
/** 返回的HTTP状态码, 符合http请求 */
private final HttpStatus httpStatus;
/** 业务异常码 */
private final String code;
/** 业务异常信息描述 */
private final String message;
ResultStatusEnum(HttpStatus httpStatus, String code, String message) {
this.httpStatus = httpStatus;
this.code = code;
this.message = message;
}
get ...
}
2.1.5.2. 异常信息国际化
默认添加异常信息国际化
messages.properties格式定义
# {class全路径}.{枚举}={异常信息}
# 无占位符
plus.wcj.heifer.boot.common.exception.ResultStatusEnum.SUCCESS=OK
# 占位符返回
plus.wcj.heifer.boot.common.exception.ResultStatusEnum.SUCCESS=OK{}
2.2. heifer-common-apisix
大部分公司的网关设计都为流量网关(nginx)
+业务网关(gateway)
两层网关的设计,流量网关主要用web服务器和上游负载均衡,业务网关主要用于Spring Cloud环境下的负载均衡, heifer在设计之初也是采用了两层网关设计,两层网关设计增加了通讯成本和运维成本。
flowchart TB
nginx --> web(wbe工程) & gateway(Spring Cloud Gateway)
gateway --> server(微服务集群)
每个 service 在第启动的时候会去Apache APISIX检查是否采摘当前service的配置信息, 如果配置信息不存在就会创建一个Route. id使用md5生成
目前和Apache APISIX集成的相关插件
插件名称 | 默认开启 | 相关依赖 |
---|---|---|
RoutesCustomizer | 开启 | web环境 |
ProxyRewritePlugin | 开启 | web环境 |
CorsPlugin | 开启 | |
NacosUpstreamCustomizer | 引入nacos后自动开启 | Spring Cloud Alibaba nacos discovery |
ZipkinPlugin | 引入sleuth和zipkin2后自动开启 | spring-cloud-sleuth-zipkin |
2.2.1. Route 生成规则
Spring Boot和Spring Cloud生成时候在,Spring Cloud多了一个注册中心的原因,Route注册upstream会有所变化,其他的基本上都是一致的
2.2.1.1. Spring Booth
{
"uri": "/heifer-boot-example/*",
"name": "heifer-boot-example",
"desc": "Heifer create",
"plugins": {
"proxy-rewrite": {
"regex_uri": [
"^/heifer-boot-example/(.*)",
"/$1"
]
}
},
"upstream": {
"nodes": [
{
"host": "192.168.31.64",
"port": 58329,
"weight": 1
}
],
"type": "roundrobin",
"hash_on": "vars",
"scheme": "http",
"pass_host": "pass"
},
"labels": {
"service": "heifer-boot-example",
"source": "SPRING_BOOT"
},
"status": 1
}
2.2.1.2. Spring Cloud
{
"uri": "/heifer-boot-example/*",
"name": "heifer-boot-example",
"desc": "Heifer create",
"plugins": {
"proxy-rewrite": {
"regex_uri": ["^/heifer-boot-example/(.*)", "/$1"]
}
},
"upstream": {
"type": "roundrobin",
"hash_on": "vars",
"scheme": "http",
"discovery_type": "nacos",
"discovery_args": {
"group_name": "DEFAULT_GROUP",
"namespace_id": "public"
},
"pass_host": "pass",
"service_name": "heifer-boot-example"
},
"labels": {
"group": "DEFAULT_GROUP",
"namespace": "public",
"service": "heifer-boot-example",
"source": "SPRING_CLOUD_ALIBABA"
},
"status": 1
}
2.2.2. Zipkin
引入Zipk后json数据增加一下数据
{
"plugins": {
"zipkin": {
"endpoint": "http://192.168.31.112:9411/api/v2/spans",
"sample_ratio": 1,
"service_name": "heifer-common-apisix-example-APISIX",
"span_version": 2
}
}
}
2.3. heifer-common-dynamic-datasource
感觉没有多少用处, 还不如直接买阿里云的高性能服务器
2.4. heifer-common-feign
feign集成 okhttp, ResponseBodyAdvice自动拆箱, rpc快速失败
2.4.1. 使用 okhttp
开启okhttp需要配置文件开启,
feign:
okhttp:
enabled: true
httpclient:
enabled: false
2.4.2. ResponseBodyAdvice自动拆箱
ResponseBodyAdvice在Spring Boot当中是没有侵入性的,但是在Spring Cloud OpenFeign中具有了侵入性, 目前仅判断 feign interface的方法和类中使用了 @ResponseBodyResult
就会进行拆箱
2.4.3. rpc快速失败
使用Spring Boot的全局异常拦截, 拦截FeignException
, 然后一直将信息返回给调用方.
web浏览器->A服务->B服务->C服务
如果C服务发生了异常会被Spring Boot全局异常拦截,返回异常信息给B服务, B服务拦截FeignException
将C服务的异常信息原封不动的返回给A服务, A服务拦截FeignException
将信息原封不动的返回给web浏览器,
2.5. heifer-common-mybatis-plus
也是一个没有多少作用的功能模块
MyBatis Plus在Spring Boot环境中开发是一件很舒服的事情,但是他对项目的健康度不是很友好, 建议使用heifer提供的IService可以稍微增加一下鲁棒性
- Wrapper灵活度太高, 无法让模块形成高内聚
- soa开发时直接暴露service层时 Wrapper序列化和反序列化时有很大问题
2.6. heifer-common-nacos-discovery
目前没有多少作用,就是注册的时候Instance会注入一些元数据, 就jvm信息呀,os信息呀. 方便以后做做些基于元数据的骚操作啦
2.7. heifer-common-redis
这个模块也就那样子吧, 缓存和锁
2.7.1. Cache Abstraction
jsr107的那些注解和spring cache的那些注解啦, 基于Redis cache增加了一个时间偏移量, 防止面试的天天问我Redis雪崩和击穿这些问题的出现啦
spring.cache.redis.time-offset-to-live
偏移量配置路径也就那样子 会在timeToLive+timeToLiveOffset之间产生一个随机数,
2.7.2. lock
Redis 锁, 也就那样子啦 , 悲观锁呀,tryLock呀,没有多大用处的,
要不要增加注解模式的锁呐,反正百度一大堆, 懒得弄了
2.8. heifer-common-security
啧啧,有趣的模块了, 因为Spring Security的模块设计太繁琐了,
2.8.1. 自定义登陆
删除了默认的UserDetailsService,所以你无法登陆, 需要自定义登陆 自己造一个Controller进行多因子登陆多方便呀,比写什么过滤器呀,拦截器呀,userDetailsService什么的方便多了,随便玩了.
2.8.2. 自定义拦截器
自己去实现 IamOncePerRequestFilter 就可以了实现 token解析这些了, 然后把解析数据放进Spring Security context里面
2.8.3. 忽略路由拦截
嗯, 设计了2个部分
- 配置忽略: 适合静态资源和Controller
- 注解忽略: 适合Controller
- 实现IgnoredRequestConfigurer接口
我没有做进去,哈哈哈哈
配置忽略
heifer.security.ignore.matchers:
pattern:
get:
post:
delete:
put:
head:
patch:
options:
trace:
注解忽略
// 类和方法都支持
@RequestMapping(value = {"ignoreWebSecurity")
@IgnoreWebSecurity
public String ignoreWebSecurity() {
return "IgnoreWebSecurity";
}
2.9. heifer-common-swagger
- swagger整合了Spring Security 注解, 让swagger的note能显示Spring Security的注解
- swagger的路由自动注入到Spring Security拦截白名单 // todo
⚠️ 生产环境关闭swagger
2.10. heifer-gateway
没啥好说的, 加了nacos, loadbalancer,actuator
引用一下,加一下配置就可以了
spring:
application:
name: heifer-gateway
cloud:
nacos:
discovery:
server-addr: nuc8i7.wcj.plus:8848
gateway:
discovery:
locator:
enabled: true
server:
port: 8080
management:
endpoints:
web:
exposure:
include: gateway
endpoint:
health:
show-details: always
2.11. heifer-metadata
就是各个模块中共用的 bean啦,
2.12. heifer-plugin-aliyun-oss
就很正经的oss, 能支持多个oss操作啦, 默认实现了OssController和AliyunOssServer, 觉得不好用就自己造一个吧,
配置如下
heifer:
aliyun:
bucket1:
accessId:
accessKey:
bucket:
endpoint:
host:
expire:
bucket2:
accessId:
accessKey:
bucket:
endpoint:
host:
expire:
2.12.1. 服务端签名后直传
设计之初就按照服务端签名后直传, AliyunOssServer那几个上传是给本地文件上传用的,
看AliyunOssServer#policy
的实现的,
2.12.2. Resource支持
Resource本身是spring提供读取文件的, 和spring的原生用法一直, 很方便.
xxx就是heifer.aliyun
配置的key,
@Value("oss://xxx/sister1.jpg")
private Resource defaultFile;
2.13. heifer-plugin-iam
iam服务支持saas, 提供多租户,数据权限,功能权限,rbac,acl, 用户跨租户, 登陆等等等
因为不会大前端 所以一直没有对接前端页面, 也就 table设计有参考价值
后面说吧
2.14. heifer-plugin-iam-security
由于plugin模块设计的是偏向于业务的, 所以这一块太TMD复杂了, 涉及到到plugin和common多个模块之间的联动
-
heifer-plugin-iam
提供 授权 -
heifer-common-security
提供 鉴权模型, 需要完善多个接口才能使用
heifer-plugin-iam-security
本身设计存在有状态
和无状态
两个方案, 所以本身就是很难抉择的一个东西, 最后选择了 有状态
设计. 所以存在多个模块的耦合.
heifer-plugin-iam
+heifer-plugin-iam-security
即可实现权限的自举,
- 实现了
IamOncePerRequestFilter
来完成token的解析 - 实现了
UserPrincipalService
来完成 获取heifer-plugin-iam
的权限信息 -
UserPrincipalService
的类都修饰了@Cacheable
具有缓存性质, 在分布式缓存的情况中能保证性能, 但是在本地缓存中的存在过期问题
2.14.1. 权限拦截
JwtTokenAuthenticationFilter
实现IamOncePerRequestFilter
完成对jwt的解析
jwt无效就会立马返回401
jwt有效就调用UserPrincipalService
获取功能权限并生成一个用户注入SecurityContext中
2.14.2. user解析
支持 UserDetails和Tenant在Controller层的注入, 解决了Spring Security context这种线程隐式传递带来的问题,
没有用aop或者代理来实现注入, 不会有性能上面的问题的啦
@RequestMapping
public void hello (Tenant tenant, UserDetails userDetails, @RequestBody Object body){
return null
}
2.14.3. 数据权限
数据权限都存在tenant中了, 详细查看tenant类就可以了
3. Who is using
感谢前辈们的指点和批评。也感谢AnyEx负责人对heifer提出的意见。
鸣谢
感谢 JetBrains 对开源项目的支持
4. License
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].