愛了!分享一個基於SpringBoot的API、RESTfulAPI項目種子(骨架)

前言

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

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

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

特徵&提供

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

愛了!分享一個基於SpringBoot的API、RESTfulAPI項目種子(骨架


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

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

/**
* 統一API響應結果封裝
*/
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方法
}
/**
* 響應碼枚舉,參考HTTP狀態碼的語義
*/
public enum ResultCode {
   SUCCESS(200),//成功
   FAIL(400),//失敗
   UNAUTHORIZED(401),//未認證(簽名錯誤)
   NOT_FOUND(404),//接口不存在
   INTERNAL_SERVER_ERROR(500);//服務器內部錯誤

   public int code;

   ResultCode(int code) {
       this.code = code;
   }
}
/**
* 響應結果生成工具
*/
public class ResultGenerator {
   private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";

   public static Result genSucce***esult() {
       return new Result()
               .setCode(ResultCode.SUCCESS)
               .setMessage(DEFAULT_SUCCESS_MESSAGE);
   }

   public static Result genSucce***esult(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);
   }
}

統一異常處理工具

 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());
               } 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() + "] 內部錯誤,請聯繫管理員");
                   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);
               return new ModelAndView();
           }

       });
   }

經常使用基礎方法抽象封裝ui

public interface Service<T> {
   void save(T model);//持久化
   void save(List<T> 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<T> findByIds(String ids);//經過多個ID查找//eg:ids -> 「1,2,3,4」
   List<T> findByCondition(Condition condition);//根據條件查找
   List<T> findAll();//獲取全部
}

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

public abstract class 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的),代碼模板可根據實際項目的需求來定製,以便漸少重複勞動。spa

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

提供簡單的接口簽名認證

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) {
                   return true;
               } else {
                   logger.warn("簽名認證失敗,請求接口:{},請求IP:{},請求參數:{}",
                           request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));

                   Result result = new Result();
                   result.setCode(ResultCode.UNAUTHORIZED).setMessage("簽名認證失敗");
                   responseResult(response, result);
                   return false;
               }
           }
       });
   }
}
/**
* 一個簡單的簽名認證,規則:
* 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)) {
           return false;
       }
       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);//比較
}

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

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

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

相關文章
相關標籤/搜索