一個基於 Spring Boot 的項目骨架

點擊上方 "程序員小樂"關注, 星標或置頂一起成長

每天凌晨00點00分, 第一時間與你相約


每日英文

You can't just sit there and wait for life to come to you. You have to go get it.

你不能無所事事的坐等人生帶給你一切,你必須得自己努力爭取。


每日掏心話

生活總會給你另一個機會,這個機會叫明天。生活沒有過去,也沒有曾經,不管什麼事只要過去了,就會慢慢忘掉。

作者:簡單的土豆 | 責編:樂樂

來自:ImportNew

一個基於 Spring Boot 的項目骨架

程序員小樂(ID:study_tech)第 844 次推文 圖片來自百度


往日回顧:手把手教你如何搭建一款自己的私有百度網盤?


正文


最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分頁插件 連做了幾個中小型API項目,做下來覺得這套框架、工具搭配起來開發這種項目確實非常舒服,團隊的反響也不錯。在項目搭建和開發的過程中也總結了一些小經驗,與大家分享一下。


在開發一個API項目之前,搭建項目、引入依賴、配置框架這些基礎活自然不用多說,通常為了加快項目的開發進度(早點回家)還需要封裝一些常用的類和工具,比如統一的響應結果封裝、統一的異常處理、接口簽名認證、基礎的增刪改差方法封裝、基礎代碼生成工具等等,有了這些項目才能開工。


然而,下次再做類似的項目上述那些步驟可能還要搞一遍,雖然通常是拿過來改改,但是還是比較浪費時間。所以,可以利用面向對象抽象、封裝的思想,抽取這類項目的共同之處封裝成了一個種子項目(估計大部分公司都會有很多類似的種子項目),這樣的話下次再開發類似的項目直接在該種子項目上迭代就可以了,減少無意義的重複工作。


在相關項目上線之後,我花了點時間對該種子項目做了一些精簡,並且已經把該項目分享到GitHub上面了,如果你正準備做類似項目的話,可以去克隆下來試試。

項目地址&使用文檔:https://github.com/lihengming/spring-boot-api-project-seed 。

如果在使用中發現問題或者有什麼好建議的話歡迎提issue或pr一起來完善它。

特徵&提供


最佳實踐的項目結構、配置文件、精簡的POM


一個基於 Spring Boot 的項目骨架


注:使用代碼生成器生成代碼後會創建model、dao、service、web等包。


統一響應結果封裝及生成工具


/**
* 統一API響應結果封裝
*/
publicclass Result {
privateint code;
private String message;
private Object data;
public Result setCode(ResultCode resultCode) {
this.code = resultCode.code;
returnthis;
}
//省略getter、setter方法
}


/**
* 響應碼枚舉,參考HTTP狀態碼的語義
*/
publicenum ResultCode {
SUCCESS(200),//成功
FAIL(400),//失敗
UNAUTHORIZED(401),//未認證(簽名錯誤)
NOT_FOUND(404),//接口不存在
INTERNAL_SERVER_ERROR(500);//服務器內部錯誤



publicint code;

ResultCode(int code) {
this.code = code;
}
}
/**
* 響應結果生成工具
*/
publicclass ResultGenerator {
privatestaticfinal String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

public static Result genSuccessResult() {
returnnew Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE);
}

public static Result genSuccessResult(Object data) {
returnnew Result()
.setCode(ResultCode.SUCCESS)
.setMessage(DEFAULT_SUCCESS_MESSAGE)
.setData(data);
}

public static Result genFailResult(String message) {
returnnew Result()
.setCode(ResultCode.FAIL)
.setMessage(message);
}
}


統一異常處理


public void configureHandlerExceptionResolvers(List exceptionResolvers) {
exceptionResolvers.add(new HandlerExceptionResolver() {
public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
Result result = new Result();
if (e instanceof ServiceException) {//業務失敗的異常,如“賬號或密碼錯誤”
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());


logger.info(e.getMessage());
} elseif (e instanceof NoHandlerFoundException) {
result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
} elseif (e instanceof ServletException) {
result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
} else {
result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 內部錯誤,請聯繫管理員");
String message;
if (handler instanceof HandlerMethod) {
HandlerMethod handlerMethod = (HandlerMethod) handler;
message = String.format("接口 [%s] 出現異常,方法:%s.%s,異常摘要:%s",
request.getRequestURI(),
handlerMethod.getBean().getClass().getName(),
handlerMethod.getMethod().getName(),
e.getMessage());
} else {
message = e.getMessage();
}
logger.error(message, e);
}
responseResult(response, result);
returnnew ModelAndView();
}

});
}


