springboot(四):RESTful API的设计

RESTful API介绍

一种软件架构风格、设计风格,而不是标准,只是提供了一组设计原则和约束条件。它主要用于客户端和服务器交互类的软件。基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机制。

上文应用于百度百科:https://baike.baidu.com/item/RESTful/4406165?fr=aladdin

RESTful API的示例

序号 restful 请求方式 表示
/users GET 查询所有user对象
/user/{id} GET 根据id查询单个user对象
/user POST 保存user对象
/user PUT 更新user对象
/user/name PATCH 更新user对象(部分)
/user/{id} DELETE 根据id删除单个user对象

RESTful API在springboot中的应用

(一)关于注解的介绍

大家在spring项目中会使用@RequestMapping注解,通过它来指定控制器可以处理哪些URL请求。若不指定请求方式,默认GET、POST都支持,根据前端方式自动适应。所以,大家为了区分,有的时候会这样使用注解:

@RequestMapping(value = “/xxx”,method = RequestMethod.GET)

当然,上面的注解的使用方式也是正确的,但是在springboot中有更加方便的写法。接下来,就给大家一一列出。

序号 spring中的注解 springboot中的注解
@RequestMapping(value = "/url",method = RequestMethod.GET) @GetMapping(value="/url")
@RequestMapping(value = "/url",method = RequestMethod.POST) @PostMapping(value="/url")
@RequestMapping(value = "/url",method = RequestMethod.PUT) @PutMapping(value="/url")
@RequestMapping(value = "/url",method = RequestMethod.PATCH) @PatchMapping(value="/url")
@RequestMapping(value = "/url",method = RequestMethod.DELETE) @DeleteMapping(value="/url")
以上就是我们常用http请求方式了,其实还有几种: HEAD:类似于get请求,只不过返回的响应中没有具体的内容,用于获取报头。 CONNECT:HTTP/1.1协议中预留给能够将连接改为管道方式的代理服务器。 OPTIONS:允许客户端查看服务器的性能。 TRACE:回显服务器收到的请求,主要用于测试或诊断。

因为本人基本上没有用到上面这几个请求方式,所以,感兴趣的朋友,可以根据相关资料,自行研究。

(一)RESTful API实践

为了大家能直观的看数据的变化,所以这里我使用了spring-jdbc来操作数据库。至于连接数据库的这部分知识,后面也会慢慢讲到。

  1. pom.xml中导入相关的jar依赖
		<!-- jdbc -->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-jdbc</artifactId>
        </dependency>
        <!-- mysql -->
        <dependency>
            <groupId>mysql</groupId>
            <artifactId>mysql-connector-java</artifactId>
        </dependency>
  1. 修改配置文件

修改配置文件,前面章节我们讲了配置文件的使用,这里我就不详细说了,不清楚的朋友,可以参考我之前的文章: https://blog.csdn.net/qq_32101993/article/details/84628396

spring:
  datasource:
    url: jdbc:mysql://localhost:3306/springboot_1?serverTimezone=UTC&useUnicode=true&characterEncoding=utf-8&useSSL=true
    username: root
    password: root
    driver-class-name: com.mysql.cj.jdbc.Driver
  1. 创建表
