.netcore 3.1 high-performance micro Services Architecture: webapi specification

1.1 Definitions

1, the interface basis: Single Responsibility Principle, each interface is only responsible for their own business, then under the db, versatility.

2, the polymerization Interface: The interface data based polymerization caller needs, business and strong.

1.2 protocol

1. The client process backend services via the API communication, should use HTTPS (production) protocol

2. The unified data format for JSON server response

1.3 domain host

prd environment: https: //xxx-xxx-api.example.com/

uat environment: https: //xxx-xxx-api-uat.example.com/

test environment: https: //xxx-xxx-api-test.example.com/

dev environment: https: //xxx-xxx-api-dev.example.com/

 

The api subdomain in place, this approach can be flexible on some scale.

1.4 path path

path name should be based on resource-oriented name, the operation of resources is determined by HttpMethod (get, post, put, delete). So, in general words on the url should be a noun, verb must not be. Generally follow the following conventions:

(1) name of the URL must be all lowercase;
(2) must be readable URL URL;
(3) must not be exposed server architecture

(4) appear underlined words used in the composite partition, for example: animal_types

few positive examples:

 

New users: http: // / user post method localhost submitted;

Modify the user: http: // / users put submitted localhost method;

Delete the article: http: // localhost / articles author = 1 & category = 2 delete method to submit;?

Query User: http: // / users get method localhost submitted;

Queries article: http: // localhost / articles author = 1 & category = 2get method submitted;?

Bad example as follows:

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 verbs

  1. RESTful core idea is that the client data manipulation instruction is issued "Verb + object" structure, that is, four kinds of verb is HTTP method, the corresponding CRUD operations:

 

GET (SELECT): Remove the resource from the server (one or more).

POST (CREATE): a new resource on the server.

PUT (UPDATE): update the resource (after the complete resources provided by the client to change) in the server.

PATCH (UPDATE): Updating Resource (provided by the client to change the properties) in the server.

DELETE (DELETE): Delete the resource from the server.

 

among them

 (1) delete the resource must DELETE method

 (2) create a new resource must use the POST method

 (3) updates the resource should use the PUT method

 (4) obtain information resources must use the GET method

 

For each path, the following combinations are listed all possible endpoints and HTTP verbs

 

 

Requester

URL

description

 

law

 

 

 

 

 

 

 

 

GET

/zoos

List all zoos (ID and name, do not be too detailed)

 

 

 

 

 

POST

/zoos

Add a new zoo

 

 

 

 

 

GET

/ Zoos / {} zoo

Gets the zoo Details

 

 

 

 

 

PUT

/ Zoos / {} zoo

Updates the specified Zoo (whole object)

 

 

 

 

 

PATCH

/ Zoos / {} zoo

Update Zoo (part of the object)

 

 

 

 

 

DELETE

/ Zoos / {} zoo

Delete the specified Zoo

 

 

 

 

 

GET

/zoos/{zoo}/animals

Animal list (ID and retrieve the specified name in the zoo, not too detailed

 

fine)

 

 

 

 

 

 

 

 

GET

/animals

List all animals (ID and name).

 

 

 

 

 

POST

/animals

Add a new animal

 

 

 

 

 

GET

/animals/{animal}

Animals get the details specified

 

 

 

 

 

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官方提供的方法,采用工厂类+依赖注入方式:实例代码如下:

  

1、SartUp类里添加代码-- httpclient初始化:
   services.AddHttpClient("MsgApi", c =>
            {
                c.BaseAddress = new Uri(Configuration["OuterApi:MsgApi:url"]);
                c.Timeout = TimeSpan.FromSeconds(30);
            });

//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 ? 0 : context.HttpContext.Request.ContentLength.Value;
            if (contentLen > 0)
            {
                // 读取请求体中所有内容
                System.IO.Stream stream = context.HttpContext.Request.Body;
                if (context.HttpContext.Request.Method == "POST")
                {
                    stream.Position = 0;
                }
                byte[] buffer = new byte[contentLen];
                stream.Read(buffer, 0, 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);
            }

Guess you like

Origin www.cnblogs.com/puzi0315/p/12240442.html