近年来移动互联网的发展,前端设备层出不穷(手机、平板、桌面电脑、其他专用设备…),因此,必须有一种统一的机制,方便不同的前端设备与后端进行通信,于是 RESTful 火了,它可以通过一套统一的接口为 Web,iOS和Android提供服务。 
1. RESTful API 规范
协议
域名
应该尽量将API部署在专用域名之下。
https://api.example.com
如果确定API很简单,不会有进一步扩展,可以考虑放在主域名下。
https://example.org/api/
版本
将 API 的版本号放入 URL。https://api.example.com/v1/
路径
在RESTful架构中,每个网址代表一种资源(resource),所以网址中不能有动词,只能有名词,API中的名词建议使用复数。
举例来说,有一个API提供动物园(zoo)的信息,还包括各种动物和雇员的信息,则它的路径应该设计成下面这样。
- https://api.example.com/v1/zoos
- https://api.example.com/v1/animals
- https://api.example.com/v1/employees - HTTP动词- 对于资源的具体操作类型,由HTTP动词表示。 
 常用的 HTTP 动词有下面五个(括号里是对应的SQL命令)。
- GET(SELECT):从服务器取出资源(一项或多项)。 
- POST(CREATE):在服务器新建一个资源。
- PUT(UPDATE):在服务器更新资源。
- DELETE(DELETE):从服务器删除资源。
下面是一些例子。
- GET /v1/zoos:列出所有动物园
- POST /v1/zoos:新建一个动物园
- GET /v1/zoos/id:获取某个指定动物园的信息
- PUT /v1/zoos/id:更新某个指定动物园的信息(提供该动物园的全部信息)
- DELETE /v1/zoos/id:删除某个动物园
- GET /v1/zoos/id/animals:列出某个指定动物园的所有动物
- DELETE /v1/zoos/id/animals/id:删除某个指定动物园的指定动物 - 过滤信息- 如果记录数量很多,服务器不可能都将它们返回给用户。API应该提供参数,过滤返回结果。 
 下面是一些常见的参数。
- ?limit=10:指定返回记录的数量 
- ?offset=10:指定返回记录的开始位置。
- ?page=2&per_page=100:指定第几页,以及每页的记录数。
- ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
- ?animal_type_id=1:指定筛选条件
参数的设计允许存在冗余,即允许API路径和URL参数偶尔有重复。
比如,GET /v1/zoo/id/animals 与 GET /animals?zoo_id=id的含义是相同的。
状态码
服务器向用户返回的状态码和提示信息,常见的有以下:
- 200 OK - [GET]:服务器成功返回用户请求的数据。
- 401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。
- 403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。
- 404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作。
- 500 INTERNAL SERVER ERROR - [*]:服务器发生错误。
错误处理如果状态码是4xx,就应该向用户返回出错信息。一般来说,返回的信息中将error作为键名,出错信息作为键值即可。
 { error: “Invalid API key” }2. 公共接口规范公共请求头| 名称 | 必填 | 说明 | | —- | —- | —- | | Authorization | 否 | 用于验证请求合法性的认证信息。
 使用场景:非匿名请求 |
公共响应头
| 头部名称 | 必填 | 说明 | 
|---|---|---|
| Content-Type | 是 | 正常情况下该值将被设为 application/json,表示返回 JSON 格式的文本信息。 | 
成功响应
成功响应格式
HTTP/1.1 200 OK
Content-Type: application/json
{
"code": 200,
"msg": "",
"data":
}
错误响应
当您请求出现错误时,响应头部信息包括:
- Content-Type: application/json
- 一个合适的 3xx,4xx,或者 5xx 的 HTTP 状态码
错误响应格式:
HTTP/1.1 状态码 信息
Content-Type: application/json
{
"code": <httpCode int>,
"error": "<errMsg string>"
}
响应状态码
| HTTP状态码 | 说明 | 
|---|---|
| 200 | 请求成功 | 
| 401 | 认证授权失败 | 
| 403 | 权限不足,拒绝访问。 | 
| 404 | 资源不存在 包括空间资源不存在;镜像源资源不存在。 | 
| 405 | 请求方式错误 主要指非预期的请求方式。 | 
| 自定义的其它错误码…. | 
设计一组 RESTful API 完成用户信息的维护。
| 请求类型 | URL | 说明 | 
|---|---|---|
| GET | /api/sys/users | 查询用户列表 | 
| POST | /api/sys/users | 创建用户 | 
| GET | /api/sys/users/{id} | 根据 id 查询用户 | 
| PUT | /api/sys/users/{id} | 根据 id 更新用户 | 
| DELETE | /api/sys/users/{id} | 根据 id 删除用户 | 
3. 查询用户接口
描述
请求
请求语法
GET /api/sys/users HTTP/1.1
Content-Type: application/x-www-form-urlencoded
Authorization: <AccessToken>
请求参数
| 参数名称 | 必填 | 说明 | 
|---|---|---|
| name | 否 | 根据用户名模糊查询用户 | 
| 否 | 根据用户邮箱模糊查询用户 | |
| pageNum | 否 | 当前页 | 
| pageSize | 否 | 一页显示多少条 | 
请求头
请求内容
响应
响应语法
HTTP/1.1 200 OK
Content-Type: application/json
{
  "code": 200, 
  "msg": "查询用户列表成功",
  "data": {}
}
响应头
响应内容
| 名称 | 必填 | 说明 | 
|---|---|---|
| id | 是 | 用户id | 
| name | 是 | 用户名 | 
| age | 否 | 年龄 | 
| 否 | 邮箱 | 
响应状态码
该操作的实现不会返回特殊错误。有关错误和错误代码列表的一般信息。
示例
请求示例
GET /api/sys/users?name=jack&pageSize=10&pageNum=1 HTTP/1.1
Authorization: j853F3bLkWl59I5BOkWm6q1Z1mZClpr9Z9CLfDE0
响应示例
{
  "msg": "请求成功",
  "current": 1,
  "total": 7,
  "code": 200,
  "size": 10,
  "data": [
    {
      "id": 1,
      "name": "jack",
      "age": 18,
      "email": "jack@126.com"
    },
    ....
  ]
}
4. 新增用户接口
描述
请求
请求语法
POST /api/sys/users HTTP/1.1
Content-Type: application/x-www-form-urlencoded 
Authorization: <AccessToken>
请求参数
| 参数名称 | 必填 | 说明 | 
|---|---|---|
| name | 是 | 用户名 | 
| 是 | 邮箱 | |
| age | 是 | 年龄 | 
请求头
请求内容
响应
响应语法
HTTP/1.1 200 OK
Content-Type: application/json
{
  "code": 200, 
  "msg": "新增用户成功"
}
响应头
响应内容
响应状态码
该操作的实现不会返回特殊错误。有关错误和错误代码列表的一般信息。
示例
请求示例
POST /api/sys/users HTTP/1.1
Authorization: j853F3bLkWl59I5BOkWm6q1Z1mZClpr9Z9CLfDE0
响应示例
HTTP/1.1 200 OK
Content-Type: application/json
{
  "code": 200, 
  "msg": "新增用户成功",
  "data": {}
}
5. 实现接口
引入依赖
<!-- springmvc 、servlet 、tomcat -->
<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
公共类库
响应请求对象
package com.imcode.common.vo;
import com.baomidou.mybatisplus.core.metadata.IPage;
import org.springframework.http.HttpStatus;
import java.util.concurrent.ConcurrentHashMap;
/**
 * 响应对象
 */
