.netcore 3.1高性能微服务架构:webapi规范
1.1 定义
1、基础接口:单一职责原则,每个接口只负责各自的业务,下接db,通用性强。
2、聚合接口:根据调用方需求聚合基础接口数据,业务性强。
1.2 协议
1. 客户端在通过 API 与后端服务通信的过程中, 应该使用 HTTPS(生产环境) 协议
2. 服务端响应的数据格式统一为JSON
1.3域名host
prd环境:https://xxx-xxx-api.example.com/
uat环境:https://xxx-xxx-api-uat.example.com/
test环境:https://xxx-xxx-api-test.example.com/
dev环境:https://xxx-xxx-api-dev.example.com/
将api放到子域名里,这种做法可以保持某些规模化上的灵活性。
1.4路径path
path命名应该是以资源为导向的命名,对资源的操作是由HttpMethod(get、post、put、delete)来决定。所以一般来说url上的单词都应该是名词,一定不要是动词。一般遵循以下约定:
(1)URL 的命名必须全部小写;
(2) URL 必须 是易读的 URL;
(3)一定不可 暴露服务器架构
(4)出现复合词汇使用下划线分隔,例如:animal_types
举几个正面例子:
新增用户:http://localhost/user post方法提交;
修改用户:http://localhost/users put方法提交;
删除文章:http://localhost/articles?author=1&category=2
delete方法提交;
查询用户:http://localhost/users get方法提交;
查询文章:http://localhost/articles?author=1&category=2get方法提交;
错误的例子如下:
http://localhost/get_user
https://api.example.com/getUserInfo?userid=1
https://api.example.com/getusers
https://api.example.com/sv/u
https://api.example.com/cgi-bin/users/get_user.php?userid=1
1.5动词
- RESTful
的核心思想就是,客户端发出的数据操作指令都是"动词 + 宾语"的结构,动词通常就是四种 HTTP 方法,对应 CRUD 操作: 
| 
 GET(SELECT):从服务器取出资源(一项或多项)。 POST(CREATE):在服务器新建一个资源。 PUT(UPDATE):在服务器更新资源(客户端提供改变后的完整资源)。 PATCH(UPDATE):在服务器更新资源(客户端提供改变的属性)。 DELETE(DELETE):从服务器删除资源。  | 
其中
(1)删除资源 必须 用 DELETE 方法
(2)创建新的资源 必须 使用 POST 方法
(3)更新资源 应该 使用 PUT 方法
(4)获取资源信息 必须 使用 GET 方法
针对每一个路径来说,下面列出所有可行的 HTTP 动词和端点的组合
| 
 请求方  | 
 URL  | 
 描述  | 
|
| 
 法  | 
|||
| 
 GET  | 
 /zoos  | 
 列出所有的动物园(ID和名称,不要太详细)  | 
|
| 
 POST  | 
 /zoos  | 
 新增一个新的动物园  | 
|
| 
 GET  | 
 /zoos/{zoo}  | 
 获取指定动物园详情  | 
|
| 
 PUT  | 
 /zoos/{zoo}  | 
 更新指定动物园(整个对象)  | 
|
| 
 PATCH  | 
 /zoos/{zoo}  | 
 更新动物园(部分对象)  | 
|
| 
 DELETE  | 
 /zoos/{zoo}  | 
 删除指定动物园  | 
|
| 
 GET  | 
 /zoos/{zoo}/animals  | 
 检索指定动物园下的动物列表(ID和名称,不要太详  | 
|
| 
 细)  | 
|||
| 
 GET  | 
 /animals  | 
 列出所有动物(ID和名称)。  | 
|
| 
 POST  | 
 /animals  | 
 新增新的动物  | 
|
| 
 GET  | 
 /animals/{animal}  | 
 获取指定的动物详情  | 
|
| 
 PUT  | 
 /animals/{animal}  | 
 更新指定的动物(整个对象)  | 
|
| 
 PATCH  | 
 /animals/{animal}  | 
 更新指定的动物(部分对象)  | 
|
| 
 GET  | 
 /animal_types  | 
 获取所有动物类型(ID和名称,不要太详细)  | 
|
| 
 GET  | 
 /animal_types/{type}  | 
 获取指定的动物类型详情  | 
|
| 
 GET  | 
 /employees  | 
 检索整个雇员列表  | 
|
| 
 GET  | 
 /employees/{employee}  | 
 检索指定特定的员工  | 
|
| 
 GET  | 
 /zoos/{zoo}/employees  | 
 检索在这个动物园工作的雇员的名单(身份证和姓名)  | 
|
| 
 POST  | 
 /employees  | 
 新增指定新员工  | 
|
| 
 POST  | 
 /zoos/{zoo}/employees  | 
 在特定的动物园雇佣一名员工  | 
