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>
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.
集成了 Spring MVC和validation
提供全局拦截器, 对异常进行全局拦截器
异常返回格式
{
"code": "",
"message": "",
"data": {},
"@comment": {
"code": "/** 业务错误码 */",
"message": "/** 信息描述 */",
"data": "/** 返回参数 */"
}
}
全局统一返回
Controller的类或者方法中使用了 @ResponseBodyResult
就会进行装箱
{
"code": "",
"message": "",
"data": {},
"@comment": {
"code": "/** 业务错误码 */",
"message": "/** 信息描述 */",
"data": "/** 返回参数 */"
}
}
提供ResponseBodyResult
自动拆箱,会判断是否启用ResponseBodyResult
添加快速失败
plus.wcj.heifer.metadata.exception.ResultException是定制的异常类, 支持国际化, 枚举类, 占位符
自定义异常枚举需要实现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 ...
}
默认添加异常信息国际化
messages.properties格式定义
# {class全路径}.{枚举}={异常信息}
# 无占位符
plus.wcj.heifer.boot.common.exception.ResultStatusEnum.SUCCESS=OK
# 占位符返回
plus.wcj.heifer.boot.common.exception.ResultStatusEnum.SUCCESS=OK{}
大部分公司的网关设计都为流量网关(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 |
Spring Boot和Spring Cloud生成时候在,Spring Cloud多了一个注册中心的原因,Route注册upstream会有所变化,其他的基本上都是一致的
{
"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
}
{
"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
}
引入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
}
}
}
感觉没有多少用处, 还不如直接买阿里云的高性能服务器
feign集成 okhttp, ResponseBodyAdvice自动拆箱, rpc快速失败
开启okhttp需要配置文件开启,
feign:
okhttp:
enabled: true
httpclient:
enabled: false
ResponseBodyAdvice在Spring Boot当中是没有侵入性的,但是在Spring Cloud OpenFeign中具有了侵入性, 目前仅判断 feign interface的方法和类中使用了 @ResponseBodyResult
就会进行拆箱
使用Spring Boot的全局异常拦截, 拦截FeignException
, 然后一直将信息返回给调用方.
web浏览器->A服务->B服务->C服务
如果C服务发生了异常会被Spring Boot全局异常拦截,返回异常信息给B服务, B服务拦截FeignException
将C服务的异常信息原封不动的返回给A服务, A服务拦截FeignException
将信息原封不动的返回给web浏览器,
也是一个没有多少作用的功能模块
MyBatis Plus在Spring Boot环境中开发是一件很舒服的事情,但是他对项目的健康度不是很友好, 建议使用heifer提供的IService可以稍微增加一下鲁棒性
- Wrapper灵活度太高, 无法让模块形成高内聚
- soa开发时直接暴露service层时 Wrapper序列化和反序列化时有很大问题
目前没有多少作用,就是注册的时候Instance会注入一些元数据, 就jvm信息呀,os信息呀. 方便以后做做些基于元数据的骚操作啦
这个模块也就那样子吧, 缓存和锁
jsr107的那些注解和spring cache的那些注解啦, 基于Redis cache增加了一个时间偏移量, 防止面试的天天问我Redis雪崩和击穿这些问题的出现啦
spring.cache.redis.time-offset-to-live
偏移量配置路径也就那样子 会在timeToLive+timeToLiveOffset之间产生一个随机数,
Redis 锁, 也就那样子啦 , 悲观锁呀,tryLock呀,没有多大用处的,
要不要增加注解模式的锁呐,反正百度一大堆, 懒得弄了
啧啧,有趣的模块了, 因为Spring Security的模块设计太繁琐了,
删除了默认的UserDetailsService,所以你无法登陆, 需要自定义登陆 自己造一个Controller进行多因子登陆多方便呀,比写什么过滤器呀,拦截器呀,userDetailsService什么的方便多了,随便玩了.
自己去实现 IamOncePerRequestFilter 就可以了实现 token解析这些了, 然后把解析数据放进Spring Security context里面
嗯, 设计了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";
}
- swagger整合了Spring Security 注解, 让swagger的note能显示Spring Security的注解
- swagger的路由自动注入到Spring Security拦截白名单 // todo
没啥好说的, 加了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
就是各个模块中共用的 bean啦,
就很正经的oss, 能支持多个oss操作啦, 默认实现了OssController和AliyunOssServer, 觉得不好用就自己造一个吧,
配置如下
heifer:
aliyun:
bucket1:
accessId:
accessKey:
bucket:
endpoint:
host:
expire:
bucket2:
accessId:
accessKey:
bucket:
endpoint:
host:
expire:
设计之初就按照服务端签名后直传, AliyunOssServer那几个上传是给本地文件上传用的,
看AliyunOssServer#policy
的实现的,
Resource本身是spring提供读取文件的, 和spring的原生用法一直, 很方便.
xxx就是heifer.aliyun
配置的key,
@Value("oss://xxx/sister1.jpg")
private Resource defaultFile;
iam服务支持saas, 提供多租户,数据权限,功能权限,rbac,acl, 用户跨租户, 登陆等等等
因为不会大前端 所以一直没有对接前端页面, 也就 table设计有参考价值
后面说吧
由于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
具有缓存性质, 在分布式缓存的情况中能保证性能, 但是在本地缓存中的存在过期问题
JwtTokenAuthenticationFilter
实现IamOncePerRequestFilter
完成对jwt的解析
jwt无效就会立马返回401
jwt有效就调用UserPrincipalService
获取功能权限并生成一个用户注入SecurityContext中
支持 UserDetails和Tenant在Controller层的注入, 解决了Spring Security context这种线程隐式传递带来的问题,
没有用aop或者代理来实现注入, 不会有性能上面的问题的啦
@RequestMapping
public void hello (Tenant tenant, UserDetails userDetails, @RequestBody Object body){
return null
}
数据权限都存在tenant中了, 详细查看tenant类就可以了
感谢前辈们的指点和批评。也感谢AnyEx负责人对heifer提出的意见。
感谢 JetBrains 对开源项目的支持
The project license file is available https://raw.githubusercontent.com/spring-cloud/spring-cloud-openfeign/main/LICENSE.txt[here].