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

背景

什么是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

配置

Spring全家桶实践-REST风格接口实现_第1张图片
image

实操

目录结构

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

Spring全家桶实践-REST风格接口实现_第2张图片
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 findAll() {
        System.out.println("MemberDao.findAll");
        List list = new ArrayList();
        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 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 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 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"
     * 数据通过
表单者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 {
    /**
     * 判定哪些请求要执行beforeBodyWrite,返回true执行,返回false不执行
     * */
    @Override
    public boolean supports(MethodParameter methodParameter, Class> 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> 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风格接口实现)