如何快速編寫一個微信Api?

雪雁發表於2021-04-08

概述

Magicodes.Wx.Sdk致力於打造最簡潔最易於使用的微信Sdk,逐步包括公眾號Sdk、小程式Sdk、企業微信Sdk等,以及Abp VNext整合。

本篇將側重於講述如何向Magicodes.Wx.Sdk進行貢獻。

WebApiClientCore

Magicodes.Wx.Sdk之簡潔很大層面依託於NCC的開源庫WebApiClientCoreMagicodes.Wx.Sdk依託WebApiClientCore完成了微信介面的包裝和校驗。

開源庫地址:https://github.com/dotnetcore/WebApiClient

快速開始

這裡我們以【客服訊息】【新增客服賬號】為例進行講解,官方介面文件地址為:https://developers.weixin.qq.com/doc/offiaccount/Customer_Service/Customer_Service_Management.html#2。

比如新增客服賬號介面官方介面文件說明如下所示:

新增客服賬號

主體步驟如下:

1)新增介面IKfAccountApi

參考程式碼如下所示:

/// <summary>
/// 客服管理
/// </summary>
[HttpHost("https://api.weixin.qq.com/customservice/kfaccount/")]
public interface IKfAccountApi : IWxApiWithAccessTokenFilter
{
    /// <summary>
    /// 新增客服賬號
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost("add")]
    Task<ApiResultBase> AddAsync(AddOrUpdateKfAccountInput input);

    /// <summary>
    /// 設定客服資訊
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost("update")]
    Task<ApiResultBase> UpdateAsync(AddOrUpdateKfAccountInput input);

    /// <summary>
    /// 刪除客服賬號
    /// </summary>
    /// <param name="input"></param>
    /// <returns></returns>
    [HttpPost("del")]
    Task<ApiResultBase> DelAsync(DelKfAccountInput input);
}

如上述程式碼所示,有幾個注意事項:

  1. 需要定義介面

  2. 繼承自IWxApiWithAccessTokenFilter介面將自動在介面請求啟用AccessTokenApiFilter篩選器,即會自動會在介面請求時帶上access_token。

  3. HttpHost用於定義介面跟地址

  4. HttpPost用於設定介面請求方法,常用特性有:

    特性名稱功能描述備註
    HttpHostAttribute 請求服務http絕對完整主機域名 優先順序比Options配置低
    HttpGetAttribute 宣告Get請求方法與路徑 支援null、絕對或相對路徑
    HttpPostAttribute 宣告Post請求方法與路徑 支援null、絕對或相對路徑
    HttpPutAttribute 宣告Put請求方法與路徑 支援null、絕對或相對路徑
    HttpDeleteAttribute 宣告Delete請求方法與路徑 支援null、絕對或相對路徑
    HeaderAttribute 宣告請求頭 常量值
    TimeoutAttribute 宣告超時時間 常量值
    FormFieldAttribute 宣告Form表單欄位與值 常量鍵和值
    FormDataTextAttribute 宣告FormData表單欄位與值 常量鍵和值

2)新增Dto

這一步是非必要的,需要看引數的具體內容和要求。新增客服介面的輸入引數程式碼參考如下:

 public class AddOrUpdateKfAccountInput
    {
        /// <summary>
        /// 完整客服帳號,格式為:帳號字首@公眾號微訊號,帳號字首最多10個字元,必須是英文、數字字元或者下劃線,字尾為公眾號微訊號,長度不超過30個字元
        /// </summary>
        [JsonProperty("kf_account")]
        [StringLength(30, MinimumLength = 3)]
        [Required]
        public string Account { get; set; }

        /// <summary>
        /// 客服暱稱,最長16個字
        /// </summary>
        [JsonProperty("nickname")]
        [StringLength(16, MinimumLength = 1)]
        public string Nickname { get; set; }
    }

Dto實體的新增這裡給大家分享一個小技巧。當實體欄位以及層級比較多時,大家可以使用VS的【編輯】==》【選擇性貼上】==》【將Json貼上為類】:

選擇性貼上

3)新增ApiResultBase

框架中封裝了預設的返回結果基類,如果沒有其他額外的返回內容,僅需返回ApiResultBase即可。如果有額外的範圍內容,則需要定義子類繼承自ApiResultBase,在可能的情況下,有可能需要重寫部分方法(比如IsSuccess)。如下述程式碼:

public class TokenApiResult : ApiResultBase
{
    /// <summary>
    ///     獲取到的憑證
    /// </summary>
    [JsonProperty("access_token")]
    public string AccessToken { get; set; }

