一個基於 Spring Boot 的項目骨架

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


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


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


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

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


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


特徵&提供


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


一個基於 Spring Boot 的項目骨架


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


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

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


<code>/**
* 響應碼枚舉,參考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);
}
}/<code>


統一異常處理

<code>public void configureHandlerExceptionResolvers(List<handlerexceptionresolver> 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();
}

});
}/<handlerexceptionresolver>/<code>


常用基礎方法抽象封裝

<code>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();//獲取所有

}
/<code>


提供代碼生成器來生成基礎代碼

<code>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);
}
}
...
}/<code>


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


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


提供簡單的接口簽名認證

<code>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;
}
}
});
}
}/<code>


<code>/**
* 一個簡單的簽名認證,規則:
* 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<string> keys = new ArrayList<string>(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);//比較
}/<string>/<string>/<code>


集成MyBatis、通用Mapper插件、PageHelper分頁插件,實現單表業務零SQL


使用Druid Spring Boot Starter 集成Druid數據庫連接池與監控


使用FastJsonHttpMessageConverter,提高JSON序列化速度


技術選型&文檔


<code>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/<code>


分享到:


相關文章: