很nice 的 springboot 项目的脚手架,请路过的朋友点一个star。
对 springboot 项目感兴趣的小伙伴可以关注 poseidon
对 springCloud 感兴趣的小伙伴可以关注 poseidon-cloud
本人的书:muggle的书
博客:muggle.javaboy.org
框架集成功能:
- 异常报警
- 权限动态配置
- 幂等锁
- 日志分组
- 用户操作日志记录
- 查询接口通用化。
-
2020.3.9 1.0.0.Beta 发布。多方测试bug
-
2020.4.5 项目从cloud-starter 分裂出来,补充部分不成熟地方。
-
2020.4.16 发布
0.0.1.Beta
版 -
2020.4.17 开始开发webflux版,提供对webflux的支持(webflux 分支 版本号0.0.1-webflux.Alpha)。
-
2020.6.1
0.0.1.Beta
添加查询组件功能,并计划发布0.0.1.release
。
具体使用案例可参考 sofia 小伙伴喜欢的可以关注下这个项目嗷。
第一步拉取项目 并且使用 maven 安装到本地。 拉取项目:
git clone https://github.com/muggle0/poseidon-boot-starter.git
安装到本地仓库:
cd poseidon-boot-starter
mvn install
第二步 创建 spring boot工程 并引入依赖:
<dependency>
<groupId>com.muggle</groupId>
<artifactId>poseidon-boot-starter</artifactId>
<version>0.0.1.Beta</version>
</dependency>
第三步开启自动化配置并注册 tokenService
和 securityStore
appplication.properties:
spring.profiles.active=dev
# 使用内置logback配置代替spring logback配置
logging.config=classpath:poseidon-logback.xml
# 配置内置logback参数 log文件位置
log.dir=logs
#是否开启自动化配置,开启自动化配置后会注入权限管理,幂等锁,统一异常处理功能
poseidon.auto=true
# 权限管理配置忽略的 url 使用ant匹配符。
poseidon.ignore-path=/**
接下来往spring容器注册
/**
* muggle
*/
@Service
public class SofiaSecurityStore implements SecurityStore {
private static String publicKey="test";
@Override
public UserDetails getUserdetail(String token) throws BasePoseidonCheckException {
String role = JwtTokenUtils.getBody(token, publicKey, "role");
String username = JwtTokenUtils.getBody(token, publicKey, "username");
SofiaUserDO sofiaUserDO = new SofiaUserDO();
sofiaUserDO.setAccountNonExpired(true);
sofiaUserDO.setAccountNonLocked(true);
SimpleGrantedAuthority admin = new SimpleGrantedAuthority(role);
sofiaUserDO.setAuthorities(Arrays.asList(admin));
sofiaUserDO.setEnabled(true);
sofiaUserDO.setUsername(username);
return sofiaUserDO;
}
@Override
public String signUserMessage(UserDetails userDetails) {
Map<String, Object> roleMap=new HashMap<>();
roleMap.put("username",userDetails.getUsername());
roleMap.put("role","/admin/**");
String token = JwtTokenUtils.createToken(roleMap, publicKey, 12L);
return token;
}
@Override
public Boolean cleanToken(String s) {
return null;
}
}
@Component
public class SofiaTokenService implements TokenService {
private AntPathMatcher antPathMatcher=new AntPathMatcher();
@Override
public UserDetails getUserById(Long aLong) {
return null;
}
@Override
public boolean rooleMatch(Collection<? extends GrantedAuthority> collection, String s) {
boolean match=false;
for (GrantedAuthority grantedAuthority : collection) {
String authority = grantedAuthority.getAuthority();
match = antPathMatcher.match(authority, s);
}
return match;
}
@Override
public void saveUrlInfo(List<AuthUrlPathDO> list) {
}
@Override
public UserDetails login(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse) throws SimplePoseidonCheckException {
return loadUserByUsername("admin");
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
SofiaUserDO sofiaUserDO = new SofiaUserDO();
sofiaUserDO.setAccountNonExpired(true);
sofiaUserDO.setAccountNonLocked(true);
sofiaUserDO.setEnabled(true);
sofiaUserDO.setUsername(username);
return sofiaUserDO;
}
}
访问自定义的接口,我们就能看到权限拦截成功了。
SecurityStore.getUserdetail
方法 根据token获取用户信息,当请求头 header
里面 有 token
如:
POST http://localhost:8080/test
Content-Type:application/json
token:eyJhbGciOiJIUzUxMiJ9.eyJyb2xlIjoiYWRtaW4iLCJ1c2VybmFtZSI6Ii9hZG1pbi8qKiJ9.8FOhRpN7DDii2YuuuXdcOU2BofwoEJ6YxCBb4k69sPCGxs9vpH9nd_cTjhvfilvS8itbiUJfgOAT3P9DtDvmiQ
这时框架会调用调用该方法来获取用户信息 userDetails
, 接下来会调用 userDetails.getAuthorities()
获取角色集合并获取请求的 uri 一并传递给 TokenService.rooleMatch
让实现者自己做权限判断。
登录请求的url 为 /sign_in
访问该 url 的时候会调用 TokenService.login
方法来获取一个token返回给前端。TokenService.loadUserByUsername
为 security 框架默认的方法,这里不会去使用它,所以可以只实现该方法,不必去实现其逻辑
当 spring 的 profiles
也就是配置 spring.profiles.active
为 "uat","sit","online","refresh" 时 会获取 swagger 接口注解上的信息并调用: TokenService.saveUrlInfo()
的方法,你可以选择直接return 也可以 将这些 url 存到数据库 用于做权限控制
当使用者实现接口 DistributedLocker
并注册到spring容器的时候会激活日志切面和幂等拦截。幂等拦截和日志切面使用示例:
@RestController
@RequestMapping("/admin")
public class TestController {
@GetMapping("/test")
@InterfaceAction(Idempotent = true,expertime = 4L)
public ResultBean test(){
return ResultBean.success();
}
@PostMapping("/test0")
@InterfaceAction
public ResultBean test0(){
return ResultBean.success();
}
}
@InterfaceAction
是切面注解,当使用该注解的时候 会拦截用户请求 和请求信息打印 info 日志:
POSEIDON---- 2020-04-06 12:15:06 [http-nio-8080-exec-3] INFO com.muggle.poseidon.aop.RequestAspect - 请求日志 username=admin url=/admin/test0 method=POST ip=0:0:0:0:0:0:0:1 classMethod=com.fight.controller.TestController.test0 paramters=[]
注解默认不开启幂等拦截,如果想开启幂等拦截需要将 Idempotent
设置为 true,其他的幂等参数设置 expertime
为接口上锁时间, message
为幂等拦截后返回给前端的提示信息。
可能有部分开发者对用户行为日志写库的需求,我这里未做支持,如果有该需求的开发者可以自己修改 RequestAspect
源码。
如果你有对日志写库或者二次处理的需求,你只需要实现 RequestLogProcessor
接口并注册就能获取到请求与入参。
框架收录了平时使用的 utlis 类在 com.muggle.poseidon.util
包下,使用者可以按需修改调整。com.muggle.poseidon.base
包下提供了基类,基础异常和 ResultBean
使用者请根据实际情况按需调整
com.muggle.poseidon.handler.web.WebResultHandler.WebResultHandler
是统一异常处理类,该类定义了部分异常捕获后返回给前端的json信息,用户根据实际情况按需调整。
这里需要注意一个 异常报警功能的使用:
@ExceptionHandler(value = {Exception.class})
public ResultBean exceptionHandler(Exception e, HttpServletRequest req) {
try {
UserDetails userInfo = UserInfoUtils.getUserInfo();
ExceptionEvent exceptionEvent = new ExceptionEvent(String.format("系统异常: [ %s ] 时间戳: [%d] ", e.getMessage(),System.currentTimeMillis()), this);
applicationContext.publishEvent(exceptionEvent);
log.error("系统异常:" + req.getMethod() + req.getRequestURI()+" user: "+userInfo.toString() , e);
return ResultBean.error("系统异常");
}catch (Exception err){
log.error("紧急!!! 严重的异常",err);
return ResultBean.error("系统发生严重的错误");
}
}
当系统抛出无法处理的异常的时候,会发布一个事件 ExceptionEvent
,我们可以通过监听这个事件来实现系统报警:
@Component
public class ExceptionListener implements ApplicationListener<ExceptionEvent> {
@Override
public void onApplicationEvent(ExceptionEvent event) {
String message = event.getMessage();
// TODO 将异常信息投递到邮箱等,通知开发人员系统异常,尽快处理。
}
}
我们还可以添加我们自定义的异常拦截,我们需要,继承 com.muggle.poseidon.handler.web.WebResultHandler
然后添加我们需要的处理方法, 示例:
@RestControllerAdvice
@Configuration
public class MyWebResultHandler extends WebResultHandler {
private static final Logger log = LoggerFactory.getLogger(OAwebResultHandler.class);
@ExceptionHandler({ConstraintViolationException.class})
public ResultBean methodArgumentNotValidException(ConstraintViolationException e, HttpServletRequest req) {
log.error("参数未通过校验", e);
ResultBean error = ResultBean.error(e.getConstraintViolations().iterator().next().getMessage());
return error;
}
}
示例中添加了一个参数校验异常处理方法。
对于其他的异常处理逻辑请阅读源码 com.muggle.poseidon.handler.web.WebResultHandler
类。
为了减少开发者对查询接口开发的开发时间,该框架设计了一个简单的配合 PageHelper
使用的插件,该功能由于构思不是很成熟,
所以其部分实现需要框架的使用者自己去完成。下面介绍该功能的使用方式和原理:
第一步注册查询拦截切面:
@Bean
QueryAspect getQueryAspect(){
return new QueryAspect();
}
第二步定义查询类:
public class MyQuery extends BaseQuery {
@ApiModelProperty(value = "是否有效")
private Boolean enable;
@ApiModelProperty(value = "请求类型")
private String requestType;
@ApiModelProperty(value = "类名")
private String className;
@ApiModelProperty(value = "方法名")
private String methodName;
@ApiModelProperty(value = "请求路径")
private String url;
@ApiModelProperty(value = "描述")
private String description;
private String finalSql;
@Override
public void processSql() {
Map<String, Operator> operatorMap = this.getOperatorMap();
StringBuilder builder=new StringBuilder();
if (operatorMap!=null){
Iterator<String> iterator = operatorMap.keySet().iterator();
while (iterator.hasNext()) {
String next = iterator.next();
try {
Object field=getFieldValue(next);
if ((field instanceof Number)){
builder.append(next+operatorMap.get(next).getValue()+field);
}else {
builder.append(next+operatorMap.get(next).getValue()+"'"+field+"'");
}
} catch (NoSuchFieldException | IllegalAccessException e) {
throw new OAException("查询参数异常:"+next);
}
}
}
List<String> groupBy = this.getGroupBy();
if (!CollectionUtils.isEmpty(groupBy)){
builder.append(" group by");
for (int i = 0; i < groupBy.size(); i++) {
if (i==groupBy.size()-1){
builder.append(groupBy.get(i));
}else {
builder.append(groupBy.get(i)+",");
}
}
}
this.finalSql=builder.toString();
}
private Object getFieldValue(String next) throws NoSuchFieldException, IllegalAccessException {
Field field = this.getClass().getDeclaredField(next);
//打开私有访问
field.setAccessible(true);
//获取属性值
return field.get(this);
}
@Override
public String getFinalSql() {
return finalSql;
}
public void setFinalSql(String finalSql) {
this.finalSql = finalSql;
}
}
然后在 mybatis
的xml中使用 finalSql
:
<select id="getUrlInfo" resultMap="base">
select * from table
where 1=1
<if test="finalSql !=null ">
and ${finalSql}
</if>
</select>
这个功能的设计思路是 在 BaseQuery
的 orderBy
字段为排序的list,groupBy
是 排序的list, operatorMap
是字段的运算符。
在 QueryAspect
的切面中它做的事情很简单,调用 processSql()
方法和 init()
方法,同时会调用 QuerySqlProcessor
的方法进行返回值和查询参数的自定义处理。
所以这个功能只定义最基础的骨架,其内部的具体实现还是要使用者自己去完成。
使用框架logback配置:logging.config=classpath:poseidon-logback.xml
具体配置信息信息在源码中有注释。
- 权限控制: 因为不同项目对权限的管理粒度不一样,所以框架将这一部分暴露给使用者实现;关于权限的管控思路——粗粒度权限管控的可以以url的命名来简单管控如
/admin/**
的url 只能 admin角色访问,以此类推。
对于粒度再细一点的,可以将需要进行权限管控的url 缓存到内存,然后通过用户角色来判断是否有权限访问该url。
对于粒度更加细一点的权限控制,可以结合上面两种方法做权限管控,从url命名上约束接口是否需要权限控制,然后再将角色的url权限保存到数据库中。
- 自动化配置扩展: 部分使用者可能希望诸如自定义的序列化器,拦截器,过滤器,监听器等也能根据项目需要实现自动化配置,我们可以在框架中加入我们自己的bean
假如我现在想自动化配置一个监听器:
public class ExceptionListener implements ApplicationListener<ExceptionEvent> {
@Override
public void onApplicationEvent(ExceptionEvent event) {
}
}
我们在 com.muggle.poseidon.auto.ExpansibilityConfig
类中注册bean:
@Bean
ExceptionListener listener(){
return new ExceptionListener();
}
就可以让引用该starter包的spring容器中注册该监听器,或者你也可以在 spring.factories
加上你的类限定名:
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.muggle.poseidon.handler.web.WebResultHandler,\
com.username.lestener.ExceptionListener
微信号:muggle_wx
喜欢的朋友 starter 一下吧,撸码不容易