RESTful风格openapi接口设计+openapi远程服务调用
我们平常开发一般只使用GET、POST方法。而对于HTTP给出的PUT、DELETE等其他方法都没使用。以RESTful风格设计接口就能全部用上这些方法。
按照RESTful理查德森成熟度模型改造接口
这个模型跟数据库范式等级相似,都是一层一层满足。我们的mvc接口不好说是哪一级,一般都是每个操作一个相当于一个资源。
但.ashx一般处理程序我们平时使用都是是第0级的,总共就一个接口,然后加各种参数,后端判断跳转,使用就特别繁琐。只是满足的不是很标准。
其等级如下
- 级别 0:单一 URI(Single URI):
- 所有 API 请求都发送到同一个 URI。
- 使用 HTTP 动词来区分不同的操作,例如使用 POST 来创建资源,GET 来读取资源,PUT 来更新资源,DELETE 来删除资源。
- 这种级别的 API 没有使用 RESTful 架构的优势,与传统的 RPC 风格相似。
- 级别 1:每个资源一个 URI(One URI per resource):
- 对于不同的资源,使用不同的 URI。
- 仍然使用 HTTP 动词来执行操作,但每个资源都有自己的 URI。
- 这种级别的 API 更符合 RESTful 架构的思想,但还没有完全利用 HTTP 的特性。
- 级别 2:每个资源一个 URI 和标准 HTTP 方法(One URI per resource with standard HTTP methods):
- 每个资源有自己的 URI,并且使用标准的 HTTP 方法来执行操作。
- 使用 GET 来读取资源,POST 来创建资源,PUT 或 PATCH 来更新资源,DELETE 来删除资源。
- 这种级别的 API 开始充分利用了 HTTP 协议的特性,使得 API 更具表现力和可扩展性。
- 级别 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; }
}
现在每个person都成为一个资源,有了自己的路径,并且对person的操作换为了相应的操作码,而不是都是get、post
第三级别接口
这一级的要求是资源的自描述,比如每一个资源要包含它能够接受的操作。我使用RiskFirst.Hateoas
包来实现。
- 首先引用这个包
- 然后添加服务
//添加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<ItemsLinkContainer<Person>>> 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<Person>> 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<List<Person>> 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<Person> 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"
}
}
}
OpenAPI远程服务调用
除了浏览器网页端一般会使用openapi资源,还可以在后端远程调用其他主机的openapi服务。(不过有时候我们都是把openapi服务关了的)。这比注入使用httpclient
去一个一个发请求要清晰的多。
- 连接方式
在连接的服务那里选择添加openapi服务。选择swagger.json文件或在线地址。为了方便,我还是选择运行发布后的exe,再在解决方案中连接里面的opanapi服务。为此,我先注释掉if (app.Environment.IsDevelopment())
再发布
然后添加连接的服务
改下命名空间,免得冲突。显示添加成功
这会在项目文件中添加一个引用<OpenApiReference Include="OpenAPIs\swagger1.json" CodeGenerator="NSwagCSharp" Namespace="JWT授权实验OpenAPI" ClassName="RPCOpenAPI">
并且会生成对应的类。
- 过程调用
需要指定服务IP地址和端口,再使用我们连接服务是定义的类。里面有所需接口。
[AllowAnonymous]
[HttpGet]
public async Task<string> GetPersons()
{
var client = new RPCOpaenAPI("http://localhost:5000", new HttpClient());
var result = await client.GetPersonsAsync();
return JsonConvert.SerializeObject(result);
}
结果如下
RESTful风格openapi接口设计+openapi远程服务调用的更多相关文章
- Restful风格API接口开发springMVC篇
Restful风格的API是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机 ...
- RestKit ,一个用于更好支持RESTful风格服务器接口的iOS库
简介 RestKit 是一个用于更好支持RESTful风格服务器接口的iOS库,可直接将联网获取的json/xml数据转换为iOS对象. 项目主页: RestKit 最新示例: 点击下载 注意: 如果 ...
- SpringBoot RestFul风格API接口开发
本文介绍在使用springBoot如何进行Restful Api接口的开发及相关注解已经参数传递如何处理. 一.概念: REST全称是Representational State Transfer,中 ...
- SpringBoot实战(二)Restful风格API接口
在上一篇SpringBoot实战(一)HelloWorld的基础上,编写一个Restful风格的API接口: 1.根据MVC原则,创建一个简单的目录结构,包括controller和entity,分别创 ...
- SpringBoot之RESTFul风格的接口调用(jQuery-Ajax)
一.Get $.ajax({ type: "get", url: "url地址", async: true, dataType:"json" ...
- RESTFUL风格的接口命名规范
1.首先restfulf风格的api是基于资源的,url命名用来定位资源,而不是表示动作,动作通过请求方式进行表示. 2.URL中应该单复数区分,推荐的实践是永远只用复数.比如GET /api/use ...
- 关于之前提到的python开发restful风格的接口
此处不做详细说明. https://gitee.com/alin2017/my-i-demo.git 附上git地址,有兴趣的可以去clone一下. 里面针对代码都有相应的注释, 对于每一个文件也有r ...
- Restful风格wcf调用
文章:Restful风格wcf调用 作者相当于把wcf服务改造成rest风格. Restful风格wcf调用2——增删改查 这篇文章在第一篇的基础上,进行了优化. Restful风格wcf调用3——S ...
- restful风格接口和spring的运用
Restful风格的API是一种软件架构风格,设计风格而不是标准,只是提供了一组设计原则和约束条件.它主要用于客户端和服务器交互类的软件.基于这个风格设计的软件可以更简洁,更有层次,更易于实现缓存等机 ...
- 第一节:WebApi的纯原生态的RestFul风格接口和路由规则介绍
一. 原生态接口 1. 从默认路由开始分析 在WebApiConfig.cs类中的Register方法中,我们可以看到默认路由如下: 分析:请求地址在 controller 前面需要加上 api/,c ...
随机推荐
- 【FAQ】接入HMS Core广告服务中的常见问题总结和解决方法
HMS Core广告服务(Ads Kit)为开发者提供流量变现服务和广告标识服务,依托华为终端能力,整合资源,帮助开发者获取高质量的广告内容.同时提供转化跟踪参数服务,支持三方监测平台.广告主进行转化 ...
- 代码覆盖率检查工具 -- Coverage,简单使用
Coverage 一个专门用来检查代码覆盖率的工具,他的使用非常简单,有两种使用方法:[命令行运行,配合测试套件使用] 安装: pip install coverage 一.准备素材 main.py ...
- openGauss/MOGDB时间消耗相关视图
openGauss/MOGDB 时间消耗相关视图 本文出处:https://www.modb.pro/db/388212 数据库版本 openGauss/MOGDB-2.1.1 一.显示当前用户在各个 ...
- SpringBoot常用注解整理
@SpringBootApplication 定义在main方法入口类处,用于启动sping boot应用项目 @Target(ElementType.TYPE) @Retention(Retenti ...
- 实训篇-Html-注册页面【简单】
<!DOCTYPE html> <html> <head> <meta charset="utf-8"> <title> ...
- 升级Django项目过程中问题记录
升级内容: python版本:3.8.4升到3.10.7 Django版本:2.2.13升到4.2 所遇问题: 1. error in anyjson setup command: use_2to3 ...
- 如何把jQuery对象转成DOM对象?OR DOM对象转化成jQuery对象
如何把jQuery对象转成DOM对象? 参考:https://www.imooc.com/code/8110 利用数组下标的方式读取到jQuery中的DOM对象 <div>元素一</ ...
- 【vue】纯前端图形验证码实现
[vue]纯前端图形验证码实现 感觉人不能在SQL里面淹死,得看看别的东西了 因为是上班摸鱼偷摸搞的,所以人比较懒,很多东西也懒得修修改改,直接放在一个html文件下了 页面如下 js的生成图形逻辑是 ...
- 力扣1126(MySQL)-查询活跃业务(中等)
题目: 事件表:Events 此表的主键是 (business_id, event_type). 表中的每一行记录了某种类型的事件在某些业务中多次发生的信息. 问题写一段 SQL 来查询所有活跃的业务 ...
- 【开发者成长】喧哗的背后:Serverless 的挑战
作者 | 许晓斌 阿里云高级技术专家,目前负责阿里集团 Serverless 研发运维平台建设,<Maven 实战>作者,曾经是 Maven 中央仓库的维护者. 导读:本文作者作为阿里集 ...