美文网首页Spring Boot&Spring CloudMobDevGroupJava Community
一个基于Spring Boot的API、RESTful API项

一个基于Spring Boot的API、RESTful API项

作者: 简单的土豆 | 来源:发表于2017-06-28 15:53 被阅读17844次

    前言

    最近使用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<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() {
                @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序列化速度

    技术选型&文档


    后言

    感谢大家的支持,没想到一个简单的项目总结分享在短短两天获得这么多人的关注,还上了GitHub Trending榜单,有点受宠若惊哈,话说现在国内技术社区氛围真是越来越好了,希望大家有时间的话都能参与到开源分享的行列来,分享知识,快乐编码,共勉。

    相关文章

      网友评论

      • private菜鸟:请不要使用尾行注释
      • 0c45406e8da8:文章很棒。我们侠课岛正好在找远程录制课程视频或图文教程的朋友,我们会给到课程的需求大纲,每一节课程需要你来详细展开写一些代码举例和讲解清楚,对经验积累和创新能力有一定的要求。有兴趣联系我,微信:zhimadt
      • 树懒偷懒了:什么时候升级成SpringBoot2.0
      • da7f8302435d:谢谢分享
      • huanfuan:能请问一下楼主怎么使用Mappper简化mybatis操作吗?
      • 2ad5094bc3ce:社会社会
      • Pishum:学习了
        ResultCode枚举带上message说明参数会更好
      • chenzzz:fork了,使用了一下,很好用,但是有一个问题,正式项目中有些接口不需要签名认证,如登陆,注册之类的,这个该如何配置
        chenzzz:@陈司机 ,知道怎么根据路径判断权限了
      • cmazxiaoma:刚需,正准备学习Spring Boot
      • j4fan:楼主,看了你的文章收益匪浅,能请教下,加入我建了个表User,service里会有UserService接口,这个接口你这边直接继承了泛型的Service<T>,在Implement里面又继承了abstractService和实现了UserService接口,我是感觉既然已经继承了abstractService实现了增删改查,UserService这个接口就没必要再继承Service了,我的理解对吗?求回复谢谢
        j4fan:我知道了,是我理解错了,如果service没有继承方法则无法被注入到controller中,谢谢楼主的项目
      • 253a5ebbd6f0:用了你的这个项目,发现在配置使用新的Mapper的时候会有问题。
      • 武汉苏乞儿:不错哦,以后mybatis就可以参考你这套咯。现在我在使用spring data jpa 也是无sql,声明方法即可!!是spring推荐的!!
        简单的土豆:嗯,spring data jpa 也不错啊 通过分析方法名称自动生成sql是个亮点,不过mybatis会更灵活点。
      • 169537fe92e8:不错不错,收藏了。

        推荐下,分库分表中间件 Sharding-JDBC 源码解析 17 篇:http://www.yunai.me/categories/Sharding-JDBC/?jianshu&401


        631d17b59354:赞赞赞
      • b6166f8aea43:要炸了,一直启动不了
        java.util.concurrent.ExecutionException: org.apache.catalina.LifecycleException: Failed to start component [StandardEngine[Tomcat].StandardHost[localhost].TomcatEmbeddedContext[]]
        为什么啊 求解答
        简单的土豆:加我qq89921218帮你看看
      • 蓝色天际_4e01:这个项目DEMO真不错, 请问下 要整合AWT处理图形 除了启动类继承 Frame之外, 是否还有其他方法呢
        简单的土豆:这个我还真不清楚,你得自己查查啦:smile:
      • ALLSTAR_59c6:您好,我用您的这个框架, 打包的时候用mvn package 报错了。 错误代码36420 请问这个项目框架要怎么打包部署呢?
        简单的土豆:默认是打jar包,也可以打war包,你这个问题解决了么?不行的话加我qq89921218帮你看看
      • c35023715e9a:请问楼主 List<T> findByCondition(Condition condition);//根据条件查找 这个条件查找要怎么用? 获取条件对象后,setCountProperty好像只能指定查询属性,但如何给要查询的属性赋值呢?
        c35023715e9a:您好, Condition con = new Condition(FabricStock.class); 报了个Caused by: tk.mybatis.mapper.MapperException: 无法获取实体类com.company.project.model.FabricStock对应的表名!
        简单的土豆:文档地址:http://git.oschina.net/free/Mapper/blob/master/wiki/mapper3/3.2.Use330.md
        简单的土豆:Condition condition = new Condition(User.class);
        condition.createCriteria()
        .andEqualTo("name", "土豆"))
        .andEqualTo("mobile", "1888888888");

        这就相当于 SQL 的 where条件 ,详细用法可以API或者文档。
      • d21f09d74ed7:加了spring-boot-devtools报错,怎么解决啊?
        class com.company.project.core.Result, com.company.project.model.User cannot be cast to com.company.project.model.User
        简单的土豆:解决方案看这里:https://github.com/lihengming/spring-boot-api-project-seed/issues/33
      • f07fa1df47fc:能否给个多表操作的例子,谢谢?
        小鹏Mart:同问:如何进行多表联合查询,单表的,比如自动生成的UserMapper.xml也没有看到有任何sql语句的。Android准备转向后台小学僧一枚
        f07fa1df47fc:@简单的土豆 嗯,我现在不知道怎么调用,刚开始学。
        简单的土豆:@李队 多表在对应的Mapper.xml中写SQL即可。
      • f7a880fc7c2c:大神,请问能否增加oracle数据库支持呢?
        简单的土豆:@逍遥游_e634 Druid MyBatis 都和数据库没关系的
        简单的土豆:@逍遥游_e634 和数据库没关系呀,你把里面MySql相关的配置更改为Oracle就行了。
        f7a880fc7c2c:毕竟公司项目,还是很多人用oracle数据库的
      • f7a880fc7c2c:谢谢博主,有个不情之请,能否提供配置以便可以去掉乔大馍狒祖的启动图案?
        原因请看这篇文章:http://www.zdaox.com/p/447.html
        简单的土豆:如何自定义Spring Boot启动Banner : https://github.com/lihengming/spring-boot-api-project-seed/issues/29
        简单的土豆:@逍遥游_e634 删除resources目录下的banner.txt即可。
      • 飞翔咖啡馆:可以补充点文档,代码注释
        简单的土豆:@飞翔咖啡馆 大部分代码都有注释的【表情】
      • 誓词倾城:手动点赞喽:+1:
        简单的土豆::smile: 谢谢支持
      • 807dc588156e:不太懂,中小型api项目是什么,这个可以用来做什么呢,如果我有一个功能直接用这个可以做么
        简单的土豆:@朱利尔 不客气
        807dc588156e:@简单的土豆 明白了,谢谢
        简单的土豆:就是没有页面的项目,只返回JSON 比如给安卓IOS提供后端接口的项目,或者是前后端分离的项目。
      • 念念de爸:为好东西点要
        冬虫夏草_2333:楼主您好,为啥我生成的mapper 文件中没有 sql语句呢 只有resultMap,设置了tableConfiguration 好几个参数都没用
      • 小卟啾:加油🌺
      • 咗手倒影:感谢分享,马下慢慢学习
      • 我小时候可猛了_:main方法 成功 也打印出来dao service mapper 生成成功 但是刷新项目 关掉重新打开 都没有生成的文件 是怎么回事啊
        简单的土豆:更新下代码,已经支持mac了
        我小时候可猛了_:@简单的土豆 mac
        简单的土豆:不会吧,你是什么操作系统?
      • YoRuo_:还可以加一些东西呢

        实体类可以加 @Data注解 引入 lombok jar包就行了。

        httpClient 可以使用 retrofit2 用他的工厂方法注入一下

        :smiley: :smiley:
        2012坐忘:你好,报这个错什么意思?Error starting ApplicationContext. To display the auto-configuration report re-run your application with 'debug' enabled.
        2017-06-30 14:58:11.839 ERROR 3012 --- [ main] o.s.b.d.LoggingFailureAnalysisReporter :

        ***************************
        APPLICATION FAILED TO START
        ***************************

        Description:

        Parameter 0 of method redisTemplate in org.springframework.boot.autoconfigure.data.redis.RedisAutoConfiguration$RedisConfiguration required a bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' that could not be found.
        - Bean method 'redisConnectionFactory' not loaded because @ConditionalOnClass did not find required class 'org.apache.commons.pool2.impl.GenericObjectPool'


        Action:

        Consider revisiting the conditions above or defining a bean of type 'org.springframework.data.redis.connection.RedisConnectionFactory' in your configuration.
        YoRuo_:@简单的土豆 我的生成工具生成的都是带着

        @Data
        @AllArgsConstructor
        @NoArgsConstructor

        哈哈 就是感觉代码少点看着舒服 = = :smile:
        简单的土豆:retrofit2 不错哈,@Data就不用了吧,反正Model都是直接生成的。
      • 1d65221a9608:作者你好,能方便给出这套骨架的源码吗?没做过后台,想自己鼓捣学习一下。
        简单的土豆:文章里写的有,https://github.com/lihengming/spring-boot-api-project-seed
      • 洋__:我加了swagger2的PR,第一次pr,望采纳:grin:
      • 叫我十三吧:跟我们的架子好像,我们的只比你这个多了几个全局拦截器:smile:
        简单的土豆:@itzjm 都是和业务相关的,不具备普适性
        简单的土豆:嘿嘿,我这是精简过的,真用的时候肯定会扩展更多东东的
      • 洋__:很棒,可以加入swagger2:smiley:
        tenlee:@简单的土豆 感谢,学习了
        简单的土豆:SpringFox - Swagger2 确实挺好用的,不过这个不是每个公司都会用它来写文档,根据需要,自己添加吧,毕竟是种子项目,要保持精简。

        关于API文档&测试,在这里推荐几个好用的开源产品

        SpringFox - Swagger2 ,优点是文档漂亮、文档随API更新而更新,可在线调试API,缺点在于代码依赖、侵入太强,不利于前(移动端)后端配合。
        ShowDoc,优点是使用简单、文档漂亮、基于Markdown编辑器、提供Docker镜像快速部署,缺点是修改API后需要手动跟新文档,没法在线调试API,就是一个纯文档。
        阿里的RAP,这个就比较强大了,有兴趣的自己去了解下。
      • 开发者阿俊:很不错 架子看起来蛮标准的
      • dfe147f4a102:没碰过后台 看起来挺不错的
      • 花样棉花:写的好棒
        花样棉花: @简单的土豆 加油
        简单的土豆:@孤独总是那么可怕 :relaxed: 嘿嘿,做完东西要多总结嘛
      • f2f45b0e1a6b:不错收藏:http://www.ctolib.com/lihengming-spring-boot-api-project-seed.html
        简单的土豆:这什么网站,可以自动抓取GitHub上的信息?

      本文标题:一个基于Spring Boot的API、RESTful API项

      本文链接:https://www.haomeiwen.com/subject/blvucxtx.html