.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動詞

  1. 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官方提供的方法,采用工廠類+依賴注入方式:實例代碼如下:

  

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);
            }


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM