分享一个springboot脚手架

1,601 阅读8分钟

项目介绍

在我们开发项目的时候各个项目之间总有一些可共用的代码或者配置,如果我们每新建一个项目就把代码复制粘贴再修改就显得很没有必要。于是我就做了一个 poseidon-boot-starter 该项目是基于 spring-boot的 starter 功能开发的,因此只适用于 spring-boot 项目。该项目集成了如下功能:

  • 异常通知
  • 权限配置
  • 幂等锁
  • 日志配置
  • 用户操作日志记录
  • 查询接口通用化

项目地址:github.com/muggle0/pos…

下面介绍该组件如何在我们的 spring-boot 项目中使用。

首先我们需要下载下来这个项目:

git clone https://github.com/muggle0/poseidon-boot-starter.git

然后安装到我们的本地仓库或者私有云:

cd poseidon-boot-starter

mvn install

安装完成之后在spring boot 项目中引入依赖:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-autoconfigure</artifactId>
 </dependency>

然后进行一些基础的配置:

poseidon.auto=true
poseidon.static-path=/**/*.*
poseidon.ignore-path=/**
logging.config=classpath:poseidon-logback.xml
log.dir=logs
logging.level.com.muggle=debug
spring.profiles.include=refresh

自动化配置默认是不开启的,我们需要使用 poseidon.auto=true 来启用相关功能,当开启自动化配置之后,我们必须要实现两个接口并注入到spring容器—— com.muggle.poseidon.store.SecurityStorecom.muggle.poseidon.service.TokenServiceposeidon.static-path 是 ant 匹配的静态资源路径,符合该规则的url不会被权限过滤器拦截,poseidon.ignore-path 是鉴权忽略规则,符合该规则的url不会参与鉴权,直接放行。logging.config=classpath:poseidon-logback.xml 则是采用 poseidon-boot-starter 中的logback配置策略(五彩斑斓的黑),如果采用该配置则必须指定 log.dir 日志文件输出路径。logging.level.com.muggle=debug 是指定包名以debug的级别输出,方便看一些日志调试。spring.profiles.include=refresh 当指定这个 profile 的时候,会去获取当前项目的所有url并交给 tokenService去处理。还有其他默认不开启的功能,在源码解读中介绍。

源码解读

前文我们提到过,该项目是基于 springboot 的 starter 功能开发的,其原理就是一个 springboot 定制版的 spi 这里不做太多介绍,这里我主要介绍如何在项目中使用的。

首先在 META-INF/spring.factories,中指定了要注入的类有哪些:

org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
com.muggle.poseidon.auto.ExpansibilityConfig,\
com.muggle.poseidon.auto.SecurityAutoConfig,\
com.muggle.poseidon.handler.web.WebUrlHandler,\
com.muggle.poseidon.handler.web.WebResultHandler

ExpansibilityConfig 是预留的配置类,实际未使用,SecurityAutoConfig 是整合 spring-security 相关的配置。WebUrlHandler 是处理一些特殊的url的。WebResultHandler 是统一异常处理配置。这几个类都通过 @ConditionalOnProperty(prefix = "poseidon", name = "auto", havingValue = "true", matchIfMissing = false) 来控制是否自动配置。配置类具体的源码细节这里就不介绍了。下面对各个功能的源码进行解读。

security

项目集成了security,并重写了处理器和鉴权相关的类,改造成了纯返回json,并从请求头中获取token的方式。首先我们看重写了哪些处理器:

  • com.muggle.poseidon.handler.security.PoseidonAccessDeniedHandler 鉴权失败处理器;
  • com.muggle.poseidon.handler.security.PoseidonAuthenticationFailureHandler 登录失败处理器;
  • com.muggle.poseidon.handler.security.PoseidonAuthenticationSuccessHandler 登录成功处理器;
  • com.muggle.poseidon.handler.security.PoseidonLoginUrlAuthenticationEntryPoint 未登录处理器;
  • com.muggle.poseidon.handler.security.PoseidonLogoutSuccessHandler 登出成功处理器。

以上几个处理器都是返回json的数据,如果需要修改json格式或者需要改成重定向的方式,需要手动去找到相关处理器去修改;因为这部分相关工作(比如重定向或者提示信息)都可以在前端解决,所以这里未做扩展处理。

然后是 token过滤器 com.muggle.poseidon.filter.SecurityTokenFilter,该过滤器会首先从请求头中获取token,如果获取失败则会从cookie 中获取token,key都是 token,获取到token后调用 securityStore.getUserdetail(String token) 得到一个 UserDetails ,因此,怎么通过token获取用户信息需要使用者自己去扩展,你可以直接从数据库中读,或者从缓存中读,或者直接就像jwt那样,通过解析token生成。在接下来的鉴权流程中。会从该 UserDetails 中获取 GrantedAuthority 集合 和 url 一并传递给 rooleMatch(Collection<? extends GrantedAuthority> authorities, String path) 去鉴权(如果匹配为 IgnorePath 则不鉴权直接通过)。这里的鉴权方案也是需要使用者去自己实现,鉴权方案肯定是通过匹配url来实现,那么怎么去匹配设计方案就很多了,这里提供几个思路:

  1. 当配置 spring.profiles.include=refresh 的时候会去获取项目中的所有url和相关的swagger注释。交给 TokenService.processUrl(List<AuthUrlPathDO> list) 去处理,你可以保存到数据库,为后续鉴权提供依据。
  2. 你可以制定一套url的命名规则,当鉴权的时候和 GrantedAuthority 进行直接匹配,通过规则我们就能直接判断哪些用户是有权限访问的了。
  3. 前端发请求的时候,在url末尾带上一个参数来指定哪些角色可访问(不安全,可通过伪造请求跳过鉴权)。

TokenServiceSecurityStore 中还有其他相关的方法,如登入登出等,这里不做介绍了,请参看源码注释。

统一异常处理

统一异常处理相关的类是 WebResultHandler 它定义了一些异常信息的处理策略。如果你不想要这些策略可以直接删掉它,或者自己重新注入一个异常处理器,如果你想扩展它,那么你可以参考项目中readme.md文档中的案例:

@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;
    }
}

需要注意的一个地方,如果我们项目中出现了未知的异常,应该要引起重视,因此当发生未知异常的时候会抛出一个事件。使用者可以注册监听器来监听这个事件,当发生未知的异常的时候可以及时的通知到开发人员,示例:

@Component
public class ExceptionListener implements ApplicationListener<ExceptionEvent> {

    @Override
    public void onApplicationEvent(ExceptionEvent event) {
        String message = event.getMessage();
        // TODO 将异常信息投递到邮箱等,通知开发人员系统异常,尽快处理。
    }
}

请求日志及幂等锁

想要使用请求日志的功能需要实现 DistributedLocker 接口并注册到spring容器中以激活日志切面。然后再需要拦截的方法上加上 @InterfaceAction 当我们请求这个方法时就会以info级别将请求参数输入到日志中,目前日志格式是写死的,格式形如:

INFO  com.muggle.poseidon.aop.RequestAspect - 》》》》》》 请求日志   用户名:用户未登录 url=/user/regester.jsonmethod=POSTip=127.0.0.1host=127.0.0.1port=57180classMethod=com.muggle.poseidon.oa.controller.UserController.regesterparamters [  (OaUserVO(gender=1, username=muggle, password=xxxxxx, email=null, imgUrl=null))  ]

如果想做幂等拦截 则需要在注解上添加参数 @InterfaceAction(Idempotent = true,message = "请求太频繁,请稍后再试")Idempotent 是否开启幂等拦截, message 是 被拦截后的提示信息,expertime 是幂等锁时长 。开启拦截后会 拼接一个 key String key = "lock:idempotent:" + request.getRequestURI() + ":" + username + ":" + RequestUtils.getIP(request); 然后调用 DistributedLocker.trylock(String key, Long express) 方法进行上锁,express 参数就是注解上配置 expertime,上锁方式需要使用者自己实现,你可以用redis,zookeeper,或者缓存来上锁。

部分使用者可能希望能把请求相关的信息存储到数据库,我也提供了扩展接口:RequestLogProcessor 只要实现该接口并注册到 spring 你就能在recordBefore 方法中拿到 请求相关信息 ,在recordAfterReturning 方法中拿到返回值,注意如果方法抛出异常,是不会拿到返回值的,需要自己去修改源码添加异常切面方法,异常切面方法的注解是 @AfterThrowing

日志配置

日志配置主要是两个地方,一个是 banner.txt另外一个是 poseidon-logback.xml 如果小伙伴不喜欢这个banner想去掉,只需要在自己的项目中添加一个 banner.txt 进行覆盖就行了。

poseidon-logback.xml 是对日志格式等的配置,通过 logging.config=classpath:poseidon-logback.xml 来启用该配置,同时需要指定日志文件输出路径 log.dir=/temp/xxx,启用该配置后你就可以在控制台上看到五彩斑斓的黑,如果小伙伴不喜欢这个配色,可以根据配置文件中的注释去修改。

查询配置

做出查询配置这个功能是为了减少平时开发写查询接口的开发成本,这个功能本身是结合 mybatis 的 pagehelper 插件使用的,如果你没有用这个插件,那就享受不到这个福利了。

由于各个公司或者的查询要求不尽相同,所以这里我也只做了一个顶层抽象。具体查询策略还是需要开发者去实现,将扩展性预留了出来。下面介绍这个功能的思路。

查询bean的 顶层抽象为 com.muggle.poseidon.base.BaseQuery,这里面定义查询的一些通用属性。然后在 com.muggle.poseidon.aop.QueryAspect 中拦截查询方法,拦截规则是类名必须要以 Controller 结尾,入参必须是 BaseQuery 的子类。

这个切面是没有注册的,需要手动注册一下:

    @Bean
    QueryAspect getQueryAspect(){
        return new QueryAspect();
    }

在切面的 doBefore(JoinPoint joinPoint) 中 对查询参数进行转化,在doAfterReturning(JoinPoint joinPoint, Object result) 对查询的返回值进行再次处理。实际使用中小伙伴就根据项目需求进行扩展吧。

一些基础类的封装

com.muggle.poseidon.util 收集了一些工具类,小伙伴们请按需增删。com.muggle.poseidon.base包下的 com.muggle.poseidon.base.ResultBean是对 controller 层的返回值的bean的封装。exception 包下是自定义异常的顶层抽象类。

结语

目前项目只发布了 BETA 版,后续不会再在这个版本上加新功能,当版本稳定后,我会在这个版本基础上发布一个 REALSE 版本。如果小伙伴发现bug,或者有改进意见,或者对这个项目有新的需求请务必联系我,撸码不易,点个star支持一下吧,球球了。

点击关注我的博客