美文网首页Spring全家桶实践
Spring全家桶实践-REST风格接口实现

Spring全家桶实践-REST风格接口实现

作者: 一块自由的砖 | 来源:发表于2019-07-26 15:18 被阅读0次

    背景

    什么是REST(来自百度百科)

    REST全称是Representational State Transfer,中文意思是表述(编者注:通常译为表征)性状态转移。 它首次出现在2000年Roy Fielding的博士论文中,Roy Fielding是HTTP规范的主要编写者之一。 他在论文中提到:"我这篇文章的写作目的,就是想在符合架构原理的前提下,理解和评估以网络为基础的应用软件的架构设计,得到一个功能强、性能好、适宜通信的架构。REST指的是一组架构约束条件和原则。" 如果一个架构符合REST的约束条件和原则,我们就称它为RESTful架构。

    REST本身并没有创造新的技术、组件或服务,而隐藏在RESTful背后的理念就是使用Web的现有特征和能力, 更好地使用现有Web标准中的一些准则和约束。虽然REST本身受Web技术的影响很深, 但是理论上REST架构风格并不是绑定在HTTP上,只不过目前HTTP是唯一与REST相关的实例。 所以我们这里描述的REST也是通过HTTP实现的REST。

    理解RESTful

    要理解RESTful架构,需要理解Representational State Transfer这个词组到底是什么意思,它的每一个词都有些什么涵义。REST基本原则。

    *资源与URI
    *统一资源接口
    *资源的表述
    *资源的链接
    *状态的转移

    环境

    *jdk 1.8
    *spring boot 2.1.6
    *maven 4.0.0
    *intellj idea 2018.2
    *window 10

    配置

    image

    实操

    目录结构

    在上一版增加了user模块,主要用来处理用户信息相关的业务

    1563268741229.jpg

    源码

    主要是为了学习接口,不引入数据操作,Dao层模拟下数据库的操作输出即可。
    平台用户基本信息实体(Member.java)

    package com.springboot.action.saas.modules.user.po;
    
    import lombok.Data;
    /*
    * 平台用户信息数据
    * */
    //这里使用@Data注解,是lombok包中的,自动生成一些操作成员变量的方法
    @Data
    public class Member {
        //用户id
        private Integer id;
        //用户手机号
        private String phone;
        //用户密码
        private String password;
        //用户昵称
        private String nickname;
        //邮箱
        private String email;
        //注册时间
        private Integer ctime;
        //更新时间
        private Integer utime;
        //最近登录时间
        private Integer last_login_time;
        //最近登录ip
        private String last_login_ip;
        //邀请码
        private String invite_code;
        //激活(绑定手机)状态标示
        private Integer is_active;
        //删除状态标识
        private Integer is_delete;
    }
    

    数据访问对象(MemberDao.java),这里为了循序渐进的学习,先不引入数据的操作,先模拟记录下

    package com.springboot.action.saas.modules.user.dao;
    
    import com.springboot.action.saas.modules.user.po.Member;
    import org.springframework.stereotype.Component;
    
    import java.util.ArrayList;
    import java.util.List;
    
    @Component
    public class MemberDao {
    
        private Member testmember;
    
        public void add(Member member){
            System.out.println("MemberDao.add");
            testmember = new Member();
            testmember.setId(1);
            testmember.setNickname("demo");
        }
    
        public void save(Member member){
            System.out.println("MemberDao.save");
            testmember.setNickname(member.getNickname());
        }
    
        public void delete(Integer id){
            System.out.println("MemberDao.delete"+id);
        }
    
        public Member findOne(Integer id) {
            System.out.println("MemberDao.findOne"+id);
            return testmember;
        }
    
        public List<Member> findAll() {
            System.out.println("MemberDao.findAll");
            List<Member> list = new ArrayList<Member>();
            Member member1 = new Member();
            member1.setId(1);
            member1.setNickname("demo1");
            list.add(member1);
            Member member2 = new Member();
            member2.setId(2);
            member2.setNickname("demo2");
            list.add(member2);
            return list;
        }
    }
    

    事务处接口定义(MemberService.java)

    package com.springboot.action.saas.modules.user.service;
    
    import com.springboot.action.saas.modules.user.po.Member;
    
    import java.util.List;
    /*
    * 业务接口定义
    * */
    public interface MemberService {
        public void addMember(Member member);
        public void deleteMember(Integer id);
        public void updateMember(Member member);
        public Member findMember(Integer id);
        public List<Member> findAllMember();
    }
    

    事务处接口实现类(MemberServiceImpl.java)

    package com.springboot.action.saas.modules.user.service.impl;
    
    import com.springboot.action.saas.modules.user.dao.MemberDao;
    import com.springboot.action.saas.modules.user.po.Member;
    import com.springboot.action.saas.modules.user.service.MemberService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.stereotype.Service;
    
    import java.util.List;
    
    /*
     * 业务接口是实现
     * */
    @Service
    public class MemberServiceImpl implements MemberService {
        @Autowired
        private MemberDao memberDao;
        @Override
        public void addMember(Member member) {
            memberDao.add(member);
        }
    
        @Override
        public void deleteMember(Integer id) {
            memberDao.delete(id);
        }
    
        @Override
        public void updateMember(Member member) {
            memberDao.save(member);
        }
    
        @Override
        public Member findMember(Integer id) {
            return memberDao.findOne(id);
        }
    
        @Override
        public List<Member> findAllMember() {
            return memberDao.findAll();
        }
    }
    

    控制层实现(MemberController.java)

    package com.springboot.action.saas.modules.user.controller;
    
    import com.springboot.action.saas.modules.user.po.Member;
    import com.springboot.action.saas.modules.user.service.MemberService;
    import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.web.bind.annotation.*;
    
    import java.util.List;
    /*
    *  restful 风格接口
    * */
    //@RestController 代替 @Controller,省略以后的 @ResponseBody
    @RestController
    //处理请求地址映射的注解,可用于类或方法上。用于类上,表示类中的所有响应请求的方法都是以该地址作为父路径。
    @RequestMapping("/member")
    public class MemberController {
        @Autowired
        private MemberService memberService;
    
        /**
         * 显示所有Member,请求url:"http://xxx/member/v1/findall"
         *
         * @return List
         */
        @RequestMapping(value = "/v1/findall")
        public List<Member> findAllMember() {
            return memberService.findAllMember();
        }
    
    
        /**
         * 查找id对应的Member信息,请求url:"http://xxx/member/v1/findone/1"
         *
         * @param id
         * @return Member
         */
    
        // == @RequestMapping(value = "/v1/findone/{id}", method = RequestMethod.GET)
        @GetMapping("/v1/findone/{id}")
        public Member findMember(@PathVariable("id") Integer id) {
            return memberService.findMember(id);
        }
    
    
        /**
         * 删除id对应的Member,请求url:"http://xxx/member/v1/deleteone/4"
         * 可以通过 jquery的 $.ajax者postman方法,并type="delete"
         *
         * @param id
         *
         * @return void
         */
        // == @RequestMapping(value = "/v1/deleteone/{id}", method = RequestMethod.DELETE)
        @DeleteMapping("/v1/deleteone/{id}")
        public void deleteMember(@PathVariable("id") Integer id) {
            memberService.deleteMember(id);
        }
    
    
        /**
         * 增加member信息,请求url:"http://xxx/member/v1/addone"
         * 数据通过<form>表单者postman模拟验证
         *
         * @param member
         *
         * @return void
         */
        // == @RequestMapping(value="/v1/addone",method=RequestMethod.POST)
        @PostMapping("/v1/addone")
        public void addMember(Member member) {
            memberService.addMember(member);
        }
    
    
        /**
         * 修改对应的Member,请求url:"http://xxx/member/v1/updateone"
         * 验证:可以通过 jquery的 $.ajax方法或者postman,并type="put",同时注意data形式——A=a&B=b&C=c
         *
         * @param member
         *
         * @return void
         */
        // == @RequestMapping(value="/v1/addone",method=RequestMethod.PUT)
        @PutMapping("/v1/updateone")
        public void updateMember(Member member) {
            memberService.updateMember(member);
        }
    }
    

    运行测试输出

    获取全部用户列表


    image.png

    打tag 1.0.2版本,提交代码。

    git tag -a v1.0.2 -m "实现用户模块简单的rest风格接口"

    git push origin v1.0.2

    github地址:https://github.com/horacepei/springsaas

    正确/出错/异常返回数据格式统一

    如果出现类型不对的请求或者参数错误会报错,实际上客户端是期望返回统一格式返回值。实现原理,可以在接口方法返回之后,在序列化器对该结果进行序列化之前,我们可以使用ResponseBodyAdvice在这两个阶段之间对结果进行操作,因此我们可以使用该Adivice在序列化之前,拦截返回结果,对结果进行包装,然后再将包装后的结果返回,最后序列化成对应的JSON字符串。
    统一返回数据格式(RestReturn.java)

    package com.springboot.action.saas.common.controller;
    
    import com.fasterxml.jackson.databind.ObjectMapper;
    import lombok.Data;
    
    import java.time.Instant;
    
    /**
     * 接口返回统一数据结构
     */
    @Data
    public class RestReturn {
        //是否成功标志
        private boolean success;
        //code错误码
        private Integer code;
        //外带数据信息
        private Object data;
        //前端进行页面展示的信息
        private Object message;
        //返回时间戳
        private Long currentTime;
        /**
         *构造函数(无参数)
         */
        public RestReturn() {
            //毫秒
            this.currentTime = Instant.now().toEpochMilli();
        }
        /**
         *构造函数(有参数)
         */
        public RestReturn(boolean success, Integer code, Object data, Object message) {
            this.success = success;
            this.code = code;
            this.data = data;
            this.message = message;
            //毫秒
            this.currentTime = Instant.now().toEpochMilli();
        }
        @Override
        public String toString() {
            return "RestReturn{" +
                    "success=" + success +
                    ",code='" + code + '\'' +
                    ",data=" + data +
                    ",message=" + message +
                    ",currentTime=" + currentTime +
                    '}';
        }
    
        public RestReturn success(Object data, Object message) {
            this.success = true;
            this.code = 0;
            this.data = data;
            this.message = message;
    
            return this;
        }
    
        public RestReturn error(Integer code, Object data, Object message) {
            this.success = false;
            this.code = code;
            this.data = data;
            this.message = message;
    
            return this;
        }
    
        public boolean isRestReturnJson(String data) {
            //临时实现先判定下字符串的格式和字段
            try {
                /**
                 * ObjectMapper支持从byte[]、File、InputStream、字符串等数据的JSON反序列化。
                 */
                ObjectMapper mapper = new ObjectMapper();
                RestReturn dataRestReturn = mapper.readValue(data, RestReturn.class);
                //比较两个类的字段,如果一致返回为真,不一致返回为假
                return true;
            } catch (Exception e) {
                return false;
            }
        }
    }
    

    返回数据拦截器的封装(MemberController.java)

    package com.springboot.action.saas.common.controller;
    
    import org.springframework.core.MethodParameter;
    import org.springframework.core.io.Resource;
    import org.springframework.http.MediaType;
    import org.springframework.http.converter.HttpMessageConverter;
    import org.springframework.http.server.ServerHttpRequest;
    import org.springframework.http.server.ServerHttpResponse;
    import org.springframework.web.bind.annotation.ControllerAdvice;
    import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice;
    
    @ControllerAdvice
    public class RestReturnWrapper implements ResponseBodyAdvice<Object> {
        /**
         * 判定哪些请求要执行beforeBodyWrite,返回true执行,返回false不执行
         * */
        @Override
        public boolean supports(MethodParameter methodParameter, Class<? extends HttpMessageConverter<?>> converterType) {
            //获取当前处理请求的controller的方法
            //String methodName = methodParameter.getMethod().getName();
            // 拦/不拦截处理返回值的方法,如登录
            //String method = "login";
            //这里可以加入很多判定,如果在白名单的List里面,是否拦截
            return true;
        }
    
    
        /**
         * 返回前对body,request,response等请求做处理
         *
         * @param body
         * @param methodParameter
         * @param mediaType
         * @param httpMessageConverter
         * @param serverHttpRequest
         * @param serverHttpResponse
         *
         * @return
         * */
        @Override
        public Object beforeBodyWrite(Object body,
                        MethodParameter methodParameter,
                        MediaType mediaType,
                        Class<? extends HttpMessageConverter<?>> httpMessageConverter,
                        ServerHttpRequest serverHttpRequest,
                        ServerHttpResponse serverHttpResponse) {
            //具体返回值处理
            //情况1 如果返回的body为null
            if(body == null){
                if (mediaType == MediaType.APPLICATION_JSON) {
                    //返回是json个格式类型,无body内容
                    RestReturn restReturn = new RestReturn();
                    return restReturn.success("", "");
                } else {
                    return null;
                }
            } else {
                //情况2 文件上传下载,不需要改动,直接返回
                if (body instanceof Resource) {
                    return body;
                } else if (body instanceof String) {
                    // 返回的是 String,
                    RestReturn restReturn = new RestReturn();
                    try {
                        if (restReturn.isRestReturnJson((String) body)) {
                            // 情况3 已经是RestReturn格式的json 字符串不做统一格式封装
                            return body;
                        } else {
                            //情况4 普通的返回,需要统一格式,把数据赋值回去即可。
                            return restReturn.success(body, "");
                        }
                    } catch (Exception e) {
                        // 因为 API返回值为String,理论上不会走到这个分支。
                        return restReturn.error(10000, body, e.getMessage());
                    }
                } else {
                    //返回的是非字符串格式,实际上很多时候用都是是在应用程返回的对象居多
                    if(body instanceof RestReturn){
                        //情况5 如果已经封装成RestReturn,直接return
                        return body;
                    }else{
                        //情况6 非字符串非统一格式的返回,需要统一格式
                        RestReturn restReturn = new RestReturn();
                        return restReturn.success(body, "");
                    }
                }
            }
        }
    }
    

    打tag 1.0.3版本,提交代码。

    git tag -a v1.0.3 -m "正确/出错/异常返回格式统一处理"

    git push origin v1.0.3

    github地址:https://github.com/horacepei/springsaas

    相关文章

      网友评论

        本文标题:Spring全家桶实践-REST风格接口实现

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