RESTful架构风格规范

REST与RESTful介绍

  • REST翻译为表现层状态转移(Representational State Transfer, REST),是一种软件架构风格,是Roy Thomas Fielding在他2000年的博士论文中提出的,该结构风格提供一些指导原则和最优方法来帮助我创建可扩展的web服务
  • REST是应用于分布式系统组件设计的一套协调的约束,它可以带来更高性能和更可维护的体系结构
  • RESTful风格的API是遵循REST风格构建的API,它可以通过一套统一的接口为Web,IOS和Android提供服服务
  • RESTful架构风格的本质就是设计出结构清晰、符合标准的URI,用URI定位资源、用HTTP动词(GET、POST、PUT、DELETE)描述操作,以便于理解和方便扩展

URI命名规范

REST风格对于资源的定义,即URL的定义,是最重要的,根据查阅的相关资料,总结以下规则作为基本遵循

1.URL中不能有动词

  • Restful架构中,每个网址代表的是一种资源,所以网址中没有有动词只有名词,而且所用的名词往往与数据库的表格名对应

  • 一般来说,数据库中的表都是同种记录的集合,所以API中的名词也应该使用复数

    例如:https://api.example.com/v1/employees 表示所有员工

2.URL结尾不应该包含斜杠"/"

  • 作为URL路径中处理中最重要的规则之一,正斜杠("/")不会增加语义值,可能导致混淆

  • REST API不允许一个尾部的斜杠,不应该将它们包含在提供给客户端的链接的结尾处

  • URI中的每个字符都会计入资源的唯一身份的识别中,两个不同的URI映射到两个不同的资源

  • REST API必须生成和传递精确的URI,不能容忍任何的客户端尝试不精确的资源定位

3.正斜杠分隔符"/"用来指示层级关系

URI路径中的正斜杠"/"字符用于指示资源之间的层次关系

例如:

https://api.example.com/v1/employees/101 表示工号为101的员工

https://api.example.com/v1/employees/101/departments表示101号员工的部门

4.API的版本号

https://api.example.com/v1/employees

URI请求中可使用版本号以区分版本,取决于自己开发团队的习惯和业务的需要,不是强制的

有的做法是将版本号放在HTTP头信息中,但不如放入URL方便和直观