CREATE TABLE `book` (
  `id` bigint(11) NOT NULL AUTO_INCREMENT COMMENT '主键id',
  `name` varchar(255) DEFAULT NULL COMMENT '书名',
  `author` varchar(255) DEFAULT NULL COMMENT '作者',
  PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=2 DEFAULT CHARSET=utf8;
  1. 创建Book对象
package com.project.parent.springbootrestful.pojo;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName: Book
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2018/12/18 11:22
 * @Version 1.0
 **/
@Data
public class Book implements Serializable {

    private Long id;
    private String name;
    private String author;
	
	//这里使用了lombok来完成自动生成setter、getter方法,需要导入相关的jar依赖。
	//然后使用@data注解,就能自动生成了。	    
}

需要使用lombok的朋友,可以在pom.xml中加入以下依赖:

        <!-- lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.4</version>
            <scope>compile</scope>
        </dependency>
  1. 返回统一接口的实体类封装

上一篇文章我们讲了,怎么使用springboot返回对象、集合、自定义对象。但是为了规范化,这里我们返回统一的对象。所以需要书写下面几个类。

ResultCode:定义了成功,失败的标志,推荐使用枚举,我这里就简化了。

public class ResultCode {

    public static final Integer SUCCESS = 200;

    public static final Integer FAIL = -1;
}

Result:这就是我们需要返回的对象

package com.project.parent.springbootrestful.pojo;

import lombok.Data;

import java.io.Serializable;

/**
 * @ClassName: Result
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2018/12/18 10:34
 * @Version 1.0
 **/
@Data
public class Result<T> implements Serializable {

    private static final long serialVersionUID = 874200365941306385L;

    private Integer code;
    private String msg;
    private T data;
	
	//同样使用了lombok
}

ResultBean:操作Result的对象

public class ResultBean {
    public static Result success() {
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        return result;
    }

    public static Result success(Object data) {
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setData(data);
        return result;
    }

    public static Result success(Object data, String msg) {
        Result result = new Result();
        result.setCode(ResultCode.SUCCESS);
        result.setData(data);
        result.setMsg(msg);
        return result;
    }

    public static Result failure() {
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        return result;
    }

    public static Result failure(String msg) {
        Result result = new Result();
        result.setCode(ResultCode.FAIL);
        result.setMsg(msg);
        return result;
    }
  1. service层的实现

因为博主偷懒,这里就没有dao层了,所有操作都在service层完成,这是一个不好的习惯,但是由于时间原因,所以希望大家理解。
BookService:接口

public interface BookService {

    /**
     * 增加
     * @param book
     * @return
     */
    int insert(Book book);

    /**
     * 根据删除
     * @param id
     * @return
     */
    int delete(Long id);

    /**
     * 更新全部
     * @param book
     * @return
     */
    int update(Book book);

    /**
     * 更新部分
     * @param book
     * @return
     */
    int updateName(Book book);

    /**
     * 根据id查询
     * @param id
     * @return
     */
    Book findOne(Long id);

    /**
     * 查询所有
     * @return
     */
    List<Book> findAll();
}

BookServiceImpl :接口的实现类

@Service
public class BookServiceImpl implements BookService {

	//因为使用的是spring-jdbc,所以这里使用JdbcTemplate来完成增删改查操作
    @Autowired
    private JdbcTemplate jdbcTemplate;

    @Override
    public int insert(Book book) {
        String sql = "INSERT INTO book(name,author) VALUES(?,?)";
        Object[] params = {book.getName(), book.getAuthor()};
        return jdbcTemplate.update(sql, params);
    }

    @Override
    public int delete(Long id) {
        String sql = "DELETE FROM book WHERE id=?";
        return jdbcTemplate.update(sql, id);
    }

    @Override
    public int update(Book book) {
        String sql = "UPDATE book SET name=?,author=? WHERE id=?";
        Object[] params = {book.getName(), book.getAuthor(), book.getId()};
        return jdbcTemplate.update(sql, params);
    }

    @Override
    public int updateName(Book book) {
        String sql = "UPDATE book SET name=? WHERE id=?";
        Object[] params = {book.getName(), book.getId()};
        return jdbcTemplate.update(sql, params);
    }

    @Override
    public Book findOne(Long id) {
        String sql = "SELECT * FROM book WHERE id=?";
        return jdbcTemplate.queryForObject(sql, new Object[]{id}, new BeanPropertyRowMapper<Book>(Book.class));
    }

    @Override
    public List<Book> findAll() {
        String sql = "SELECT * FROM book";
        //返回list集合,需要我们自己实现RowMapper接口,从结果集里得到数据
        return jdbcTemplate.query(sql, new BookMapper());
    }
}

class BookMapper implements RowMapper<Book> {

    @Override
    public Book mapRow(ResultSet rs, int i) throws SQLException {
        //从结果集里把数据得到
        long id = rs.getLong("id");
        String name = rs.getString("name");
        String author = rs.getString("author");
        Book book = new Book();
        book.setId(id);
        book.setName(name);
        book.setAuthor(author);
        return book;
    }
}
  1. controller层的实现
package com.project.parent.springbootrestful.controller;

import com.project.parent.springbootrestful.pojo.Book;
import com.project.parent.springbootrestful.pojo.Result;
import com.project.parent.springbootrestful.pojo.ResultBean;
import com.project.parent.springbootrestful.service.BookService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;

/**
 * @ClassName: BookController
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2018/12/18 11:45
 * @Version 1.0
 **/
@RestController
@RequestMapping("/")
public class BookController {

    @Autowired
    private BookService bookService;
    
    @PostMapping(value = "book")
    public Result insert(Book book){
        return ResultBean.success(bookService.insert(book),"插入成功");
    }

    @GetMapping(value = "book/{id}")
    public Result findOne(@PathVariable Long id){
        return ResultBean.success(bookService.findOne(id),"查询成功");
    }

    @GetMapping(value = "books")
    public Result findAll(){
        return ResultBean.success(bookService.findAll(),"查询成功");
    }

    @DeleteMapping(value = "book/{id}")
    public Result delete(@PathVariable Long id){
        return ResultBean.success(bookService.delete(id),"删除成功");
    }

    @PutMapping(value = "book")
    public Result update(Book book){
        return ResultBean.success(bookService.update(book),"更新成功");
    }

    @PatchMapping(value = "book")
    public Result patch(Book book){
        return ResultBean.success(bookService.updateName(book),"更新成功(部分)");
    }
	//这里使用了所有我们列出来的请求:get、post、delete、put、patch	
}
  1. 测试环节

本来可以直接使用Postman来测试,但是为了直观的反应各个请求。所以下面我会使用MockMvc来进行测试,MockMvc也能发起各种http请求,测试很方便。
当然,想用测试工具测试的朋友,也可以忽略底下的篇幅。

RestfulTest :测试类

package com.project.parent.springbootrestful;

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.request.MockMvcRequestBuilders;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.web.context.WebApplicationContext;

/**
 * @ClassName: ResultfulTest
 * @Author: 清风一阵吹我心
 * @Description: TODO
 * @Date: 2018/12/18 13:11
 * @Version 1.0
 **/
@RunWith(SpringRunner.class)
@SpringBootTest
//测试环境使用,用来表示测试环境使用的ApplicationContext将是WebApplicationContext类型的
@WebAppConfiguration
public class RestfulTest {

    @Autowired
    private WebApplicationContext webApplicationContext;

    private MockMvc mockMvc;

	//使用日志来记录
    private static final Logger LOGGER = LoggerFactory.getLogger(RestfulTest.class);

	//定义出所有请求的路径
    private String insertUrl = "/book";
    private String oneUrl = "/book/{id}";
    private String allUrl = "/books";
    private String updateUrl ="/book";
    private String updateNameUrl = "/book";
    private String deleteUrl = "/book/{id}";
    
	//@Before初始化方法   对于每一个测试方法都要执行一次
    @Before
    public void setUp() {
        //指定WebApplicationContext,将会从该上下文获取相应的控制器并得到相应的MockMvc;
        mockMvc = MockMvcBuilders.webAppContextSetup(this.webApplicationContext).build();
    }


    /**
     * 1、mockMvc.perform执行一个请求。
     * 2、MockMvcRequestBuilders.get("XXX")构造一个请求
     * 3、ResultActions.param添加请求传值
     * 4、ResultActions.accept(MediaType.TEXT_HTML_VALUE))设置返回类型
     * 5、ResultActions.andExpect添加执行完成后的断言
     * 6、ResultActions.andDo添加一个结果处理器,表示要对结果做点什么事情
     * 比如此处使用MockMvcResultHandlers.print()输出整个响应结果信息
     * 7、ResultActions.andReturn表示执行完成后返回相应的结果
     *
     * @throws Exception
     */
    @Test
    public void insert() throws Exception {
        final MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
        params.add("name", "魔道祖师");
        params.add("author", "墨香铜臭");
        String content = mockMvc.perform(MockMvcRequestBuilders.post(insertUrl).params(params))
                .andReturn().getResponse().getContentAsString();
        LOGGER.info("content:{}", content);
    }

    @Test
    public void findOne() throws Exception {
        String book = mockMvc.perform(MockMvcRequestBuilders.get(oneUrl, 1))
                .andReturn().getResponse().getContentAsString();
        LOGGER.info("book:{}",book);
    }

    @Test
    public void findAll() throws Exception{
        String list = mockMvc.perform(MockMvcRequestBuilders.get(allUrl))
                .andReturn().getResponse().getContentAsString();
        LOGGER.info("list:{}",list);
    }

    @Test
    public void update() throws Exception {
        final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
        params.add("id","1");
        params.add("name","武动乾坤");
        params.add("author","天蚕土豆");
        String content = mockMvc.perform(MockMvcRequestBuilders.put(updateUrl).params(params))
                .andReturn().getResponse().getContentAsString();

        LOGGER.info("content:{}", content);
    }

    @Test
    public void patch() throws Exception {
        final MultiValueMap<String,String> params = new LinkedMultiValueMap<>();
        params.add("id","1");
        params.add("name","星辰变");
        String content = mockMvc.perform(MockMvcRequestBuilders.patch(updateNameUrl).params(params))
                .andReturn().getResponse().getContentAsString();
        LOGGER.info("content:{}", content);
    }

    @Test
    public void delete() throws Exception {
        String content = mockMvc.perform(MockMvcRequestBuilders.delete(deleteUrl, "1"))
                .andReturn().getResponse().getContentAsString();
        LOGGER.info("content:{}", content);
    }
}

使用日志来记录测试结果,给大家放几张图:
插入
根据id查询
查询所有

基本上整个RESTful API的设计,到这里就讲完了,关于MockMvc的知识,我们后面再讲解。如有不明白的朋友,可以给我留言,我一定尽自己所能,给大家解决问题。

你为什么那么努力?
因为我想要的东西很贵……
我想去的地方很远……
我喜欢的人很优秀……

猜你喜欢

转载自blog.csdn.net/qq_32101993/article/details/85058253