一個基於 Spring Boot 的項目骨架,非常舒服!

最近使用 Spring Boot 配合 MyBatis 、通用 Mapper 插件、PageHelper 分頁插件 連做了幾個中小型 API 項目,做下來覺得這套框架、工具搭配起來開發這種項目確實非常舒服,團隊的反響也不錯。

在項目搭建和開發的過程中也總結了一些小經驗,與大家分享一下。

在開發一個 API 項目之前,搭建項目、引入依賴、配置框架這些基礎活自然不用多說,通常爲了加快項目的開發進度(早點回家)還需要封裝一些常用的類和工具,

比如統一的響應結果封裝、統一的異常處理、接口簽名認證、基礎的增刪改差方法封裝、基礎代碼生成工具等等,有了這些項目才能開工。

然而,下次再做類似的項目上述那些步驟可能還要搞一遍,雖然通常是拿過來改改,但是還是比較浪費時間。

所以,可以利用面向對象抽象、封裝的思想,抽取這類項目的共同之處封裝成了一個種子項目(估計大部分公司都會有很多類似的種子項目),這樣的話下次再開發類似的項目直接在該種子項目上迭代就可以了,減少無意義的重複工作。

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

特徵 & 提供

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

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

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

 1/**
 2 * 統一API響應結果封裝
 3 */
 4publicclass Result {
 5    privateint code;
 6    private String message;
 7    private Object data;
 8    public Result setCode(ResultCode resultCode) {
 9        this.code = resultCode.code;
10        returnthis;
11      }
12   //省略getter、setter方法
13}
14/**
15 * 響應碼枚舉,參考HTTP狀態碼的語義
16 */
17publicenum ResultCode {
18    SUCCESS(200),//成功
19    FAIL(400),//失敗
20    UNAUTHORIZED(401),//未認證(簽名錯誤)
21    NOT_FOUND(404),//接口不存在
22    INTERNAL_SERVER_ERROR(500);//服務器內部錯誤
23
24    publicint code;
25
26    ResultCode(int code) {
27        this.code = code;
28    }
29}
30/**
31 * 響應結果生成工具
32 */
33publicclass ResultGenerator {
34    privatestaticfinal String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
35
36    public static Result genSuccessResult() {
37        returnnew Result()
38                .setCode(ResultCode.SUCCESS)
39                .setMessage(DEFAULT_SUCCESS_MESSAGE);
40    }
41
42    public static Result genSuccessResult(Object data) {
43        returnnew Result()
44                .setCode(ResultCode.SUCCESS)
45                .setMessage(DEFAULT_SUCCESS_MESSAGE)
46                .setData(data);
47    }
48
49    public static Result genFailResult(String message) {
50        returnnew Result()
51                .setCode(ResultCode.FAIL)
52                .setMessage(message);
53    }
54}
55
56

統一異常處理

 1public void configureHandlerExceptionResolvers(List<HandlerExceptionResolver> exceptionResolvers) {
 2        exceptionResolvers.add(new HandlerExceptionResolver() {
 3            public ModelAndView resolveException(HttpServletRequest request, HttpServletResponse response, Object handler, Exception e) {
 4                Result result = new Result();
 5                if (e instanceof ServiceException) {//業務失敗的異常,如“賬號或密碼錯誤”
 6                    result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
 7                    logger.info(e.getMessage());
 8                } elseif (e instanceof NoHandlerFoundException) {
 9                    result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
10                } elseif (e instanceof ServletException) {
11                    result.setCode(ResultCode.FAIL).setMessage(e.getMessage());
12                } else {
13                    result.setCode(ResultCode.INTERNAL_SERVER_ERROR).setMessage("接口 [" + request.getRequestURI() + "] 內部錯誤,請聯繫管理員");
14                    String message;
15                    if (handler instanceof HandlerMethod) {
16                        HandlerMethod handlerMethod = (HandlerMethod) handler;
17                        message = String.format("接口 [%s] 出現異常,方法:%s.%s,異常摘要:%s",
18                                request.getRequestURI(),
19                                handlerMethod.getBean().getClass().getName(),
20                                handlerMethod.getMethod().getName(),
21                                e.getMessage());
22                    } else {
23                        message = e.getMessage();
24                    }
25                    logger.error(message, e);
26                }
27                responseResult(response, result);
28                returnnew ModelAndView();
29            }
30
31        });
32    }
33
34

常用基礎方法抽象封裝

 1publicinterface Service<T> {
 2    void save(T model);//持久化
 3    void save(List<T> models);//批量持久化
 4    void deleteById(Integer id);//通過主鍵刪除
 5    void deleteByIds(String ids);//批量刪除 eg:ids -> “1,2,3,4”
 6    void update(T model);//更新
 7    T findById(Integer id);//通過ID查找
 8    T findBy(String fieldName, Object value) throws TooManyResultsException; //通過Model中某個成員變量名稱(非數據表中column的名稱)查找,value需符合unique約束
 9    List<T> findByIds(String ids);//通過多個ID查找//eg:ids -> “1,2,3,4”
10    List<T> findByCondition(Condition condition);//根據條件查找
11    List<T> findAll();//獲取所有
12}
13
14

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

 1publicabstractclass CodeGenerator {
 2   ...
 3    public static void main(String[] args) {
 4        genCode("輸入表名");
 5    }
 6    public static void genCode(String... tableNames) {
 7        for (String tableName : tableNames) {
 8            //根據需求生成,不需要的注掉,模板有問題的話可以自己修改。
 9            genModelAndMapper(tableName);
10            genService(tableName);
11            genController(tableName);
12        }
13    }
14  ...
15}
16
17

CodeGenerator 可根據表名生成對應的 Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默認提供 POST 和 RESTful 兩套 Controller 模板,根據需要在 

genController(tableName) 方法中自己選擇,默認是純 POST 的),代碼模板可根據實際項目的需求來定製,以便漸少重複勞動。

由於每個公司業務都不太一樣,所以只提供了一些簡單的通用方法模板,主要是提供一個思路來減少重複代碼的編寫。

在我們公司的實際使用中,其實根據業務的抽象編寫了大量的代碼模板。

提供簡單的接口簽名認證

 1public void addInterceptors(InterceptorRegistry registry) {
 2    //接口簽名認證攔截器,該簽名認證比較簡單,實際項目中可以使用Json Web Token或其他更好的方式替代。
 3    if (!"dev".equals(env)) { //開發環境忽略簽名認證
 4        registry.addInterceptor(new HandlerInterceptorAdapter() {
 5            @Override
 6            public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
 7                //驗證簽名
 8                boolean pass = validateSign(request);
 9                if (pass) {
10                    returntrue;
11                } else {
12                    logger.warn("簽名認證失敗,請求接口:{},請求IP:{},請求參數:{}",
13                            request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
14
15                    Result result = new Result();
16                    result.setCode(ResultCode.UNAUTHORIZED).setMessage("簽名認證失敗");
17                    responseResult(response, result);
18                    returnfalse;
19                }
20            }
21        });
22    }
23}
24/**
25 * 一個簡單的簽名認證,規則:
26 * 1. 將請求參數按ascii碼排序
27 * 2. 拼接爲a=value&b=value...這樣的字符串(不包含sign)
28 * 3. 混合密鑰(secret)進行md5獲得簽名,與請求的簽名進行比較
29 */
30private boolean validateSign(HttpServletRequest request) {
31        String requestSign = request.getParameter("sign");//獲得請求籤名,如sign=19e907700db7ad91318424a97c54ed57
32        if (StringUtils.isEmpty(requestSign)) {
33            returnfalse;
34        }
35        List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
36        keys.remove("sign");//排除sign參數
37        Collections.sort(keys);//排序
38
39        StringBuilder sb = new StringBuilder();
40        for (String key : keys) {
41            sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
42        }
43        String linkString = sb.toString();
44        linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最後一個'&'
45
46        String secret = "Potato";//密鑰,自己修改
47        String sign = DigestUtils.md5Hex(linkString + secret);//混合密鑰md5
48
49        return StringUtils.equals(sign, requestSign);//比較
50}
51
52

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

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

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

本文由 Readfog 進行 AMP 轉碼,版權歸原作者所有。
來源https://mp.weixin.qq.com/s/LnRckqWWUdRF9cHmUw__YA