一个基于Spring Boot的APIRESTful API项目种子(骨架)

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了一个基于Spring Boot的APIRESTful API项目种子(骨架)相关的知识,希望对你有一定的参考价值。

前言
最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。

在开发一个API项目之前,搭建项目、引入依赖、配置框架这些基础活自然不用多说,通常为了加快项目的开发进度(早点回家)还需要封装一些常用的类和工具,比如统一的响应结果封装、统一的异常处理、接口签名认证、基础的增删改差方法封装、基础代码生成工具等等,有了这些项目才能开工。

然而,下次再做类似的项目上述那些步骤可能还要搞一遍,虽然通常是拿过来改改,但是还是比较浪费时间。所以,可以利用面向对象抽象、封装的思想,抽取这类项目的共同之处封装成了一个种子项目(估计大部分公司都会有很多类似的种子项目),这样的话下次再开发类似的项目直接在该种子项目上迭代就可以了,减少无意义的重复工作。

特征&提供
最佳实践的项目结构、配置文件、精简的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 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();
    }

    });

    }
    常用基础方法抽象封装
    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();//获取所有
    }
    提供代码生成器来生成基础代码
    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() {br/>@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码排序
    1. 拼接为a=value&b=value...这样的字符串(不包含sign)
    1. 混合密钥(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序列化速度

以上是关于一个基于Spring Boot的APIRESTful API项目种子(骨架)的主要内容,如果未能解决你的问题,请参考以下文章

一个依赖解决 Spring Boot 反爬虫,防止接口盗刷

一个依赖搞定 Spring Boot 反爬虫,防止接口盗刷!

Spring Boot,Spring Security - 基于 XML 的配置

基于spring boot的统一异常处理

Spring Boot 基于角色的身份验证

无法实现对 Spring-Boot API 的基于角色的访问