RESTful风格接口设计

news/2024/10/14 4:29:30

我们平常开发一般只使用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/1return 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资源增加hateoasconfig.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资源增加hateoasconfig.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/1return 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"}}
}

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.ryyt.cn/news/26835.html

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈,一经查实,立即删除!

相关文章

前端 Vue-cli中 vue.config.js 的配置详解

Vue-cli 3 / Vue-cli 4 目录结构├── README.md # 说明 |-- dist # 打包后文件夹 ├── babel.config.js # babel语法编译 ├── package-lock.json ├── public # 静态文件夹,这类资源将会直接被拷贝,而不会经过 webpack 的处…

前端 Vue webpack配置之 webpack.config.js 文件配置

Webpack 在执行的时候,除了在命令行传入参数,还可以通过指定的配置文件来执行。默认情况下,会搜索当前目录的 webpack.config.js 文件,这个文件是一个 node.js 模块,返回一个 json 格式的配置信息对象,或者通过 --config 选项来指定配置文件。 .输入命令:webpack,即可按…

攻防世界 serial-150 IDA动调

比较完整的做题记录吧,很基础,当作过一遍题目,大佬请飘过 题目 分析过程 丢到PE里面,是一个64位的ELF丢到IDA里面,查看字符串,发现线索 但是双击进去,不是汇编。是一个只读的rodata段 左边函数也不多,发现没有main函数,先点进去启动的start函数看看,发现main函数 点进…

elementui使用

1 # 1 开源的样式库,方便在vue中使用2 -elementui:饿了么团队开源的 web端3 https://element.eleme.cn/#/zh-CN4 -vant:有赞团队,移动端5 https://vant-ui.github.io/vant/#/zh-CN6 -ant design:阿里团队7 https://1x.antdv.com/docs…

2024/5/2

完成了河北科技查询系统全部内容

cpp字符串相关

字符串相关 文章参考: [详解-字符串] C++必知必会 字符串-string常用各种操作解析 - 知乎 (zhihu.com) C++ 字符串(string)常用操作总结 - 知乎 (zhihu.com) c++读取字符串和字符的6种函数_c++获取字符串的每个字符-CSDN博客 头文件 #include <string>定义字符串 stri…

响应式动漫音乐/个人博客杂志主题国漫FM模板

国漫FM主题V1.8是以Ajax加以CSS动画的方式,很好的将优雅的设计感和极度精简的代码同时表现了出来,进而缔造出这样一款十分经典的名为Always for you的WordPress博客主题。正如作者自己所言:如果你想让你的WordPress博客看起来个性十足。FM主题为响应式格子布局(瀑布流),是一…

WordPress CVE-2022-4230复现分析

前言 开始CVE审计之旅 WP Statistics WordPress 插件13.2.9之前的版本不会转义参数,这可能允许经过身份验证的用户执行 SQL 注入攻击。默认情况下,具有管理选项功能 (admin+) 的用户可以使用受影响的功能,但是该插件有一个设置允许低权限用户也可以访问它,其实就是没对admi…