|
| 
 DELETE  | 
 /zoos/{zoo}/employees/{employee}  | 
 从某个动物园解雇一名员工  | 
|
1.6入参
1、如果记录数量很多,服务器不可能都将它们返回给用户。API 应该 提供参数,过滤返回结果。下面是一些常见的参数。
- ?limit=10:指定返回记录的数量
 - ?offset=10:指定返回记录的开始位置。
 - ?page=2&per_page=100:指定第几页,以及每页的记录数。
 - ?sortby=name&order=asc:指定返回结果按照哪个属性排序,以及排序顺序。
 - ?animal_type_id=1:指定筛选条件
 
所有URL参数 必须是全小写,必须使用下划线类型的参数形式。
分页参数 必须 固定为 page 、 per_page
经常使用的、复杂的查询 应该 标签化,降低维护成本,如
GET /trades?status=closed&sort=sortby=name&order=asc # 可为其定制快捷方式 GET /trades/recently_closed
2、入参可分为业务参数和公共参数;公共参数有:
| 
 参数  | 
 名称  | 
 说明  | 
| 
 timestamp  | 
 时间戳  | 
|
| 
 clientid  | 
 调用方appid  | 
 统一管理应用,否则不放行  | 
| 
 token  | 
 令牌  | 
 幂等情况可用  | 
| 
 version  | 
 版本号  | 
1.7响应
1、出参(返回值):必须的字段有:
| 
 字段  | 
 类型  | 
 描述  | 
| 
 code  | 
 数值  | 
 状态码  | 
| 
 msg  | 
 字符串  | 
 信息描述  | 
| 
 data  | 
 结果集  | 
 返回结果集  | 
2、如果请求处理完全正确,则状态码为0 ;
3、状态码暂定8位数数字,前4位为某一个应用(服务)拟的一个数字,后4位为具体的状态值。状态码分为2种---公共和自定义,公共码以0打头+3位数。
比如:
99990400 --客户端错误,比如请求语法格式错误、无效的请求、无效的签名等。
99991001 -----用户Id不能为空
响应的公共码如下:
| 
 编码  | 
 描述  | 
 说明  | 
| 
 001  | 
 注解使用错误  | 
 
  | 
| 
 002  | 
 微服务不在线,或网络超时  | 
 
  | 
| 
 003  | 
 TOKEN解析失败  | 
 
  | 
| 
 004  | 
 TOKEN无效或没有对应的用户  | 
 
  | 
| 
 400  | 
 客户端错误,比如请求语法格式错误、  | 
 服务器 应该 放弃该请求  | 
| 
 401  | 
 需要身份认证,比如access_token 无效/过期  | 
 客户端在收到 401 响应后,  | 
| 
 403  | 
 没有权限访问该请求  | 
 服务器收到请求但拒绝提供服务。  | 
| 
 404  | 
 用户请求的资源不存在  | 
 如获取不存在的用户信息  | 
| 
 410  | 
 请求的资源不存在,并且未来也不会存在  | 
 在收到 410 状态码后,  | 
| 
 429  | 
 请求次数超过允许范围  | 
 
  | 
| 
 500  | 
 未知异常  | 
 应该 提供完整的错误信息支持,也方便跟踪调试  | 
1.8项目结构
1、采用经典DDD领域取到模型:(默认一个解决方案有5个项目)