5.URI路径一般要求

  • URI路径区分大小写,一般选用小写字母
  • URI多个字符串下加下划线(_)字符可能可能被这个下划线部分地遮蔽,因此常用连字符(-
  • 对于URI后面的携带传递的字符串参数部分,字符串参数名称通常与后端接口参数一致即可

6.资源过滤

在获取资源的时候,有可能需要获取某些过滤后的资源,例如指定前10行数据:

https://api.example.com/employees?page=1&pageSize=10

使用HTTP状态码

有很多服务器将返回状态码一直设为200,然后在返回body里面自定义一些状态码来表示服务器返回结果的状态码。RESTful Web API是直接使用的HTTP协议,所以它的状态码也要尽量使用HTTP协议的状态码

服务器向用户返回的状态码和提示信息,常见的如下:

200 OK - [GET]:服务器成功返回用户请求的数据,该操作是幂等的(Idempotent)。

201 CREATED - [POST/PUT/PATCH]:用户新建或修改数据成功。

202 Accepted - [*]:表示一个请求已经进入后台排队(异步任务)

204 NO CONTENT - [DELETE]:用户删除数据成功。

400 INVALID REQUEST - [POST/PUT/PATCH]:用户发出的请求有错误,服务器没有进行新建或修改数据的操作,该操作是幂等的。

401 Unauthorized - [*]:表示用户没有权限(令牌、用户名、密码错误)。

403 Forbidden - [*] 表示用户得到授权(与401错误相对),但是访问是被禁止的。

404 NOT FOUND - [*]:用户发出的请求针对的是不存在的记录,服务器没有进行操作,该操作是幂等的。

406 Not Acceptable - [GET]:用户请求的格式不可得(比如用户请求JSON格式,但是只有XML格式)。

410 Gone -[GET]:用户请求的资源被永久删除,且不会再得到的。

422 Unprocesable entity - [POST/PUT/PATCH] 当创建一个对象时,发生一个验证错误。

500 INTERNAL SERVER ERROR - [*]:服务器发生错误,用户将无法判断发出的请求是否成功。

使用RESTful风格的HTTP动词

对于REST API资源的操作类型,由HTTP动词表示,常用的动词是GET,POST,PUT,DELETE,包括如下内容:

GET: 获取资源
POST: 新建资源
PUT:在服务器更新资源(客户端提供改变后的所有资源)
PATCH: 在服务器更新资源(客户端提供改变的属性,一般常用PUT)
DELETE:删除资源
HEAD:获取资源的元数据
OPTIONS:获取信息,关于资源的哪些属性是客户端可以改变的

RESTful风格的URI设计典例

URI体现层次结构,但是没有任何硬性规定,只能确保这种结构对使用者有意义,与软件开发过程中的所有内容一样,命名对于成功至关重要,以下是一些典型例子

对于客户资源建议的URI:
POST http://www.example.com/customers 插入(创建)新客户
GET http://www.example.com/customers/33245 读取客户ID为33245的客户
PUT http://www.example.com/customers/33245 用于更新ID为33245的客户
DELETE http://www.example.com/customers/33245 用于更新ID为33245的客户

对于产品的建议的URI:
POST http://www.example.com/products 创建新产品
GET | PUT | DELETE http://www.example.com/products/66432 分别用于读取,更新和删除产品66432

对于存在客户与产品关系的URI:
POST http://www.example.com/orders 创建订单,但是可以说它不在客户范围内,为了URI更直观,可以这样设计:
POST http://www.example.com/customers/33245/orders 这样更清晰,表示正在为客户ID#33245创建订单
GET http://www.example.com/customers/33245/orders 读取客户#33245已创建或拥有的订单的列表,
由于该网址在集合上运行,因此我们可能选择不支持该网址的DELETE或PUT

提现层次结构URI:
POST http://www.example.com/customers/33245/orders/8769/lineitems 给客户33245添加订单8769的订单项
GET http://www.example.com/customers/33245/orders/8769/lineitems 返回客户该订单的所有订单项
POST www.example.com/orders/8769/lineitems 单项仅在客户环境中没有有意义,或者在客户环境之外也无意义,就可以这样设计
GET http://www.example.com/orders/8769 若给定资源可能有多个URI,则只需提供一个URI支持按编号检索订单,而不必知道客户编号

更层次结构的URI:
GET http://www.example.com/customers/33245/orders/8769/lineitems/1 在相同的订单顺序中序返回第一个订单项

使用Restful风格接口案例

1.pom.xml

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.3.4.RELEASE</version>
        <relativePath/> <!-- lookup parent from repository -->
    </parent>
    <groupId>com</groupId>
    <artifactId>demo</artifactId>
    <version>0.0.1-SNAPSHOT</version>
    <name>simple</name>
    <description>Demo project for Spring Boot</description>

    <properties>
        <java.version>1.8</java.version>
        <version.swagger>2.8.0</version.swagger>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>

        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-test</artifactId>
            <scope>test</scope>
            <exclusions>
                <exclusion>
                    <groupId>org.junit.vintage</groupId>
                    <artifactId>junit-vintage-engine</artifactId>
                </exclusion>
            </exclusions>
        </dependency>
                <dependency>
                    <groupId>org.springframework.boot</groupId>
                    <artifactId>spring-boot-starter-test</artifactId>
                    <scope>test</scope>
                    <exclusions>
                        <exclusion>
                            <groupId>org.junit.vintage</groupId>
                            <artifactId>junit-vintage-engine</artifactId>
                        </exclusion>
                    </exclusions>
                </dependency>
        <!-- https://mvnrepository.com/artifact/org.projectlombok/lombok -->
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.18.12</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>

    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>

</project>

2.User.java

package com.modle;

import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;

import java.util.Arrays;
import java.util.LinkedList;
import java.util.List;
import java.util.stream.Collectors;

/**
 * 简单的模型类用于测试
 * @Author: wongzeng
 */
@Slf4j
@Data
public class User{
    private Long id;
    private String username;
    private String password;

    static List<User> userList = new LinkedList<>();
    static {
        for(int i=0;i<30;i++){
            userList.add(new User((long)i+1,"user-"+i,"passwd-"+i));
        }
    }
    //测试
    public static List<User> getUserList(){
        return userList;
    }
    public static boolean addUser(User user){
        if(user!=null){
            userList.add(user);
            return true;
        }
        return false;
    }
    //测试条件删除
    public static boolean removeUser(Long id){
        return userList.removeIf(user -> user.getId().equals(id));
    }
    //测试更新
    public static boolean updateUser(User user){
        if(userList.stream().filter(obj->obj.getId().equals(user.getId())).findAny().orElse(null)==null){
            return false;
        }
        userList = userList.stream().map(obj->obj.getId().equals(user.getId())?user:obj).collect(Collectors.toList());
        return true;
    }
    public User(Long id,String username, String password) {
        this.username = username;
        this.password = password;
        this.id = id;
    }
    //测试
    public static void main(String[] args) {
        userList.add(new User(210303193443566L,"xx","xx"));
        Long id =  210303193443566L;
        long id2 = 210303193443566L;
        //基本类型long与对象Long使用==比较,Long会转为基本类型,所以相等
        System.out.println(id==id2);
        //true
        Long id3 = 210303193443566L;
        //对象与对象判断相等,不能用==,而应该用equals方法
        System.out.println(id==id3);
        //false
        System.out.println(id.equals(id3));
        //true
        String username = "fly-sky";
        String password = "666666";
        User user = new User(id,username,password);
        //查找与指定id相等的对象
        for (User p:getUserList().stream().filter(o->o.getId().equals(id)).collect(Collectors.toList())){
            System.out.println(p);
        }
        //更新对象
        System.out.println("result="+updateUser(user));

    }

}


3.UserController.java

package com.controller;

import com.modle.User;
import org.springframework.web.bind.annotation.*;
import java.text.SimpleDateFormat;
import java.util.*;

/**
 * Restful风格接口的测试
 * @Author wongzeng
 */
@ResponseBody
@RestController
//为避免每个方法中都频繁映射/users/...,可直接提取到类上,表示一类资源
@RequestMapping("/users")
public class UserController {
    //查询所有用户使用类上映射,uri:/users
    @GetMapping
    public List<User> getUserByPage(int page, int pageSize){
        int beginIndex = (page-1) * pageSize;
        int endIndex = page * pageSize;
        List<User> users = User.getUserList();
        List<User> result = new LinkedList<>();
        for(int i=0;i<users.size();i++){
            if(i>=beginIndex && i<endIndex){
                result.add(users.get(i));
            }
        }
        return result;
    }
    //添加用户,uri:/users
    @PostMapping
    public User addUser(String username,String password) {
        //生成一个ID:例如210303214107445
        Long id = Long.parseLong(new SimpleDateFormat("yyMMddHHmmssSSS").format(new Date()));
        User user=  new User(id,username,password);
        System.out.println(user);
         return User.addUser(user)?user:null;
    }

    //查询指定用户 uri:/users/{id}
    @GetMapping("/{id}")
    public User getUser(@PathVariable("id") Long id) {
        System.out.println("getUser...");
        Optional<User> user = User.getUserList().stream().filter(obj->obj.getId().equals(id)).findFirst();
        //orElseGet(null)直接会抛异常,而user.orElse(null)不会抛异常而是返回null
        System.out.println(user.orElse(null).toString());
        return user.orElse(null);
    }
    //删除用户
    @DeleteMapping("/{id}")
    public boolean delUser(@PathVariable("id") Long id) {
        System.out.println("delUser...");
        return User.removeUser(id);
    }
    //修改用户信息
    @PutMapping("/{id}")
    public boolean modifyUser(@PathVariable("id")Long id, String username, String password) {
        System.out.println("modifyUser...");
        System.out.println("id = " + id + ", username = " + username + ", password = " + password);
        return User.updateUser(new User(id,username,password));
    }
}

参考资料

RESTful API手册

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 2
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值