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

举报
轻狂书生FS 发表于 2020/12/02 22:50:21 2020/12/02
【摘要】 1 基于Spring Boot的API、RESTful API 项目骨架 最近使用Spring Boot 配合 MyBatis 、通用Mapper插件、PageHelper分页插件 连做了几个中小型API项目,做下来觉得这套框架、工具搭配起来开发这种项目确实非常舒服,团队的反响也不错。在项目搭建和开发的过程中也总结了一些小经验,与大家分享一下。 在开发一个API项目之前,...

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

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

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

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

在相关项目上线之后,我花了点时间对该种子项目做了一些精简,并且已经把该项目分享到GitHub上面了,如果你正准备做类似项目的话,可以去克隆下来试试。

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

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

特征&提供

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

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

统一响应结果封装及生成工具


  
  1. /**
  2. * 统一API响应结果封装
  3. */
  4. public class Result {
  5. private int code;
  6. private String message;
  7. private Object data;
  8. public Result setCode(ResultCode resultCode) {
  9. this.code = resultCode.code;
  10. return this;
  11. }
  12. //省略getter、setter方法
  13. }
  14. /**
  15. * 响应码枚举,参考HTTP状态码的语义
  16. */
  17. public enum ResultCode {
  18. SUCCESS(200),//成功
  19. FAIL(400),//失败
  20. UNAUTHORIZED(401),//未认证(签名错误)
  21. NOT_FOUND(404),//接口不存在
  22. INTERNAL_SERVER_ERROR(500);//服务器内部错误
  23. public int code;
  24. ResultCode(int code) {
  25. this.code = code;
  26. }
  27. }
  28. /**
  29. * 响应结果生成工具
  30. */
  31. public class ResultGenerator {
  32. private static final String DEFAULT_SUCCESS_MESSAGE = "SUCCESS";
  33. public static Result genSuccessResult() {
  34. return new Result()
  35. .setCode(ResultCode.SUCCESS)
  36. .setMessage(DEFAULT_SUCCESS_MESSAGE);
  37. }
  38. public static Result genSuccessResult(Object data) {
  39. return new Result()
  40. .setCode(ResultCode.SUCCESS)
  41. .setMessage(DEFAULT_SUCCESS_MESSAGE)
  42. .setData(data);
  43. }
  44. public static Result genFailResult(String message) {
  45. return new Result()
  46. .setCode(ResultCode.FAIL)
  47. .setMessage(message);
  48. }
  49. }

统一异常处理


  
  1. public 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. } else if (e instanceof NoHandlerFoundException) {
  9. result.setCode(ResultCode.NOT_FOUND).setMessage("接口 [" + request.getRequestURI() + "] 不存在");
  10. } else if (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. return new ModelAndView();
  29. }
  30. });
  31. }

常用基础方法抽象封装


  
  1. public interface 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. }

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


  
  1. public abstract class 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. }

CodeGenerator 可根据表名生成对应的Model、Mapper、MapperXML、Service、ServiceImpl、Controller(默认提供POST和RESTful两套Controller模板,根据需要在 genController(tableName)方法中自己选择,默认是纯POST的),代码模板可根据实际项目的需求来定制,以便渐少重复劳动。

由于每个公司业务都不太一样,所以只提供了一些简单的通用方法模板,主要是提供一个思路来减少重复代码的编写。在我们公司的实际使用中,其实根据业务的抽象编写了大量的代码模板。扩展:优秀的代码都是如何分层的?

提供简单的接口签名认证


  
  1. public 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. return true;
  11. } else {
  12. logger.warn("签名认证失败,请求接口:{},请求IP:{},请求参数:{}",
  13. request.getRequestURI(), getIpAddress(request), JSON.toJSONString(request.getParameterMap()));
  14. Result result = new Result();
  15. result.setCode(ResultCode.UNAUTHORIZED).setMessage("签名认证失败");
  16. responseResult(response, result);
  17. return false;
  18. }
  19. }
  20. });
  21. }
  22. }
  23. /**
  24. * 一个简单的签名认证,规则:
  25. * 1. 将请求参数按ascii码排序
  26. * 2. 拼接为a=value&b=value...这样的字符串(不包含sign)
  27. * 3. 混合密钥(secret)进行md5获得签名,与请求的签名进行比较
  28. */
  29. private boolean validateSign(HttpServletRequest request) {
  30. String requestSign = request.getParameter("sign");//获得请求签名,如sign=19e907700db7ad91318424a97c54ed57
  31. if (StringUtils.isEmpty(requestSign)) {
  32. return false;
  33. }
  34. List<String> keys = new ArrayList<String>(request.getParameterMap().keySet());
  35. keys.remove("sign");//排除sign参数
  36. Collections.sort(keys);//排序
  37. StringBuilder sb = new StringBuilder();
  38. for (String key : keys) {
  39. sb.append(key).append("=").append(request.getParameter(key)).append("&");//拼接字符串
  40. }
  41. String linkString = sb.toString();
  42. linkString = StringUtils.substring(linkString, 0, linkString.length() - 1);//去除最后一个'&'
  43. String secret = "Potato";//密钥,自己修改
  44. String sign = DigestUtils.md5Hex(linkString + secret);//混合密钥md5
  45. return StringUtils.equals(sign, requestSign);//比较
  46. }

集成MyBatis、通用Mapper插件、PageHelper分页插件,实现单表业务零SQL

使用Druid Spring Boot Starter 集成Druid数据库连接池与监控

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

技术选型&文档

Spring Boot:https://www.jianshu.com/p/1a9fd8936bd8MyBatis:http://www.mybatis.org/mybatis-3/zh/index.htmlMyBatisb通用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
 

文章来源: blog.csdn.net,作者:轻狂书生FS,版权归原作者所有,如需转载,请联系作者。

原文链接:blog.csdn.net/LookForDream_/article/details/104987281

【版权声明】本文为华为云社区用户转载文章,如果您发现本社区中有涉嫌抄袭的内容,欢迎发送邮件进行举报,并提供相关证据,一经查实,本社区将立刻删除涉嫌侵权内容,举报邮箱: cloudbbs@huaweicloud.com
  • 点赞
  • 收藏
  • 关注作者

评论(0

0/1000
抱歉,系统识别当前为高风险访问,暂不支持该操作

全部回复

上滑加载中

设置昵称

在此一键设置昵称,即可参与社区互动!

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。

*长度不超过10个汉字或20个英文字符,设置后3个月内不可修改。