    /// <summary>
    ///     憑證有效時間,單位:秒
    /// </summary>
    [JsonProperty("expires_in")]
    internal int Expires { get; set; }

    /// <summary>
    ///     憑證過期時間
    /// </summary>
    public DateTime ExpiresTime { get; set; }
}

至此,一個介面就編寫完成了。只需要定義interface和Dto就可以了。是不是非常簡單?

4)單元測試編寫

/// <summary>
/// 模板訊息單元測試
/// </summary>
public class TemplateApiTest : TestBase, IClassFixture<TestWebApplicationFactory>
{
    private readonly ITemplateApi templateApi;
    public TemplateApiTest(TestWebApplicationFactory webApplicationFactory, ITestOutputHelper output) : base(webApplicationFactory, output)
    {
        //通過父類的GetRequiredService獲取到Api
        templateApi = GetRequiredService<ITemplateApi>();
    }

    /// <summary>
    /// 模板訊息傳送測試
    /// </summary>
    /// <returns></returns>
    [Fact]
    public async Task SendAsync_Test()
    {
        var result = await templateApi.SendAsync(new SendTemplateMessageInput()
        {
            To = "oXELNwzZgamuLS0JrJhVgdelzKyw",
            TemplateId = "riid7aad8OKRQD9Ey6dclWBBkrqZSFDhlpKh0_spGLA",
            Data = new System.Collections.Generic.Dictionary<string, TemplateDataItem>()
            {
                {"first",new TemplateDataItem("測試") },
                {"keyword1",new TemplateDataItem("雪雁") },
                {"keyword2",new TemplateDataItem("2021.2.5") },
                {"remark",new TemplateDataItem("備註") },
            }
        });
        //判斷Api是否呼叫成功,未成功將丟擲異常WxSdkException
        result.EnsureSuccess();
    }
}

整體參考:

https://github.com/xin-lai/Magicodes.Wx.Sdk/pull/1/commits/85263ed9a807581f7315a90fe6e00c51c994d386

其他須知內容

IWxApiWithAccessTokenFilter介面

IWxApiWithAccessTokenFilter介面用於定義需要攜帶公眾號Acces sToken的介面。

如下述參考程式碼所示,其啟用了AccessTokenApiFilter篩選器,以及關閉了AcceptContentType的匹配約束(公眾號的介面官方寫的一塌糊塗,很不規範)。

參考程式碼:

/// <summary>
/// 
/// </summary>
[JsonNetReturn(EnsureMatchAcceptContentType = false)]
[AccessTokenApiFilter]
//[LoggingFilter]
public interface IWxApiWithAccessTokenFilter
{
}

AccessTokenApiFilter篩選器

AccessTokenApiFilter介面篩選器會在啟用的API、Action上自動新增access_token引數值。

參考程式碼如下所示:

public class AccessTokenApiFilter : ApiFilterAttribute
{
    public override async Task OnRequestAsync(ApiRequestContext context)
    {
        ITokenManager tokenManager = context.HttpContext.ServiceProvider.GetRequiredService<ITokenManager>();
        string accessToken = await tokenManager.GetAccessTokenAsync();
        context.HttpContext.RequestMessage.AddUrlQuery("access_token", accessToken);
    }

    public override Task OnResponseAsync(ApiResponseContext context)
    {
        return Task.CompletedTask;
    }
}

關於ApiResultBase

ApiResultBase定義了公眾號API返回基類,用於接收錯誤碼、錯誤訊息、是否執行成功的判斷以及獲取友好訊息提示。

參考程式碼:

/// <summary>
///     API請求結果
///     {"errcode":40164,"errmsg":"invalid ip 218.76.8.29 ipv6 ::ffff:218.76.8.29, not in whitelist rid: 60122c35-705c3134-51b45a3d"}
/// </summary>
public class ApiResultBase
{
    /// <summary>
    ///     返回碼
    /// </summary>
    [JsonProperty("errcode")]
    public virtual ReturnCodes ReturnCode { get; set; }

    /// <summary>
    ///     錯誤訊息
    /// </summary>
    [JsonProperty("errmsg")]
    public virtual string Message { get; set; }

    /// <summary>
    ///     是否為成功返回
    /// </summary>
    /// <returns></returns>
    public virtual bool IsSuccess()
    {
        return ReturnCode == ReturnCodes.請求成功;
    }

    /// <summary>
    ///     獲取友好提示
    /// </summary>
    /// <returns></returns>
    public virtual string GetFriendlyMessage()
    {
        return ReturnCode.ToString();
    }
}
 

相關文章