分享一個(gè)springboot腳手架

項(xiàng)目介紹

在我們開(kāi)發(fā)項(xiàng)目的時(shí)候各個(gè)項(xiàng)目之間總有一些可共用的代碼或者配置,如果我們每新建一個(gè)項(xiàng)目就把代碼復(fù)制粘貼再修改就顯得很沒(méi)有必要。于是我就做了一個(gè) poseidon-boot-starter 該項(xiàng)目是基于 spring-boot的 starter 功能開(kāi)發(fā)的,因此只適用于 spring-boot 項(xiàng)目。該項(xiàng)目集成了如下功能:

  • 異常通知
  • 權(quán)限配置
  • 冪等鎖
  • 日志配置
  • 用戶(hù)操作日志記錄
  • 查詢(xún)接口通用化

項(xiàng)目地址:https://github.com/muggle0/poseidon-boot-starter

下面介紹該組件如何在我們的 spring-boot 項(xiàng)目中使用。

首先我們需要下載下來(lái)這個(gè)項(xiàng)目:

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

然后安裝到我們的本地倉(cāng)庫(kù)或者私有云:

cd poseidon-boot-starter

mvn install

安裝完成之后在spring boot 項(xiàng)目中引入依賴(lài):

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

然后進(jìn)行一些基礎(chǔ)的配置:

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

自動(dòng)化配置默認(rèn)是不開(kāi)啟的,我們需要使用 poseidon.auto=true 來(lái)啟用相關(guān)功能,當(dāng)開(kāi)啟自動(dòng)化配置之后,我們必須要實(shí)現(xiàn)兩個(gè)接口并注入到spring容器—— com.muggle.poseidon.store.SecurityStorecom.muggle.poseidon.service.TokenService 。poseidon.static-path 是 ant 匹配的靜態(tài)資源路徑,符合該規(guī)則的url不會(huì)被權(quán)限過(guò)濾器攔截,poseidon.ignore-path 是鑒權(quán)忽略規(guī)則,符合該規(guī)則的url不會(huì)參與鑒權(quán),直接放行。logging.config=classpath:poseidon-logback.xml 則是采用 poseidon-boot-starter 中的logback配置策略(五彩斑斕的黑),如果采用該配置則必須指定 log.dir 日志文件輸出路徑。logging.level.com.muggle=debug 是指定包名以debug的級(jí)別輸出,方便看一些日志調(diào)試。spring.profiles.include=refresh 當(dāng)指定這個(gè) profile 的時(shí)候,會(huì)去獲取當(dāng)前項(xiàng)目的所有url并交給 tokenService去處理。還有其他默認(rèn)不開(kāi)啟的功能,在源碼解讀中介紹。

源碼解讀

前文我們提到過(guò),該項(xiàng)目是基于 springboot 的 starter 功能開(kāi)發(fā)的,其原理就是一個(gè) springboot 定制版的 spi 這里不做太多介紹,這里我主要介紹如何在項(xiàng)目中使用的。

首先在 META-INF/spring.factories,中指定了要注入的類(lèi)有哪些:

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 是預(yù)留的配置類(lèi),實(shí)際未使用,SecurityAutoConfig 是整合 spring-security 相關(guān)的配置。WebUrlHandler 是處理一些特殊的url的。WebResultHandler 是統(tǒng)一異常處理配置。這幾個(gè)類(lèi)都通過(guò) @ConditionalOnProperty(prefix = "poseidon", name = "auto", havingValue = "true", matchIfMissing = false) 來(lái)控制是否自動(dòng)配置。配置類(lèi)具體的源碼細(xì)節(jié)這里就不介紹了。下面對(duì)各個(gè)功能的源碼進(jìn)行解讀。

security

項(xiàng)目集成了security,并重寫(xiě)了處理器和鑒權(quán)相關(guān)的類(lèi),改造成了純返回json,并從請(qǐng)求頭中獲取token的方式。首先我們看重寫(xiě)了哪些處理器:

  • com.muggle.poseidon.handler.security.PoseidonAccessDeniedHandler 鑒權(quán)失敗處理器;
  • com.muggle.poseidon.handler.security.PoseidonAuthenticationFailureHandler 登錄失敗處理器;
  • com.muggle.poseidon.handler.security.PoseidonAuthenticationSuccessHandler 登錄成功處理器;
  • com.muggle.poseidon.handler.security.PoseidonLoginUrlAuthenticationEntryPoint 未登錄處理器;
  • com.muggle.poseidon.handler.security.PoseidonLogoutSuccessHandler 登出成功處理器。

以上幾個(gè)處理器都是返回json的數(shù)據(jù),如果需要修改json格式或者需要改成重定向的方式,需要手動(dòng)去找到相關(guān)處理器去修改;因?yàn)檫@部分相關(guān)工作(比如重定向或者提示信息)都可以在前端解決,所以這里未做擴(kuò)展處理。