5个项目分别为:
Web层为最外层接口定义;
Service为具体的应用服务处理;
Infrastructure基础设施层,处理具体的业务逻辑和数据DB的处理;
Domain领域层为模型和仓库接口interface;
Common为通用的一些Helper类;
2、一个解决方案创建5个项目(如上图),并且里包含常用的基础组件:Log4net日志,听云监听;dockerfile,skywalking,全局异常捕捉,接口请求开始和结束的日志记录,swagger,service层的依赖注入,Mapping等。
3、代码全部采用依赖注入写法,尽量少些静态类;
4、HttpClient的写法:使用采用.netcore官方提供的方法,采用工厂类+依赖注入方式:实例代码如下:
、SartUp类里添加代码-- httpclient初始化:
services.AddHttpClient("MsgApi", c =>
{
c.BaseAddress = new Uri(Configuration["OuterApi:MsgApi:url"]);
c.Timeout = TimeSpan.FromSeconds();
}); //2 构造函注入
private IDbContext _dbContext;
private IUnitOfWork _unitOfWork;
private IordersRepository _ordersRepository;
private IordercourseRepository _ordercourseRepository;
private ILogger _logger;
privatereadonly IConfiguration _config;
privatereadonly IHttpClientFactory _clientFactory; public ordersService(IDbContext dbContext, ILogger<ordersService> logger, IConfiguration config, IHttpClientFactory clientFactory)
{
_dbContext = dbContext;
_unitOfWork = new UnitOfWork(_dbContext);
_ordersRepository = new ordersRepository(_dbContext);
_ordercourseRepository = new ordercourseRepository(_dbContext);
_mapper = mapper;
_config = config;
_logger = logger;
_clientFactory = clientFactory;
} //3使用
///<summary>
///判断此时该校区是否可以下单
///</summary>
///<param name="req"></param>
///<returns></returns>
publicasync Task<Result<string>> CheckDept(CheckSchoolDeptReq req)
{
Result<string> sendRet = new Result<string>();
try
{
HttpClient client = _clientFactory.CreateClient("ContractApi");
MyHttpClientHelper myHttpClientHelper = new MyHttpClientHelper();
MarketToUPCCheckReq checkreq = new MarketToUPCCheckReq();
sendRet = await myHttpClientHelper.GetData<Result<string>>(client, "MarketToUPCCheck", checkreq);
}
catch (Exception ex)
{
sendRet.state = false;
sendRet.error_code = ErrorCode.SysExceptionError;
sendRet.error_msg = "调用《是否可以下订单接口》报错了。请重试或者联系管理员!";
_logger.LogError(ex, ErrorCode.SysExceptionError +"调用《是否可以下订单》接口报错了:" + ex.Message);
} return sendRet;
}
1.9日志
1、接口开始前和结束后都已在LogstashFilter里记录,接口里就不需要再次记录;
LogstashFilter里的代码如下:
/// <summary>
/// 记录日志用过滤器
/// </summary>
public class LogstashFilter : IActionFilter, IResultFilter
{
private string ActionArguments { get; set; } /// <summary>
/// 请求体中的所有值
/// </summary>
private string RequestBody { get; set; }
private Stopwatch Stopwatch { get; set; } private ILogger _logger; public LogstashFilter(ILogger<LogstashFilter> logger )
{
_logger = logger; } /// <summary>
/// Action 调用前执行
/// </summary>
/// <param name="context"></param>
public void OnActionExecuting(ActionExecutingContext context)
{ long contentLen = context.HttpContext.Request.ContentLength == null ? : context.HttpContext.Request.ContentLength.Value;
if (contentLen > )
{
// 读取请求体中所有内容
System.IO.Stream stream = context.HttpContext.Request.Body;
if (context.HttpContext.Request.Method == "POST")
{
stream.Position = ;
}
byte[] buffer = new byte[contentLen];
stream.Read(buffer, , buffer.Length); RequestBody = System.Text.Encoding.UTF8.GetString(buffer);// 转化为字符串
} ActionArguments = JsonConvert.SerializeObject(context.ActionArguments); Stopwatch = new Stopwatch();
Stopwatch.Start(); string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
string method = context.HttpContext.Request.Method; _logger.LogInformation($"地址:{url} \n " +
$"方式:{method} \n " +
$"请求体:{RequestBody} \n " +
$"完整参数:{ActionArguments}\n " ); } /// <summary>
/// Action 方法调用后,Result 方法调用前执行
/// </summary>
/// <param name="context"></param>
public void OnActionExecuted(ActionExecutedContext context)
{
// do nothing
} /// <summary>
/// Result 方法调用前(View 呈现前)执行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuting(ResultExecutingContext context)
{
// do nothing
} /// <summary>
/// Result 方法调用后执行
/// </summary>
/// <param name="context"></param>
public void OnResultExecuted(ResultExecutedContext context)
{ Stopwatch.Stop();
string url = context.HttpContext.Request.Host + context.HttpContext.Request.Path + context.HttpContext.Request.QueryString;
string method = context.HttpContext.Request.Method;
string qs = ActionArguments;
string res = "在返回结果前发生了异常";
if (context.Result is ObjectResult)
{
dynamic result = context.Result.GetType().Name == "EmptyResult" ? new { Value = "无返回结果" } : context.Result as dynamic;
if (result != null)
{
res = JsonConvert.SerializeObject(result.Value);
} } _logger.LogInformation($"地址:{url} \n " +
$"方式:{method} \n " +
$"请求体:{RequestBody} \n " +
$"参数:{qs}\n " +
$"结果:{res}\n " +
$"耗时:{Stopwatch.Elapsed.TotalMilliseconds} 毫秒"); }
}
2、try Catch日志必须要添加LogError日志,并且要将堆栈信息记录,代码如下:
catch (Exception ex)
{
_logger.LogError(ex, ErrorCode500 + ex.Message);
}
.netcore 3.1高性能微服务架构:webapi规范的更多相关文章
- .netcore 3.1高性能微服务架构:封装调用外部服务的接口方法--HttpClient客户端思路分析
		
众所周知,微服务架构是由一众微服务组成,项目中调用其他微服务接口更是常见的操作.为了便于调用外部接口,我们的常用思路一般都是封装一个外部接口的客户端,使用时候直接调用相应的方法.webservice或 ...
 - .netcore 3.1高性能微服务架构:加入swagger接口文档
		
