首页 > 其他分享 >RESTful风格接口设计

RESTful风格接口设计

时间:2024-05-02 15:44:39浏览次数:24  
标签:return Name persons 接口 public person 风格 RESTful id

我们平常开发一般只使用GET、POST方法。而对于HTTP给出的PUT、DELETE等其他方法都没使用。以RESTful风格设计接口就能全部用上这些方法。

按照RESTful理查德森成熟度模型改造接口

这个模型跟数据库范式等级相似,都是一层一层满足。我们的mvc接口不好说是哪一级,一般都是每个操作一个相当于一个资源。
但.ashx一般处理程序我们平时使用都是是第0级的,总共就一个接口,然后加各种参数,后端判断跳转,使用就特别繁琐。只是满足的不是很标准。
其等级如下

  1. 级别 0:单一 URI(Single URI):
  • 所有 API 请求都发送到同一个 URI。
  • 使用 HTTP 动词来区分不同的操作,例如使用 POST 来创建资源,GET 来读取资源,PUT 来更新资源,DELETE 来删除资源。
  • 这种级别的 API 没有使用 RESTful 架构的优势,与传统的 RPC 风格相似。
  1. 级别 1:每个资源一个 URI(One URI per resource):
  • 对于不同的资源,使用不同的 URI。
  • 仍然使用 HTTP 动词来执行操作,但每个资源都有自己的 URI。
  • 这种级别的 API 更符合 RESTful 架构的思想,但还没有完全利用 HTTP 的特性。
  1. 级别 2:每个资源一个 URI 和标准 HTTP 方法(One URI per resource with standard HTTP methods):
  • 每个资源有自己的 URI,并且使用标准的 HTTP 方法来执行操作。
  • 使用 GET 来读取资源,POST 来创建资源,PUT 或 PATCH 来更新资源,DELETE 来删除资源。
  • 这种级别的 API 开始充分利用了 HTTP 协议的特性,使得 API 更具表现力和可扩展性。
  1. 级别 3:使用超媒体(HATEOAS)(Hypermedia as the Engine of Application State):
  • API 响应中包含了超媒体链接,使得客户端能够动态地发现和使用 API 中的资源。
  • 客户端可以通过响应中的链接来导航和操作资源,而不需要依赖于固定的 API 结构。
  • 这种级别的 API 是最符合 RESTful 架构的,提供了最大的灵活性和可发现性。

RESTful风格的一些要点

  • 资源不包含动词
  • 资源使用复数
  • 资源唯一标识
    对于第一点,一般都是使用路由模板。路由名称和方法名称分离。
    对于第三点,使用路由参数代替查询字符串

第二级别接口

第二级别接口
[AllowAnonymous]
[ApiController]
[Route("persons")]
public class RESTfulUserController : Controller
{
    private List<Person> _persons = new List<Person>()
    {
        new Person() { Id = 1, Name = "John", Age = 30 },
        new Person() { Id = 2, Name = "Alice", Age = 25 },
        new Person() { Id = 3, Name = "Bob", Age = 35 }
    };

    [HttpGet("")]
    public ActionResult GetPersons()
    {
        return Ok(_persons);
    }

    //{id}是一个占位符,用于表示在 URL 中传递的参数,叫做路由参数。也表示特定资源的标识符,通常是资源的唯一标识符。需与形参一致
    //Name字段用于为路由命名,别名的意思,通常与 CreatedAtRoute 或 Url.Action,Url.RouteUrl 等方法结合使用,以便在代码中引用具体的路由
    //nameof(生成字符串"GetPersons")
    // GET api/persons/{id}
    [HttpGet("{id}",Name =nameof(GetPerson))]
    public ActionResult GetPerson(int id)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        if (person == null)
        {
            return NotFound();
        }
        return Ok(person);
    }

    //多条件查询reastful风格应单独做成资源,不使用路由参数资源化
    [HttpGet("search")]
    public ActionResult SearchPerson(string? name,int? age)
    {
        var searchperson = _persons;
        if (!string.IsNullOrEmpty(name))
            searchperson= searchperson.Where(p => p.Name == name).ToList();
        if (age.HasValue)
        {
            searchperson = searchperson.Where(r => r.Age == age).ToList();
        }
        if (searchperson.Count==0)
        {
            return NotFound();
        }
        return Ok(searchperson);
    }

    // POST api/persons
    [HttpPost("")]
    public ActionResult CreatePerson(Person person)
    {
        person.Id = _persons.Count + 1;
        _persons.Add(person);
        //返回201表示资源已创建,并且在location标头中设置资源地址
        //第二个参数表示查询参数,如果该路由具有具有参数,则会将查询参数转化为路由参数,比如persons?id=1=?persons/1
        return CreatedAtRoute(nameof(GetPerson), new { id = person.Id }, person);
    }

    // PUT api/persons/{id}
    [HttpPut("{id}")]
    public ActionResult UpdatePerson(int id, Person person)
    {
        var existingPerson = _persons.FirstOrDefault(p => p.Id == id);
        if (existingPerson == null)
        {
            return NotFound();
        }
        existingPerson.Name = person.Name;
        existingPerson.Age = person.Age;
        return Ok(existingPerson);
    }

    // DELETE api/persons/{id}
    [HttpDelete("{id}")]
    public ActionResult DeletePerson(int id)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        if (person == null)
        {
            return NotFound();
        }
        _persons.Remove(person);
        //返回204表示操作成功,并且资源已不存在
        return StatusCode((int)System.Net.HttpStatusCode.NoContent);
    }
}

public class Person
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

image
image

现在每个person都成为一个资源,有了自己的路径,并且对person的操作换为了相应的操作码,而不是都是get、post

第三级别接口

这一级的要求是资源的自描述,比如每一个资源要包含它能够接受的操作。我使用RiskFirst.Hateoas包来实现。

  • 首先引用这个包
    image
  • 然后添加服务
//添加restful hateoas服务
builder.Services.AddLinks(config =>
{
    //给person资源增加hateoas
    config.AddPolicy<Person>(policy =>
    {
        policy.RequireSelfLink();
        //第一个参数代表一个link的字段名,第二个参数是路由名称,由http特性的Name字段配置,参三个字段是路由参数
        policy.RequireRoutedLink("update", nameof(RESTfulUserController.UpdatePerson), x => new { id = x.Id });
        policy.RequireRoutedLink("delete", nameof(RESTfulUserController.DeletePerson), x => new { id = x.Id });
        policy.RequireRoutedLink("all", nameof(RESTfulUserController.GetPersons));
    });
    //给persons资源增加hateoas
    config.AddPolicy<ItemsLinkContainer<Person>>(policy =>
    {
        policy.RequireSelfLink()
        .RequireRoutedLink("search", nameof(RESTfulUserController.SearchPerson), x => new { Name = "name", Age = 0 })
        .RequireRoutedLink("create", nameof(RESTfulUserController.CreatePerson));

    });
});
  • 最后改造资源
    主要是为返回的实体添加继承Person:LinkContainer
    注入hateoas服务ILinksService linksService到控制器中
    调用await LinksService.AddLinksAsync(person);处理实体
第三级实现
[AllowAnonymous]
[ApiController]
[Route("persons")]
public class RESTfulUserController : Controller
{
    private List<Person> _persons = new List<Person>()
    {
        new Person() { Id = 1, Name = "John", Age = 30 },
        new Person() { Id = 2, Name = "Alice", Age = 25 },
        new Person() { Id = 3, Name = "Bob", Age = 35 }
    };

    public ILinksService LinksService { get; }

    public RESTfulUserController(ILinksService linksService)
    {
        LinksService = linksService;
    }

    [HttpGet("",Name =nameof(GetPersons))]
    public async Task<ActionResult> GetPersons()
    {
        _persons.ForEach(async p => await LinksService.AddLinksAsync(p));
        var result = new ItemsLinkContainer<Person>() { Items = _persons };
        await LinksService.AddLinksAsync(result);
        return Ok(result);
    }