然后是 token過(guò)濾器 com.muggle.poseidon.filter.SecurityTokenFilter,該過(guò)濾器會(huì)首先從請(qǐng)求頭中獲取token,如果獲取失敗則會(huì)從cookie 中獲取token,key都是 token,獲取到token后調(diào)用 securityStore.getUserdetail(String token) 得到一個(gè) UserDetails ,因此,怎么通過(guò)token獲取用戶(hù)信息需要使用者自己去擴(kuò)展,你可以直接從數(shù)據(jù)庫(kù)中讀,或者從緩存中讀,或者直接就像jwt那樣,通過(guò)解析token生成。在接下來(lái)的鑒權(quán)流程中。會(huì)從該 UserDetails 中獲取 GrantedAuthority 集合 和 url 一并傳遞給 rooleMatch(Collection<? extends GrantedAuthority> authorities, String path) 去鑒權(quán)(如果匹配為 IgnorePath 則不鑒權(quán)直接通過(guò))。這里的鑒權(quán)方案也是需要使用者去自己實(shí)現(xiàn),鑒權(quán)方案肯定是通過(guò)匹配url來(lái)實(shí)現(xiàn),那么怎么去匹配設(shè)計(jì)方案就很多了,這里提供幾個(gè)思路:

  1. 當(dāng)配置 spring.profiles.include=refresh 的時(shí)候會(huì)去獲取項(xiàng)目中的所有url和相關(guān)的swagger注釋。交給 TokenService.processUrl(List<AuthUrlPathDO> list) 去處理,你可以保存到數(shù)據(jù)庫(kù),為后續(xù)鑒權(quán)提供依據(jù)。
  2. 你可以制定一套u(yù)rl的命名規(guī)則,當(dāng)鑒權(quán)的時(shí)候和 GrantedAuthority 進(jìn)行直接匹配,通過(guò)規(guī)則我們就能直接判斷哪些用戶(hù)是有權(quán)限訪(fǎng)問(wèn)的了。
  3. 前端發(fā)請(qǐng)求的時(shí)候,在url末尾帶上一個(gè)參數(shù)來(lái)指定哪些角色可訪(fǎng)問(wèn)(不安全,可通過(guò)偽造請(qǐng)求跳過(guò)鑒權(quán))。

TokenServiceSecurityStore 中還有其他相關(guān)的方法,如登入登出等,這里不做介紹了,請(qǐng)參看源碼注釋。

統(tǒng)一異常處理

統(tǒng)一異常處理相關(guān)的類(lèi)是 WebResultHandler 它定義了一些異常信息的處理策略。如果你不想要這些策略可以直接刪掉它,或者自己重新注入一個(gè)異常處理器,如果你想擴(kuò)展它,那么你可以參考項(xiàng)目中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("參數(shù)未通過(guò)校驗(yàn)", e);
        ResultBean error = ResultBean.error(e.getConstraintViolations().iterator().next().getMessage());
        return error;
    }
}

需要注意的一個(gè)地方,如果我們項(xiàng)目中出現(xiàn)了未知的異常,應(yīng)該要引起重視,因此當(dāng)發(fā)生未知異常的時(shí)候會(huì)拋出一個(gè)事件。使用者可以注冊(cè)監(jiān)聽(tīng)器來(lái)監(jiān)聽(tīng)這個(gè)事件,當(dāng)發(fā)生未知的異常的時(shí)候可以及時(shí)的通知到開(kāi)發(fā)人員,示例:

@Component
public class ExceptionListener implements ApplicationListener<ExceptionEvent> {

    @Override
    public void onApplicationEvent(ExceptionEvent event) {
        String message = event.getMessage();
        // TODO 將異常信息投遞到郵箱等,通知開(kāi)發(fā)人員系統(tǒng)異常,盡快處理。
    }
}

請(qǐng)求日志及冪等鎖

想要使用請(qǐng)求日志的功能需要實(shí)現(xiàn) DistributedLocker 接口并注冊(cè)到spring容器中以激活日志切面。然后再需要攔截的方法上加上 @InterfaceAction 當(dāng)我們請(qǐng)求這個(gè)方法時(shí)就會(huì)以info級(jí)別將請(qǐng)求參數(shù)輸入到日志中,目前日志格式是寫(xiě)死的,格式形如:

INFO  com.muggle.poseidon.aop.RequestAspect - 》》》》》》 請(qǐng)求日志   用戶(hù)名:用戶(hù)未登錄 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))  ]

