<kbd id="afajh"><form id="afajh"></form></kbd>
<strong id="afajh"><dl id="afajh"></dl></strong>
    <del id="afajh"><form id="afajh"></form></del>
        1. <th id="afajh"><progress id="afajh"></progress></th>
          <b id="afajh"><abbr id="afajh"></abbr></b>
          <th id="afajh"><progress id="afajh"></progress></th>

          你值得擁有!一個(gè)基于 Spring Boot 的API、RESTful API 的項(xiàng)目

          共 7628字,需瀏覽 16分鐘

           ·

          2020-10-06 06:51

          點(diǎn)擊上方“JAVA”,星標(biāo)公眾號(hào)

          重磅干貨,第一時(shí)間送達(dá)
          • 前言
          • 特征&提供
          • 技術(shù)選型&文檔

          前言

          最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分頁(yè)插件 連做了幾個(gè)中小型API項(xiàng)目,做下來(lái)覺(jué)得這套框架、工具搭配起來(lái)開(kāi)發(fā)這種項(xiàng)目確實(shí)非常舒服,團(tuán)隊(duì)的反響也不錯(cuò)。在項(xiàng)目搭建和開(kāi)發(fā)的過(guò)程中也總結(jié)了一些小經(jīng)驗(yàn),與大家分享一下。
          在開(kāi)發(fā)一個(gè)API項(xiàng)目之前,搭建項(xiàng)目、引入依賴、配置框架這些基礎(chǔ)活自然不用多說(shuō),通常為了加快項(xiàng)目的開(kāi)發(fā)進(jìn)度(早點(diǎn)回家)還需要封裝一些常用的類和工具,比如統(tǒng)一的響應(yīng)結(jié)果封裝、統(tǒng)一的異常處理、接口簽名認(rèn)證、基礎(chǔ)的增刪改差方法封裝、基礎(chǔ)代碼生成工具等等,有了這些項(xiàng)目才能開(kāi)工。
          然而,下次再做類似的項(xiàng)目上述那些步驟可能還要搞一遍,雖然通常是拿過(guò)來(lái)改改,但是還是比較浪費(fèi)時(shí)間。所以,可以利用面向?qū)ο蟪橄蟆⒎庋b的思想,抽取這類項(xiàng)目的共同之處封裝成了一個(gè)種子項(xiàng)目(估計(jì)大部分公司都會(huì)有很多類似的種子項(xiàng)目),這樣的話下次再開(kāi)發(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)問(wè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 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 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 findByIds(String ids);//通過(guò)多個(gè)ID查找//eg:ids -> “1,2,3,4”
          List findByCondition(Condition condition);//根據(jù)條件查找
          List 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ù)需求生成,不需要的注掉,模板有問(wèn)題的話可以自己修改。
          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)) { //開(kāi)發(fā)環(huán)境忽略簽名認(rèn)證
          registry.addInterceptor(new HandlerInterceptorAdapter() {
          @Override
          public 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=19e907700db7ad91318424a97c54ed57
          if (StringUtils.isEmpty(requestSign)) {
          return false;
          }
          List keys = new ArrayList(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);//混合密鑰md5

          return 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

          項(xiàng)目地址及使用文檔獲取

          關(guān)注我的另一個(gè)公眾號(hào),回復(fù):項(xiàng)目

          (一定要回復(fù):項(xiàng)目)否則啥也等不到

          掃描上方二維碼關(guān)注并回復(fù):項(xiàng)目

          就有申請(qǐng)地址啦!

          瀏覽 62
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          評(píng)論
          圖片
          表情
          推薦
          點(diǎn)贊
          評(píng)論
          收藏
          分享

          手機(jī)掃一掃分享

          分享
          舉報(bào)
          <kbd id="afajh"><form id="afajh"></form></kbd>
          <strong id="afajh"><dl id="afajh"></dl></strong>
            <del id="afajh"><form id="afajh"></form></del>
                1. <th id="afajh"><progress id="afajh"></progress></th>
                  <b id="afajh"><abbr id="afajh"></abbr></b>
                  <th id="afajh"><progress id="afajh"></progress></th>
                  欧美一级电影在线播放 | 天天操狠狠操 | 操屄视频免费在线观看 | 亚洲无 码A片在线 | 夜夜操夜夜操夜夜操 |