常用基礎方法抽象封裝
publicinterface Service {
void save(T model);//持久化
void save(List models);//批量持久化
void deleteById(Integer id);//通過主鍵刪除
void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
void update(T model);//更新
T findById(Integer id);//通過ID查找
T findBy(String fieldName, Object value) throws TooManyResultsException; //通過Model中某個成員變量名稱(非數據表中column的名稱)查找,value需符合unique約束
List findByIds(String ids);//通過多個ID查找//eg:ids -> “1,2,3,4”


List findByCondition(Condition condition);//根據條件查找
List findAll();//獲取所有
}


提供代碼生成器來生成基礎代碼
publicabstractclass CodeGenerator {
...
public static void main(String[] args) {
genCode("輸入表名");
}
public static void genCode(String... tableNames) {
for (String tableName : tableNames) {
//根據需求生成,不需要的注掉,模板有問題的話可以自己修改。
genModelAndMapper(tableName);
genService(tableName);
genController(tableName);
}
}
...
}


CodeGenerator 可根據表名生成對應的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默認提供POST和RESTful兩套Controller模板,根據需要在 genController(tableName)方法中自己選擇,默認是純POST的),代碼模板可根據實際項目的需求來定製,以便漸少重複勞動。


由於每個公司業務都不太一樣,所以只提供了一些簡單的通用方法模板,主要是提供一個思路來減少重複代碼的編寫。在我們公司的實際使用中,其實根據業務的抽象編寫了大量的代碼模板。


提供簡單的接口簽名認證
public void addInterceptors(InterceptorRegistry registry) {
//接口簽名認證攔截器,該簽名認證比較簡單,實際項目中可以使用Json Web Token或其他更好的方式替代。
if (!"dev".equals(env)) { //開發環境忽略簽名認證
registry.addInterceptor(new HandlerInterceptorAdapter() {
@Override
public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
//驗證簽名
boolean pass = validateSign(request);
if (pass) {
returntrue;
} else {
logger.warn("簽名認證失敗,請求接口:{},請求IP:{},請求參數:{}",
request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

Result result = new Result();
result.setCode(ResultCode.UNAUTHORIZED).setMessage("簽名認證失敗");
responseResult(response, result);
returnfalse;
}
}
});
}
}


/**
* 一個簡單的簽名認證,規則:
* 1. 將請求參數按ascii碼排序
* 2. 拼接為a=value&b=value...這樣的字符串(不包含sign)
* 3. 混合密鑰(secret)進行md5獲得簽名,與請求的簽名進行比較
*/
private boolean validateSign(HttpServletRequest request) {


String requestSign = request.getParameter("sign");//獲得請求籤名,如sign=19e907700db7ad91318424a97c54ed57
if (StringUtils.isEmpty(requestSign)) {
returnfalse;
}
List keys = new ArrayList(request.getParameterMap().keySet());
keys.remove("sign");//排除sign參數
Collections.sort(keys);//排序

StringBuilder sb = new StringBuilder();
for (String key : keys) {
sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
}
String linkString = sb.toString();
linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最後一個'&'

String secret = "Potato";//密鑰,自己修改
String sign = DigestUtils.md5Hex(linkString + secret);//混合密鑰md5

return StringUtils.equals(sign, requestSign);//比較
}


集成MyBatis、通用Mapper插件、PageHelper分頁插件,實現單表業務零SQL
使用Druid Spring Boot Starter 集成Druid數據庫連接池與監控
使用FastJsonHttpMessageConverter,提高JSON序列化速度

技術選型&文檔


Spring Boot:https://www.jianshu.com/p/1a9fd8936bd8

MyBatis:http://www.mybatis.org/mybatis-3/zh/index.html

MyBatisb通用Mapper插件:https://mapperhelper.github.io/docs/


MyBatis PageHelper分頁插件:https://pagehelper.github.io/

Druid Spring Boot Starter:https://github.com/alibaba/druid/tree/master/druid-spring-boot-starter/

Fastjson:https://github.com/Alibaba/fastjson/wiki/%E9%A6%96%E9%A1%B5


歡迎在留言區留下你的觀點,一起討論提高。如果今天的文章讓你有新的啟發,學習能力的提升上有新的認識,歡迎轉發分享給更多人。


猜你還想看


阿里、騰訊、百度、華為、京東最新面試題彙集

5萬字長文!SpringBoot 操作 ElasticSearch 詳解

關於 MyBatis 我總結了 10 種通用的寫法

分佈式事務之 RocketMQ 事務消息詳解

關注訂閱號「程序員小樂」,收看更多精彩內容
嘿,你在看嗎?


分享到:


相關文章: