概述
Magicodes.Wx.Sdk致力于打造最简洁最易于使用的微信Sdk,逐步包括公众号Sdk、小程序Sdk、企业微信Sdk等,以及Abp VNext集成。
本篇将侧重于讲述如何向Magicodes.Wx.Sdk进行贡献。
WebApiClientCore
Magicodes.Wx.Sdk之简洁很大层面依托于NCC的开源库WebApiClientCore。Magicodes.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); }
如上述代码所示,有几个注意事项:
需要定义接口
继承自IWxApiWithAccessTokenFilter接口将自动在接口请求启用AccessTokenApiFilter筛选器,即会自动会在接口请求时带上access_token。
HttpHost用于定义接口跟地址
HttpPost用于设置接口请求方法,常用特性有:
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(); } }