如果想做冪等攔截 則需要在注解上添加參數(shù) @InterfaceAction(Idempotent = true,message = "請(qǐng)求太頻繁,請(qǐng)稍后再試") ,Idempotent 是否開(kāi)啟冪等攔截,
message 是 被攔截后的提示信息,expertime 是冪等鎖時(shí)長(zhǎng) 。開(kāi)啟攔截后會(huì) 拼接一個(gè) key String key = "lock:idempotent:" + request.getRequestURI() + ":" + username + ":" + RequestUtils.getIP(request); 然后調(diào)用 DistributedLocker.trylock(String key, Long express) 方法進(jìn)行上鎖,express 參數(shù)就是注解上配置 expertime,上鎖方式需要使用者自己實(shí)現(xiàn),你可以用redis,zookeeper,或者緩存來(lái)上鎖。

部分使用者可能希望能把請(qǐng)求相關(guān)的信息存儲(chǔ)到數(shù)據(jù)庫(kù),我也提供了擴(kuò)展接口:RequestLogProcessor 只要實(shí)現(xiàn)該接口并注冊(cè)到 spring 你就能在recordBefore 方法中拿到 請(qǐng)求相關(guān)信息 ,在recordAfterReturning 方法中拿到返回值,注意如果方法拋出異常,是不會(huì)拿到返回值的,需要自己去修改源碼添加異常切面方法,異常切面方法的注解是 @AfterThrowing。

日志配置

日志配置主要是兩個(gè)地方,一個(gè)是 banner.txt另外一個(gè)是 poseidon-logback.xml 如果小伙伴不喜歡這個(gè)banner想去掉,只需要在自己的項(xiàng)目中添加一個(gè) banner.txt 進(jìn)行覆蓋就行了。

poseidon-logback.xml 是對(duì)日志格式等的配置,通過(guò) logging.config=classpath:poseidon-logback.xml 來(lái)啟用該配置,同時(shí)需要指定日志文件輸出路徑 log.dir=/temp/xxx,啟用該配置后你就可以在控制臺(tái)上看到五彩斑斕的黑,如果小伙伴不喜歡這個(gè)配色,可以根據(jù)配置文件中的注釋去修改。

查詢(xún)配置

做出查詢(xún)配置這個(gè)功能是為了減少平時(shí)開(kāi)發(fā)寫(xiě)查詢(xún)接口的開(kāi)發(fā)成本,這個(gè)功能本身是結(jié)合 mybatis 的 pagehelper 插件使用的,如果你沒(méi)有用這個(gè)插件,那就享受不到這個(gè)福利了。

由于各個(gè)公司或者的查詢(xún)要求不盡相同,所以這里我也只做了一個(gè)頂層抽象。具體查詢(xún)策略還是需要開(kāi)發(fā)者去實(shí)現(xiàn),將擴(kuò)展性預(yù)留了出來(lái)。下面介紹這個(gè)功能的思路。

查詢(xún)bean的 頂層抽象為 com.muggle.poseidon.base.BaseQuery,這里面定義查詢(xún)的一些通用屬性。然后在 com.muggle.poseidon.aop.QueryAspect 中攔截查詢(xún)方法,攔截規(guī)則是類(lèi)名必須要以 Controller 結(jié)尾,入?yún)⒈仨毷?BaseQuery 的子類(lèi)。

這個(gè)切面是沒(méi)有注冊(cè)的,需要手動(dòng)注冊(cè)一下:

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

在切面的 doBefore(JoinPoint joinPoint) 中 對(duì)查詢(xún)參數(shù)進(jìn)行轉(zhuǎn)化,在doAfterReturning(JoinPoint joinPoint, Object result)
對(duì)查詢(xún)的返回值進(jìn)行再次處理。實(shí)際使用中小伙伴就根據(jù)項(xiàng)目需求進(jìn)行擴(kuò)展吧。

一些基礎(chǔ)類(lèi)的封裝

com.muggle.poseidon.util 收集了一些工具類(lèi),小伙伴們請(qǐng)按需增刪。com.muggle.poseidon.base包下的 com.muggle.poseidon.base.ResultBean是對(duì) controller 層的返回值的bean的封裝。exception 包下是自定義異常的頂層抽象類(lèi)。

結(jié)語(yǔ)

目前項(xiàng)目只發(fā)布了 BETA 版,后續(xù)不會(huì)再在這個(gè)版本上加新功能,當(dāng)版本穩(wěn)定后,我會(huì)在這個(gè)版本基礎(chǔ)上發(fā)布一個(gè) REALSE 版本。如果小伙伴發(fā)現(xiàn)bug,或者有改進(jìn)意見(jiàn),或者對(duì)這個(gè)項(xiàng)目有新的需求請(qǐng)務(wù)必聯(lián)系我,擼碼不易,點(diǎn)個(gè)star支持一下吧,球球了。

點(diǎn)擊關(guān)注我的博客

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

友情鏈接更多精彩內(nèi)容