    //{id}是一个占位符,用于表示在 URL 中传递的参数,叫做路由参数。也表示特定资源的标识符,通常是资源的唯一标识符。需与形参一致
    //Name字段用于为路由命名,别名的意思,通常与 CreatedAtRoute 或 Url.Action,Url.RouteUrl 等方法结合使用,以便在代码中引用具体的路由
    //nameof(生成字符串"GetPerson")
    // GET api/persons/{id}
    [HttpGet("{id}",Name =nameof(GetPerson))]
    public async Task<ActionResult> GetPerson(int id)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        await LinksService.AddLinksAsync(person);
        if (person == null)
        {
            return NotFound();
        }
        return Ok(person);
    }

    //多条件查询reastful风格应单独做成资源,不使用路由参数资源化
    [HttpGet("search",Name =nameof(SearchPerson))]
    public ActionResult SearchPerson(string? name,int? age)
    {
        var searchperson = _persons;
        if (!string.IsNullOrEmpty(name))
            searchperson= searchperson.Where(p => p.Name == name).ToList();
        if (age.HasValue)
        {
            searchperson = searchperson.Where(r => r.Age == age).ToList();
        }
        if (searchperson.Count==0)
        {
            return NotFound();
        }
        return Ok(searchperson);
    }

    // POST api/persons
    [HttpPost("",Name =nameof(CreatePerson))]
    public ActionResult CreatePerson(Person person)
    {
        person.Id = _persons.Count + 1;
        _persons.Add(person);
        //返回201表示资源已创建,并且在location标头中设置资源地址
        //第二个参数表示查询参数,如果该路由具有具有参数,则会将查询参数转化为路由参数,比如persons?id=1=?persons/1
        return CreatedAtRoute(nameof(GetPerson), new { id = person.Id }, person);
    }

    // PUT api/persons/{id}
    [HttpPut("{id}",Name =nameof(UpdatePerson))]
    public ActionResult UpdatePerson(int id, Person person)
    {
        var existingPerson = _persons.FirstOrDefault(p => p.Id == id);
        if (existingPerson == null)
        {
            return NotFound();
        }
        existingPerson.Name = person.Name;
        existingPerson.Age = person.Age;
        return Ok(existingPerson);
    }

    // DELETE api/persons/{id}
    [HttpDelete("{id}",Name =nameof(DeletePerson))]
    public ActionResult DeletePerson(int id)
    {
        var person = _persons.FirstOrDefault(p => p.Id == id);
        if (person == null)
        {
            return NotFound();
        }
        _persons.Remove(person);
        //返回204表示操作成功,并且资源已不存在
        return StatusCode((int)System.Net.HttpStatusCode.NoContent);
    }
}

public class Person:LinkContainer
{
    public int Id { get; set; }
    public string Name { get; set; }
    public int Age { get; set; }
}

persons资源返回结果是这样子的,对每个资源增加了links字段。

/person
{
  "items": [
    {
      "id": 1,
      "name": "John",
      "age": 30,
      "links": {
        "self": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        },
        "update": {
          "rel": "RESTfulUser/UpdatePerson",
          "href": "http://localhost:5234/persons/1",
          "method": "PUT"
        },
        "delete": {
          "rel": "RESTfulUser/DeletePerson",
          "href": "http://localhost:5234/persons/1",
          "method": "DELETE"
        },
        "all": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        }
      }
    },
    {
      "id": 2,
      "name": "Alice",
      "age": 25,
      "links": {
        "self": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        },
        "update": {
          "rel": "RESTfulUser/UpdatePerson",
          "href": "http://localhost:5234/persons/2",
          "method": "PUT"
        },
        "delete": {
          "rel": "RESTfulUser/DeletePerson",
          "href": "http://localhost:5234/persons/2",
          "method": "DELETE"
        },
        "all": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        }
      }
    },
    {
      "id": 3,
      "name": "Bob",
      "age": 35,
      "links": {
        "self": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        },
        "update": {
          "rel": "RESTfulUser/UpdatePerson",
          "href": "http://localhost:5234/persons/3",
          "method": "PUT"
        },
        "delete": {
          "rel": "RESTfulUser/DeletePerson",
          "href": "http://localhost:5234/persons/3",
          "method": "DELETE"
        },
        "all": {
          "rel": "RESTfulUser/GetPersons",
          "href": "http://localhost:5234/persons",
          "method": "GET"
        }
      }
    }
  ],
  "links": {
    "self": {
      "rel": "RESTfulUser/GetPersons",
      "href": "http://localhost:5234/persons",
      "method": "GET"
    },
    "search": {
      "rel": "RESTfulUser/SearchPerson",
      "href": "http://localhost:5234/persons/search?Name=name&Age=0",
      "method": "GET"
    },
    "create": {
      "rel": "RESTfulUser/CreatePerson",
      "href": "http://localhost:5234/persons",
      "method": "POST"
    }
  }
}

标签:return,Name,persons,接口,public,person,风格,RESTful,id
From: https://www.cnblogs.com/ggtc/p/18170252

相关文章

  • 双向链表及双向循环链表接口设计
    双向链表及双向循环链表接口设计双向链表接口设计由于带头结点更加方便用户进行数据访问,所以本次创建一条带头结点的双向不循环的链表。创建新的头结点和新节点//数据类型typedefintdatatype_t;//创建结点类型typedefstructdoublelinckelist{datatype_tdata;......
  • 双向链表及双向循环链表接口设计
    双向链表及双向循环链表接口设计双向链表接口设计由于带头结点更加方便用户进行数据访问,所以本次创建一条带头结点的双向不循环的链表。创建新的头结点和新节点``//数据类型`typedefintdatatype_t;//创建结点类型typedefstructdoublelinckelist{datatype_tdata;//......
  • 【Swing】UI外观风格设置
    设置系统风格UIManager.setLookAndFeel(UIManager.getSystemLookAndFeelClassName());1、Metal风格(默认)StringlookAndFeel="javax.swing.plaf.metal.MetalLookAndFeel";UIManager.setLookAndFeel(lookAndFeel);2、Windows风格StringlookAndFeel="com.sun.java......
  • 手机运营商二要素比对接口:验证用户手机与身份信息一致性
     手机运营商二要素比对接口是一种验证用户手机与身份信息一致性的工具。在实名注册、风控审核等场景中,我们经常需要验证用户的手机号码与姓名是否一致,以确保用户身份的真实性。这个接口可以广泛应用于电商、游戏、直播、金融等需要用户实名认证的场景,并且还支持携号转网核验。......
  • 16_ioctl接口
    ioctl接口structfile_operations{ ...... long(*unlocked_ioctl)(structfile*,unsignedint,unsignedlong); ......};1.什么是unlocked_ioctl接口?​unlocked_ioctl就是ioctl接口,但是功能和对应的系统调用均没有发生变化。2.unlocked_ioctl和read/write函......
  • TypeScript入门3:接口、多态
    //接口:通常情况下,接⼝中只会包含属性和⽅法的声明,⽽不包含具体的实现细节,具体的细节由其实现类完成interfacePerson9{id:number;name:string;age:number;introduce():void;}//实现类中,需要包含接⼝属性的赋值逻辑,以及接⼝⽅法的实现逻辑classStudent9im......
  • Go语言系列——数组和切片、可变参数函数、Maps、字符串、指针、结构体、方法、接口(一
    文章目录11-数组和切片数组数组的声明数组是值类型数组的长度使用range迭代数组多维数组切片创建一个切片切片的修改切片的长度和容量使用make创建一个切片追加切片元素切片的函数传递多维切片内存优化12-可变参数函数什么是可变参数函数语法通过一些例子理解可变参......
  • fiddler 修改请求接口的返回结果
    修改返回结果Response结果有两种方式,一种把结果写在文件中,一种直接修改接口返回的结果第一种:把response结果放在txt文件中让其访问txt的内容response.txt文件中内容如下:{"code":0,"msg":"查询成功!","count":0,"data":[]}操作步骤:1、点击需要修改response的url2、......
  • C#接口的主要特点
    C#接口(Interface)是一种引用类型,它定义了一组契约或规范,这些契约或规范由方法、属性、事件或索引器组成。接口本身不实现任何成员,而是由实现它的类或结构来提供具体实现。C#接口的主要特点:不能包含访问修饰符:接口中的成员不能包含访问修饰符,默认为public。不能包含字段、常量或......
  • C#接口、抽象类、普通类和继承(子类与父类)都有其特定的用途和场景
    在C#(以及许多其他面向对象的编程语言中),接口、抽象类、普通类和继承(子类与父类)都有其特定的用途和场景。选择使用哪种机制通常取决于你的具体需求和设计目标。不过,关于“能使用接口就不用抽象类,能使用抽象类就不用类,能用父类就不用子类”的说法,这并不完全准确,因为每种机制都有其独......