专栏名称: 武哥聊编程
这里有技术,有段子,有生活,也有资源,要不然怎么叫 “私房菜” 呢?
目录
相关文章推荐
中国铁建  ·  城轨动脉上的“新春温度” ·  17 小时前  
中国城市规划  ·  讣告 | 深切缅怀林秋华同志 ·  3 天前  
中国铁建  ·  穿山越岭筑通途 ·  昨天  
中国铁建  ·  面朝“江海” 春暖花开! ·  2 天前  
51好读  ›  专栏  ›  武哥聊编程

一个基于 Spring Boot 的 API、RESTful API 项目骨架

武哥聊编程  · 公众号  ·  · 2020-02-25 08:45

正文

作者:简单的土豆

www.jianshu.com/p/99fcead32d35

前言

最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。 在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。
在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。
然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。 所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。
在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试。

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

如果在使用中发现问题或者有什么好建议的话欢迎提issue或pr一起来完善它。

特征&提供

最佳实践的项目结构、配置文件、精简的POM

注: 使用代码生成器生成代码后会创建model、dao、service、web等包。

统一响应结果封装及生成工具
/**
 * 统一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 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);
    }
}

统一异常处理

  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) {//业务失败的异常,如“账号或密码错误”
                    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();
            }

        });
    }

常用基础方法抽象封装

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);//更新
    findById(Integer id);//通过ID查找
    findBy (String fieldName, Object value) throws TooManyResultsException//通过Model中某个成员变量名称(非数据表中column的名称)查找,value需符合unique约束
    List findByIds(String ids);//通过多个ID查找//eg:ids -> “1,2,3,4”
    List findByCondition(Condition condition);//根据条件查找
    List findAll();//获取所有
}

提供代码生成器来生成基础代码

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的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。
由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。 在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。 扩展: 优秀的代码都是如何分层的?
提供简单的接口签名认证
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






请到「今天看啥」查看全文


推荐文章
中国铁建  ·  城轨动脉上的“新春温度”
17 小时前
中国城市规划  ·  讣告 | 深切缅怀林秋华同志
3 天前
中国铁建  ·  穿山越岭筑通途
昨天
中国铁建  ·  面朝“江海” 春暖花开!
2 天前
哎咆科技  ·  iPhone内存不足,怎么办?
8 年前
中国航务周刊  ·  马士基航运推出大中华区至巴西航线服务
7 年前