您的位置:首页 > 其它

RESTful接口API设计规范

2017-11-01 18:00 453 查看
因工作需要,最近需要做webapi接口,领导强烈要求使用RESTful的软件架构风格,所以研究了一下RESTful,本文小结了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方法对资源CRUD

GET(查询):

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×
安全性和幂等性均不保证反复请求能拿到相同的response。以 DELETE 为例,第一次DELETE返回200表示删除成功,第二次返回404提示资源不存在,这是允许的。

复杂查询

查询可以捎带以下参数:

 示例备注
过滤条件?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 接口规范
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: