javascript
你值得拥有!一个基于 Spring Boot 的API、RESTful API 的项目
點(diǎn)擊上方?好好學(xué)java?,選擇?星標(biāo)?公眾號(hào)
重磅資訊、干貨,第一時(shí)間送達(dá)
今日推薦:騰訊推出高性能 RPC 開發(fā)框架
個(gè)人原創(chuàng)100W+訪問量博客:點(diǎn)擊前往,查看更多
前言
最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分頁(yè)插件 連做了幾個(gè)中小型API項(xiàng)目,做下來(lái)覺得這套框架、工具搭配起來(lái)開發(fā)這種項(xiàng)目確實(shí)非常舒服,團(tuán)隊(duì)的反響也不錯(cuò)。在項(xiàng)目搭建和開發(fā)的過(guò)程中也總結(jié)了一些小經(jīng)驗(yàn),與大家分享一下。
在開發(fā)一個(gè)API項(xiàng)目之前,搭建項(xiàng)目、引入依賴、配置框架這些基礎(chǔ)活自然不用多說(shuō),通常為了加快項(xiàng)目的開發(fā)進(jìn)度(早點(diǎn)回家)還需要封裝一些常用的類和工具,比如統(tǒng)一的響應(yīng)結(jié)果封裝、統(tǒng)一的異常處理、接口簽名認(rèn)證、基礎(chǔ)的增刪改差方法封裝、基礎(chǔ)代碼生成工具等等,有了這些項(xiàng)目才能開工。
然而,下次再做類似的項(xiàng)目上述那些步驟可能還要搞一遍,雖然通常是拿過(guò)來(lái)改改,但是還是比較浪費(fèi)時(shí)間。所以,可以利用面向?qū)ο蟪橄蟆⒎庋b的思想,抽取這類項(xiàng)目的共同之處封裝成了一個(gè)種子項(xiàng)目(估計(jì)大部分公司都會(huì)有很多類似的種子項(xiàng)目),這樣的話下次再開發(fā)類似的項(xiàng)目直接在該種子項(xiàng)目上迭代就可以了,減少無(wú)意義的重復(fù)工作。
在相關(guān)項(xiàng)目上線之后,我花了點(diǎn)時(shí)間對(duì)該種子項(xiàng)目做了一些精簡(jiǎn),并且已經(jīng)把該項(xiàng)目分享到GitHub上面了,如果你正準(zhǔn)備做類似項(xiàng)目的話,可以去克隆下來(lái)試試。
項(xiàng)目地址&使用文檔:https://github.com/lihengming/spring-boot-api-project-seed 。
如果在使用中發(fā)現(xiàn)問題或者有什么好建議的話歡迎提issue或pr一起來(lái)完善它。
特征&提供
最佳實(shí)踐的項(xiàng)目結(jié)構(gòu)、配置文件、精簡(jiǎn)的POM
注:使用代碼生成器生成代碼后會(huì)創(chuàng)建model、dao、service、web等包。
統(tǒng)一響應(yīng)結(jié)果封裝及生成工具
/*** 統(tǒng)一API響應(yīng)結(jié)果封裝*/ public class Result {private int code;private String message;private Object data;public Result setCode(ResultCode resultCode) {this.code = resultCode.code;return this;}//省略getter、setter方法 } /*** 響應(yīng)碼枚舉,參考HTTP狀態(tài)碼的語(yǔ)義*/ public enum ResultCode {SUCCESS(200),//成功FAIL(400),//失敗UNAUTHORIZED(401),//未認(rèn)證(簽名錯(cuò)誤)NOT_FOUND(404),//接口不存在INTERNAL_SERVER_ERROR(500);//服務(wù)器內(nèi)部錯(cuò)誤public int code;ResultCode(int code) {this.code = code;} } /*** 響應(yīng)結(jié)果生成工具*/ public class ResultGenerator {private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";public static Result genSuccessResult() {return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE);}public static Result genSuccessResult(Object data) {return new Result().setCode(ResultCode.SUCCESS).setMessage(DEFAULT_SUCCESS_MESSAGE).setData(data);}public static Result genFailResult(String message) {return new Result().setCode(ResultCode.FAIL).setMessage(message);} }統(tǒng)一異常處理
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) {//業(yè)務(wù)失敗的異常,如“賬號(hào)或密碼錯(cuò)誤”result.setCode(ResultCode.FAIL).setMessage(e.getMessage());logger.info(e.getMessage());} else if (e instanceof NoHandlerFoundException) {result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");} else if (e instanceof ServletException) {result.setCode(ResultCode.FAIL).setMessage(e.getMessage());} else {result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 內(nèi)部錯(cuò)誤,請(qǐng)聯(lián)系管理員");String message;if (handler instanceof HandlerMethod) {HandlerMethod handlerMethod = (HandlerMethod) handler;message = String.format("接口 [%s] 出現(xiàn)異常,方法:%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);return new ModelAndView();}});}常用基礎(chǔ)方法抽象封裝
public interface Service<T> {void save(T model);//持久化void save(List<T> models);//批量持久化void deleteById(Integer id);//通過(guò)主鍵刪除void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”void update(T model);//更新T findById(Integer id);//通過(guò)ID查找T findBy(String fieldName, Object value) throws TooManyResultsException; //通過(guò)Model中某個(gè)成員變量名稱(非數(shù)據(jù)表中column的名稱)查找,value需符合unique約束List<T> findByIds(String ids);//通過(guò)多個(gè)ID查找//eg:ids -> “1,2,3,4”List<T> findByCondition(Condition condition);//根據(jù)條件查找List<T> findAll();//獲取所有 }提供代碼生成器來(lái)生成基礎(chǔ)代碼
public abstract class CodeGenerator {...public static void main(String[] args) {genCode("輸入表名");}public static void genCode(String... tableNames) {for (String tableName : tableNames) {//根據(jù)需求生成,不需要的注掉,模板有問題的話可以自己修改。genModelAndMapper(tableName);genService(tableName);genController(tableName);}}... }CodeGenerator 可根據(jù)表名生成對(duì)應(yīng)的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默認(rèn)提供POST和RESTful兩套Controller模板,根據(jù)需要在 genController(tableName)方法中自己選擇,默認(rèn)是純POST的),代碼模板可根據(jù)實(shí)際項(xiàng)目的需求來(lái)定制,以便漸少重復(fù)勞動(dòng)。
由于每個(gè)公司業(yè)務(wù)都不太一樣,所以只提供了一些簡(jiǎn)單的通用方法模板,主要是提供一個(gè)思路來(lái)減少重復(fù)代碼的編寫。在我們公司的實(shí)際使用中,其實(shí)根據(jù)業(yè)務(wù)的抽象編寫了大量的代碼模板。
提供簡(jiǎn)單的接口簽名認(rèn)證
public void addInterceptors(InterceptorRegistry registry) {//接口簽名認(rèn)證攔截器,該簽名認(rèn)證比較簡(jiǎn)單,實(shí)際項(xiàng)目中可以使用Json Web Token或其他更好的方式替代。if (!"dev".equals(env)) { //開發(fā)環(huán)境忽略簽名認(rèn)證registry.addInterceptor(new HandlerInterceptorAdapter() {@Overridepublic boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {//驗(yàn)證簽名boolean pass = validateSign(request);if (pass) {return true;} else {logger.warn("簽名認(rèn)證失敗,請(qǐng)求接口:{},請(qǐng)求IP:{},請(qǐng)求參數(shù):{}",request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));Result result = new Result();result.setCode(ResultCode.UNAUTHORIZED).setMessage("簽名認(rèn)證失敗");responseResult(response, result);return false;}}});} } /*** 一個(gè)簡(jiǎn)單的簽名認(rèn)證,規(guī)則:* 1. 將請(qǐng)求參數(shù)按ascii碼排序* 2. 拼接為a=value&b=value...這樣的字符串(不包含sign)* 3. 混合密鑰(secret)進(jìn)行md5獲得簽名,與請(qǐng)求的簽名進(jìn)行比較*/ private boolean validateSign(HttpServletRequest request) {String requestSign = request.getParameter("sign");//獲得請(qǐng)求簽名,如sign=19e907700db7ad91318424a97c54ed57if (StringUtils.isEmpty(requestSign)) {return false;}List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());keys.remove("sign");//排除sign參數(shù)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);//去除最后一個(gè)'&'String secret = "Potato";//密鑰,自己修改String sign = DigestUtils.md5Hex(linkString + secret);//混合密鑰md5return StringUtils.equals(sign, requestSign);//比較 }集成MyBatis、通用Mapper插件、PageHelper分頁(yè)插件,實(shí)現(xiàn)單表業(yè)務(wù)零SQL
使用Druid Spring Boot Starter 集成Druid數(shù)據(jù)庫(kù)連接池與監(jiān)控
使用FastJsonHttpMessageConverter,提高JSON序列化速度
技術(shù)選型&文檔
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分頁(yè)插件: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
來(lái)源:簡(jiǎn)單的土豆
jianshu.com/p/99fcead32d35
最后,再附上我歷時(shí)三個(gè)月總結(jié)的?Java 面試 + Java 后端技術(shù)學(xué)習(xí)指南,筆者這幾年及春招的總結(jié),github 1.4k star,拿去不謝!下載方式1.?首先掃描下方二維碼 2.?后臺(tái)回復(fù)「Java面試」即可獲取總結(jié)
以上是生活随笔為你收集整理的你值得拥有!一个基于 Spring Boot 的API、RESTful API 的项目的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 为什么HTTPS是安全的
- 下一篇: 11 张流程图帮你搞定 Spring B