RESTful接口API设计规范
2017-11-01 18:00
453 查看
因工作需要,最近需要做webapi接口,领导强烈要求使用RESTful的软件架构风格,所以研究了一下RESTful,本文小结了RESTful的设计规范,也并不全面,主要是总结了下项目所需的规范。
用中杠-不用下杠_;
参数列表要encode;
URI中的名词表示资源集合,使用复数形式。
这里,{version}代表api的版本信息。{domain}是一个你可以用来定义任何技术的区域(例如:安全-允许指定的用户可以访问这个区域。)或者业务上的原因。(例如:同样的功能在同一个前缀之下。)
资源集合:
单个资源:
过深的导航容易导致url膨胀,不易维护,如 GET /categories/1/areas/3/products/4,尽量使用查询参数代替路径中的实体导航,如GET /products?category=1&area=3;
GET(查询):
POST(创建单个资源。POST一般向“资源集合”型uri发起):
PUT(更新单个资源(全量),客户端提供完整的更新后的资源。与之对应的是 PATCH,PATCH 负责部分更新,客户端提供要更新的那些字段。PUT/PATCH一般向“单个资源”型uri发起):
DELETE(删除):
幂等性:执行1次和执行N次,对资源状态改变的效果是等价的。
安全性和幂等性均不保证反复请求能拿到相同的response。以 DELETE 为例,第一次DELETE返回200表示删除成功,第二次返回404提示资源不存在,这是允许的。
本次项目是做一个定时任务的管理系统,要为任务的增、删、查、改、启动、停止、禁用、启用提供接口,
通过路由达到 /tasks/1 这种效果,如:
URI一般使用名词,一些操作以子资源对待,而子资源可以用数据库表的字段表示,如任务的启动、停止可以这么写,这里任务的运行状态为"RunStatus":
Controller部分代码
运行效果截图(此部分界面使用Swagger,相关知识请自行学习)
RESTful 接口规范
1、URI规范说明
URI 表示资源,资源一般对应服务器端领域模型中的实体类。URI规范
不用大写;用中杠-不用下杠_;
参数列表要encode;
URI中的名词表示资源集合,使用复数形式。
高级别的模式
http(s)://server.com/app-name/{version}/{domain}/{rest-convention}这里,{version}代表api的版本信息。{domain}是一个你可以用来定义任何技术的区域(例如:安全-允许指定的用户可以访问这个区域。)或者业务上的原因。(例如:同样的功能在同一个前缀之下。)
资源集合 vs 单个资源
URI表示资源的两种方式:资源集合、单个资源。资源集合:
/categories //所有分类 /categories/1/products //id为1的分类下的所有商品
单个资源:
/categories/1 //id为1的分类 /categories/1;2;3 //id为1,2,3的分类
避免层级过深的URI
/在url中表达层级,用于按实体关联关系进行对象导航,一般根据id导航。过深的导航容易导致url膨胀,不易维护,如 GET /categories/1/areas/3/products/4,尽量使用查询参数代替路径中的实体导航,如GET /products?category=1&area=3;
2、Request
HTTP方法
通过标准HTTP方法对资源CRUDGET(查询):
GET /categories //所有分类 GET /categories/1 //id为1的分类 GET /categories/1/products //id为1的分类下的所有商品
POST(创建单个资源。POST一般向“资源集合”型uri发起):
POST /products //新增商品 POST /categories/1/properties //id为1的分类下创建属性
PUT(更新单个资源(全量),客户端提供完整的更新后的资源。与之对应的是 PATCH,PATCH 负责部分更新,客户端提供要更新的那些字段。PUT/PATCH一般向“单个资源”型uri发起):
PUT /products/1 //修改id为1的商品 PUT /categories/1 //修改id为1的分类
DELETE(删除):
DELETE /products/1 //删除id为1的商品 DELETE /categories/1/products/1;2;3 //删除id为1的分类下id为1,2,3的商品 DELETE /categories/1/properties //删除id为1的分类下所有属性
安全性和幂等性
安全性:不会改变资源状态,可以理解为只读的;幂等性:执行1次和执行N次,对资源状态改变的效果是等价的。
安全性 | 幂等性 | |
GET | √ | √ |
POST | × | × |
PUT | × | √ |
DELETE | × | √ |
复杂查询
查询可以捎带以下参数:示例 | 备注 | |
过滤条件 | ?type=1&age=16 | 允许一定的uri冗余,如 /zoos/1 与 /zoos?id=1 |
排序 | ?sort=age,desc | |
投影 | ?whitelist=id,name,email | |
分页 | ?limit=10&offset=3 |
3、代码实现
此处使用c#语言实现,vs创建webapi项目,本次项目是做一个定时任务的管理系统,要为任务的增、删、查、改、启动、停止、禁用、启用提供接口,
通过路由达到 /tasks/1 这种效果,如:
[HttpGet] [Route("tasks/{id:long}")] [ProducesResponseType(typeof(Model.Task), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task<IActionResult> GetTaskById(long id) { if (id <= 0) { return BadRequest(); } var task = await _taskRepository.GetTaskAsync(id); if (task != null) { return Ok(task); } return NotFound(); }
URI一般使用名词,一些操作以子资源对待,而子资源可以用数据库表的字段表示,如任务的启动、停止可以这么写,这里任务的运行状态为"RunStatus":
/// <summary> /// 启动任务 /// </summary> [Route("tasks/{id}/runstatus")] [HttpPut] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task<IActionResult> StartTask(long id) { await _taskRepository.StartTaskAsync(id); return Ok(); } /// <summary> /// 停止任务 /// </summary> [Route("tasks/{id}/runstatus")] [HttpDelete] [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task<IActionResult> StopTask(long id) { await _taskRepository.StopTaskAsync(id); return NoContent(); }
Controller部分代码
[Produces("application/json")]
[Route("api/v1")]
public class TasksController : Controller
{
private ITaskRepository _taskRepository;
public TasksController(ITaskRepository taskRepository)
{
_taskRepository = taskRepository;
}
/// <summary>
/// 获取任务分页列表
/// </summary>
[Route("tasks")]
[HttpGet]
[ProducesResponseType(typeof(PageListModel<Model.Task>), (int)HttpStatusCode.OK)]
public async Task<IActionResult> Tasks(string name, string group, [FromQuery]int pageIndex = 1, [FromQuery]int pageSize = 10)
{
var list = await _taskRepository.GetTaskPageList(name, group, pageIndex - 1, pageSize);
return Ok(list);
}
/// <summary>
/// 添加任务
/// </summary>
[Route("tasks")]
[HttpPost]
[ProducesResponseType((int)HttpStatusCode.Created)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> CreateTask([FromBody]Model.Task task)
{
await _taskRepository.AddTask(task);
return CreatedAtAction(nameof(GetTaskById), new { id = task._id }, null);
}
/// <summary>
/// 获取任务详情
/// </summary>
[HttpGet] [Route("tasks/{id:long}")] [ProducesResponseType(typeof(Model.Task), (int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.NotFound)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] public async Task<IActionResult> GetTaskById(long id) { if (id <= 0) { return BadRequest(); } var task = await _taskRepository.GetTaskAsync(id); if (task != null) { return Ok(task); } return NotFound(); }
/// <summary>
/// 更新任务
/// </summary>
[Route("tasks")]
[HttpPut]
[ProducesResponseType((int)HttpStatusCode.Created)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> UpdateTask([FromBody]Model.Task task)
{
await _taskRepository.UpdateTaskAsync(task);
return CreatedAtAction(nameof(GetTaskById), new { id = task._id }, null);
}
/// <summary>
/// 删除任务
/// </summary>
[Route("tasks/{id:long}")]
[HttpDelete]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
public async Task<IActionResult> DeleteTask(long id)
{
await _taskRe
4000
pository.DeleteTaskAsync(id);
return NoContent();
}
/// <summary> /// 启动任务 /// </summary> [Route("tasks/{id}/runstatus")] [HttpPut] [ProducesResponseType((int)HttpStatusCode.OK)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task<IActionResult> StartTask(long id) { await _taskRepository.StartTaskAsync(id); return Ok(); } /// <summary> /// 停止任务 /// </summary> [Route("tasks/{id}/runstatus")] [HttpDelete] [ProducesResponseType((int)HttpStatusCode.NoContent)] [ProducesResponseType((int)HttpStatusCode.BadRequest)] [ProducesResponseType((int)HttpStatusCode.NotFound)] public async Task<IActionResult> StopTask(long id) { await _taskRepository.StopTaskAsync(id); return NoContent(); }
/// <summary>
/// 启用任务
/// </summary>
[Route("tasks/{id}/status")]
[HttpPut]
[ProducesResponseType((int)HttpStatusCode.OK)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> EnableTask(long id)
{
await _taskRepository.EnableTaskAsync(id);
return Ok();
}
/// <summary>
/// 禁用任务
/// </summary>
[Route("tasks/{id}/status")]
[HttpDelete]
[ProducesResponseType((int)HttpStatusCode.NoContent)]
[ProducesResponseType((int)HttpStatusCode.BadRequest)]
[ProducesResponseType((int)HttpStatusCode.NotFound)]
public async Task<IActionResult> DisableTask(long id)
{
await _taskRepository.DisableTaskAsync(id);
return NoContent();
}
}
运行效果截图(此部分界面使用Swagger,相关知识请自行学习)
4、参考文档
Restful API 的设计规范RESTful 接口规范
相关文章推荐
- 开放接口/RESTful/Api服务的设计和安全方案
- Restful API 的设计规范 from克鲁斯卡尔的博客
- Restful API 设计规范
- RESTful API实战笔记(接口设计及Java后端实现)
- Spring+SpringMVC+MyBatis+easyUI整合进阶篇(二)RESTful API实战笔记(接口设计及Java后端实现)
- 从Feign使用注意点到RESTFUL接口设计规范
- 微服务RESTful 接口设计规范
- 关于RESTful接口api的设计
- Restful是一种非常优美的http接口设计风格及设计规范
- API Blutprint: RESTful接口文档设计工具配置
- RESTful的Api设计之统一接口
- Restful API 的设计规范(转)
- RESTful API 设计最佳实践
- yii2 RESTful 接口 api -5: restful的测试工具
- App开放接口api安全性—Token签名sign的设计与实现
- RESTful架构与接口设计
- RESTful API 设计指南
- ****RESTful API 设计最佳实践(APP后端API设计参考典范)
- Restful接口设计的学习和实践之路
- REST接口设计规范