本文为原创文章:首发:http://www.zyiz.net/tech/detail-108663.html swagger是什么? Swagger 是一个规范和完整的框架,用于生成.描述.调用和可视 ...
 - (1)学习笔记 ) ASP.NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型
		
开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点: 1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块: 2)系统耦合性强,一旦其中一个模块有问题, ...
 - (1).NET CORE微服务 Micro-Service ---- 什么是微服务架构,.netCore微服务选型
		
开发工具:VS2017 .Net Core 2.1 什么是微服务?单体结构: 缺点:1)只能采用同一种技术,很难用不同的语言或者语言不同版本开发不同模块:2)系统耦合性强,一旦其中一个模块有问题,整个 ...
 - 什么是微服务架构,.netCore微服务选型
		
什么是微服务架构,.netCore微服务选型 https://www.cnblogs.com/uglyman/p/9182485.html 开发工具:VS2017 .Net Core 2.1 什么是微 ...
 - 庐山真面目之八微服务架构 NetCore 基于 Dockerfile 文件部署
		
庐山真面目之八微服务架构 NetCore 基于 Dockerfile 文件部署 一.简介 从今天开始,不出意外的话,以后所写的文章中所介绍项目的部署环境都应该会迁移到Linux环境上,而且是 ...
 - 庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署
		
庐山真面目之九微服务架构 NetCore 基于 Docker 基础镜像和挂载文件部署 一.简介 我们在上一篇文章<庐山真面目之八微服务架构 NetCore 基于 Dockerfile ...
 - 腾讯开源微服务架构 Tars,高性能 RPC 开发框架
		
腾讯微服务架构 Tars 于今日正式开源. Tars 取名于电影“星际穿越”中的机器人,是支持多语言的高性能 RPC 开发框架和配套一体化的服务治理平台,可以帮助企业或者用户以微服务的方式快速构建稳定 ...
 - Java高并发高性能分布式框架从无到有微服务架构设计
		
微服务架构模式(Microservice Architect Pattern).近两年在服务的疯狂增长与云计算技术的进步,让微服务架构受到重点关注 微服务架构是一种架构模式,它提倡将单一应用程序划分成 ...
 
随机推荐
- 判断是否是ie浏览器或者edge浏览器,引入特定的css
			
判断是否是ie浏览器或者edge浏览器,引入特定的css 我本来要用ie浏览器专有的条件注释语句来引入,但是发现都没有效果,网上有说ie10之后的浏览器取消了条件语句,反正我是只要是IE都没有试出效果 ...
 - C# 线程参数
			
. class ThreadSample { private readonly int _iterations; public ThreadSample(int iterations) { _iter ...
 - promise 讲解
			
Promise的出现 解决了 js 回调地狱得问题 回调地狱图 Promise解决回调地狱 是不是美观多了.. 实例化Promise时传入方法里的两个参数 resolve(成功的回调)和reject ...
 - CodeForces - 1189  E.Count Pairs (数学)
			
You are given a prime number pp, nn integers a1,a2,…,ana1,a2,…,an, and an integer kk. Find the numbe ...
 - koa2--04.ejs模板引擎
			
首先在项目文件下使用cmd,输入:npm install --save koa-views ejs,将koa-views中间件和ejs模板引擎安装到文件中,并自动写入依赖 接在在index.js文件中 ...
 - 更新到@vue/cli 4.1.1版本的前端开发前的准备
			
一.概念简述 1.node.js目的是提供一个JS的运行环境. 2.npm(node package manager)是一个JS包管理器. 二.检查自己的电脑是否已安装相关配置 1.查看node.js ...
 - word HTML文件与Markdwon互相转换的几种方式
			
Tip:word文件与Markdwon转换往往是可逆的.无论使用哪种方式,要想完美转换,必须要预先处理掉markdown与word不兼容的格式,如word文件对象,带边框的代码块等等 方法一:借助pa ...
 - Google被墙怎么办?
			
Google被墙怎么办? 1 声明 请小伙伴们遵守法律法规,我们只是为了更好的查询学习资料. 想使用Google查询相关资料 想使用Google账号管理收藏夹 想使用Google商店安装软件 == 2 ...
 - 开源项目SMSS开发指南
			
SMSS是一个由我个人发起的开源项目,目的是建立一套轻量化,高可用,高安全和方便扩展的业务支撑框架.SMSS面向TCP/IP层开发,适合扩展上层业务接口.数据结构传输序列化通过Protobuf实现.传 ...
 - UI自动化和selenium相关以及八大定位
			
一.UI自动化相关 1. UI自动化的本质(重点) 定位元素→操作元素→模拟页面操作→断言→测试报告 2. 适合UI自动化的场景 UI自动化的前提条件 (1)需求不能频繁变动 (2)UI稳定(UI自动 ...