public class Result extends ConcurrentHashMap<String, Object> {
    public Result() {
        this.put("code", HttpStatus.OK.value());
        this.put("msg", "请求成功");
    }
    public static Result ok() {
        return new Result();
    }
    public static Result ok(String msg) {
        return Result.ok().put("msg", msg);
    }
    public static Result ok(Object data) {
        return Result.ok().put("data", data);
    }
    public static Result ok(IPage<?> page) {
        return Result.ok()
                // 当前页数据
                .put("data", page.getRecords())
                // 当前页面码
                .put("current", page.getCurrent())
                // 一页显示多少条
                .put("size", page.getSize())
                // 满足查询条件的总记录数
                .put("total", page.getTotal());
    }
    public static Result ok(String msg, Object data) {
        return Result.ok(msg).put("data", data);
    }
    public static Result error(HttpStatus status, String error) {
        return Result.ok()
                .put("code", status)
                .put("error", error)
                .put("msg", "");
    }
    @Override
    public Result put(String key, Object value) {
        super.put(key, value);
        return this;
    }
}
分页对象
package com.imcode.common.vo;
import lombok.Data;
@Data
public class PageVO {
    // 当前页码
    private Integer current = 1;
    // 每页显示多少条
    private Integer size = 10;
}
用户接口
查询条件对象

package com.imcode.sys.vo;
import com.imcode.common.vo.PageVO;
import lombok.Data;
@Data
public class UserQuery extends PageVO {
    private String name;
    private String email;
}
实现查询接口

package com.imcode.sys.controller;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.plugins.pagination.Page;
import com.imcode.common.vo.Result;
import com.imcode.sys.entity.User;
import com.imcode.sys.service.UserService;
import com.imcode.sys.vo.UserQuery;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
/**
 * <p>
 * 前端控制器
 * </p>
 *
 * @author jack
 * @since 2022-07-02
 */
@RestController
@RequestMapping("/api/sys")
public class UserController {
    @Autowired
    private UserService userService;
    @GetMapping("/users")
    public Result list(UserQuery param) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
         wrapper.like(StringUtils.hasLength(query.getName()), User::getName, query.getName());
        wrapper.like(StringUtils.hasLength(query.getEmail()), User::getEmail, query.getEmail());
        IPage<User> page = 
            userService.page(new Page<>(param.getCurrent(), param.getSize()), wrapper);
        return Result.ok(page);
    }
    @PostMapping("/users")
    public Result save(User user) {
        userService.save(user);
        return Result.ok("新增用户成功");
    }
}
没有前端,如何测试?API文档和测试工具
6. Swagger 构建 API 文档
Swagger 介绍
- 轻松构建 RESTful API 文档。
- 文档和代码融合,修改代码逻辑的同时方便更新文档说明。
- 提供 API 接口调试功能。
官方界面有点丑,移步knife4j:https://doc.xiaominfo.com/ 
引入依赖
<dependency>
    <groupId>com.github.xiaoymin</groupId>
    <artifactId>knife4j-spring-boot-starter</artifactId>
    <version>2.0.9</version>
</dependency>
配置类

package com.imcode.config;
import io.swagger.annotations.ApiOperation;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import springfox.documentation.builders.ApiInfoBuilder;
import springfox.documentation.builders.PathSelectors;
import springfox.documentation.builders.RequestHandlerSelectors;
import springfox.documentation.spi.DocumentationType;
import springfox.documentation.spring.web.plugins.Docket;
import springfox.documentation.swagger2.annotations.EnableSwagger2WebMvc;
@Configuration
@EnableSwagger2WebMvc
public class Knife4jConfig {
    @Bean
    public Docket defaultApi() {
        Docket docket = new Docket(DocumentationType.SWAGGER_2)
                .apiInfo(new ApiInfoBuilder()
                        .title("XX系统API接口")
                        .description("在线API文档")
                        .version("1.0")
                        .build())
                .select()
                //.apis(RequestHandlerSelectors.basePackage("com.imcode"))
                .apis(RequestHandlerSelectors
                        .withMethodAnnotation(ApiOperation.class))
                .paths(PathSelectors.any())
                .build();
        return docket;
    }
}
重启应用,访问:http://localhost:8080/doc.html
如果报错,添加如下配置:
spring:
  mvc:
    pathmatch:
    matching-strategy: ant_path_matcher
