1. <strong id="7actg"></strong>
    2. <table id="7actg"></table>

    3. <address id="7actg"></address>
      <address id="7actg"></address>
      1. <object id="7actg"><tt id="7actg"></tt></object>

        一個基于 Spring Boot 的API、RESTful API 項目骨架

        共 7699字,需瀏覽 16分鐘

         ·

        2020-09-30 06:53

        來源:jianshu.com/p/99fcead32d35


        • 前言
        • 特征&提供
        • 技術(shù)選型&文檔

        前言

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

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

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

        在相關(guān)項目上線之后,我花了點時間對該種子項目做了一些精簡,并且已經(jīng)把該項目分享到GitHub上面了,如果你正準(zhǔn)備做類似項目的話,可以去克隆下來試試。

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

        如果在使用中發(fā)現(xiàn)問題或者有什么好建議的話歡迎提issue或pr一起來完善它。

        特征&提供

        最佳實踐的項目結(jié)構(gòu)、配置文件、精簡的POM

        注:使用代碼生成器生成代碼后會創(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)碼的語義
        */

        public enum ResultCode {
        SUCCESS(200),//成功
        FAIL(400),//失敗
        UNAUTHORIZED(401),//未認證(簽名錯誤)
        NOT_FOUND(404),//接口不存在
        INTERNAL_SERVER_ERROR(500);//服務(wù)器內(nèi)部錯誤

        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ù)失敗的異常,如“賬號或密碼錯誤”
        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)部錯誤,請聯(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);//通過主鍵刪除
        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中某個成員變量名稱(非數(shù)據(jù)表中column的名稱)查找,value需符合unique約束
        List findByIds(String ids);//通過多個ID查找//eg:ids -> “1,2,3,4”
        List findByCondition(Condition condition);//根據(jù)條件查找
        List findAll();//獲取所有
        }

        提供代碼生成器來生成基礎(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ù)表名生成對應(yīng)的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默認提供POST和RESTful兩套Controller模板,根據(jù)需要在 genController(tableName)方法中自己選擇,默認是純POST的),代碼模板可根據(jù)實際項目的需求來定制,以便漸少重復(fù)勞動。

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

        提供簡單的接口簽名認證

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

        Result result = new Result();
        result.setCode(ResultCode.UNAUTHORIZED).setMessage("簽名認證失敗");
        responseResult(response, result);
        return false;
        }
        }
        });
        }
        }
        /**
        * 一個簡單的簽名認證,規(guī)則:
        * 1. 將請求參數(shù)按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)) {
        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);//去除最后一個'&'

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

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

        集成MyBatis、通用Mapper插件、PageHelper分頁插件,實現(xiàn)單表業(yè)務(wù)零SQL

        使用Druid Spring Boot Starter 集成Druid數(shù)據(jù)庫連接池與監(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分頁插件: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

        - 墻裂推薦?-

        專注于「開發(fā)者」綜合成長的深度星球
        限時優(yōu)惠進行中
        星球近期系列更新

        往期推薦


        大廠的 Redis 都是怎么搞的?

        Spring Boot 實現(xiàn)限制賬戶登錄數(shù)

        理清 WebSocket 和 HTTP 的關(guān)系

        下方二維碼關(guān)注我

        互聯(lián)網(wǎng)草根,堅持分享技術(shù)、創(chuàng)業(yè)、產(chǎn)品心得和總結(jié)~



        點擊“閱讀原文”,領(lǐng)取 2020 年最新免費技術(shù)資料大全

        ↓↓↓?
        瀏覽 45
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        評論
        圖片
        表情
        推薦
        點贊
        評論
        收藏
        分享

        手機掃一掃分享

        分享
        舉報
        1. <strong id="7actg"></strong>
        2. <table id="7actg"></table>

        3. <address id="7actg"></address>
          <address id="7actg"></address>
          1. <object id="7actg"><tt id="7actg"></tt></object>
            91三级黄色电影片 | 91精品网站 | 疯狂操逼视频 | 中文字幕成人电影 | 国产第一页在线观看 | 日韩欧美国产黄色电影 | 日韩欧美一本 | 97人人澡人人爽人人模亚洲 | 91色亚洲 | 日韩日B视频 |