添加文档内容
控制器
@Api(tags = "用户模块")
@RestController
@RequestMapping("/api/sys")
public class UserController {
    @Autowired
    private UserService userService;
    @ApiOperation(value = "分页查询")
    @GetMapping("/users")
    public Result list(UserQuery param) {
        LambdaQueryWrapper<User> wrapper = new LambdaQueryWrapper<>();
        wrapper.like(param.getName() != null, User::getName, param.getName());
        wrapper.like(param.getEmail() != null, User::getEmail, param.getEmail());
        IPage<User> page = userService.page(new Page<>(param.getCurrent(), param.getSize()), wrapper);
        return Result.ok(page);
    }
    @ApiOperation(value = "新增用户")
    @PostMapping("/users")
    public Result save(User user) {
        userService.save(user);
        return Result.ok("新增用户成功");
    }
}
实体类

@Getter
@Setter
@TableName("t_user")
@ApiModel(description="用户信息")
public class User implements Serializable {
    private static final long serialVersionUID = 1L;
    @TableId(value = "id", type = IdType.AUTO)
    @ApiModelProperty("主键")
    private Integer id;
    @ApiModelProperty("姓名")
    private String name;
    @ApiModelProperty("年龄")
    private Integer age;
    @ApiModelProperty("邮箱")
    private String email;
}
VO类

@Data
@ApiModel(description="用户查询条件")
public class UserQuery extends PageVO {
    @ApiModelProperty("姓名")
    private String name;
    @ApiModelProperty("邮箱")
    private String email;
}
@Data
@ApiModel(description="分页信息")
public class PageVO {
    // 当前页码
    @ApiModelProperty("页码")
    private Integer current = 1;
    // 一页显示多少条
    @ApiModelProperty("一页显示多少条")
    private Integer size = 10;
}
常用注解
- @Api:修饰整个类,描述Controller的作用;
- @ApiOperation:描述一个类的一个方法,或者说一个接口;
- @ApiParam:单个参数描述;用在方法参数上
- @ApiModel:用对象来接收参数;
- @ApiProperty:用对象接收参数时,描述对象的一个字段;
- @ApiIgnore:使用该注解忽略这个API;
- @ApiImplicitParam:一个请求参数;用在方法上
- @ApiImplicitParams:多个请求参数。用在方法上
- 
参考示例@Api(value = "MemberApi", description = "用户登录、注册、账户合法性校验") @RestController @RequestMapping("/api/member") public class MemberApi { /** * 会员注册 * @param * @return */ @ApiOperation(value = "会员注册", notes = "网站会员注册") @ApiImplicitParam(name = "member", value = "会员详细信息", dataType = "Member") @PostMapping("/register") public R register(@RequestBody Member member) { try { return R.success(); } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } } /** * 登录 * @param * @return */ @ApiOperation(value = "会员登录", notes = "网站会员登录") @ApiImplicitParams({ @ApiImplicitParam(name = "account", value = "会员帐号", required = true, paramType = "query", dataType = "String"), @ApiImplicitParam(name = "password", value = "会员密码", required = true, paramType = "query", dataType = "String") }) @PostMapping("/login") public R login(String account, String password) { try { return R.success(); } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } } /** * 检查帐号是否存在 * * @param * @return */ @ApiOperation(value = "检查帐号是否存在", notes = "根检查帐号是否存在") @GetMapping("/checkaccount/{account}") public R checkAccount(@ApiParam(name = "account",required = true,value = "用户帐号") @PathVariable String account) { try { return R.success(); } catch (Exception e) { e.printStackTrace(); return R.error(e.getMessage()); } } }7. ApiPost 
 官方文档地址:https://wiki.apipost.cn/ 

 
 
 
                         
                                

