眾所周知,微服務架構是由一眾微服務組成,項目中調用其他微服務介面更是常見的操作。為了便於調用外部介面,我們的常用思路一般都是封裝一個外部介面的客戶端,使用時候直接調用相應的方法。webservice或WCF的做法就是引用服務,自動生成客戶端。在webapi2.0里,我們都會手動封裝一個靜態類。那麼在 ...
眾所周知,微服務架構是由一眾微服務組成,項目中調用其他微服務介面更是常見的操作。為了便於調用外部介面,我們的常用思路一般都是封裝一個外部介面的客戶端,使用時候直接調用相應的方法。webservice或WCF的做法就是引用服務,自動生成客戶端。在webapi2.0里,我們都會手動封裝一個靜態類。那麼在.netcore3.1的微服務時代,我們該如何處理這個問題呢? ----思路都是一樣的,封裝一個外部服務,並且使用依賴註入和 HttpFactory工廠等.netcore特有的方式提升性能。接下來我們一步一步說下詳細的步驟:
第1步:--創建項目
為了便於構建生成nuget包,我們一般都每個外部服務創建一個獨立的項目;如下圖:
在解決方案里,我們創建了一個項目名為:"JR.UPC.Template.OuterClient",(項目起名一般為 公司名.部門名.項目名.xxx)。
第2步:創建一個IServiceCollection擴展服務,便於將服務註冊信息。(重點)
public static class MsgApiClientServiceCollectionExtensions { public static IServiceCollection AddMsgApiClient(this IServiceCollection services, IConfiguration MsgClientConfiguration) { services.Configure<MsgClientConfiguration>(MsgClientConfiguration) .AddHttpClient<IMsgApiClient, MsgApiClient>() .ConfigureHttpClient(config=> { config.BaseAddress = new Uri(MsgClientConfiguration.GetSection("url").Value); config.Timeout = TimeSpan.FromSeconds(30); }); return services; } }
該段代碼雖然很短,但是最關鍵的部分:
代碼的執行過程如下:
(1) services首先註冊一個操作配置文件的實例 :
services.Configure<MsgClientConfiguration>(MsgClientConfiguration)
(2) 添加HttpClientFactory工廠並且關聯到services里,並將Client強制類型為IMsgApiClient(自定義的外部微服務介面名稱) :
.AddHttpClient<IMsgApiClient, MsgApiClient>();//IMsgApiClient為接下來要創建的客戶端
(3)設置HttpClient的相關配置參數:
.ConfigureHttpClient(config=> { config.BaseAddress = new Uri(MsgClientConfiguration.GetSection("url").Value);//外部微服務介面功能變數名稱 config.Timeout = TimeSpan.FromSeconds(30); // 介面調用超時時間 });
還有如下註意點:
(1)以靜態類和靜態方法方式;
(2) this IServiceCollection services ,這個參數我就不多解釋了。
(3) 一般我們都需要讀取appsettings.json配置文件里的參數所以這裡接收了一個參數---IConfiguration MsgClientConfiguration ;當然如果你不需要讀取配置參數,也可以忽略這個參數;
第3步:寫外部介面調用的具體邏輯代碼:
(1)創建一個介面,比如IMsgApiClient
/// <summary> /// 企業微信消息發送客戶端 /// </summary> public interface IMsgApiClient { /// <summary> /// 調用介面介面:向微信發送消息 /// </summary> /// <param name="hrcodeStr">hrcode,多個以|隔開</param> /// <param name="msg">消息內容</param> /// <returns></returns> Task<Result<string>> SendWxWorkMsgAsync(string hrcodeStr, string msg); }
(2)實現該介面:
/// <summary> /// 調用外部介面:向SD發送消息 /// </summary> /// <param name="hrcodeStr">hrcode,多個以|隔開</param> /// <param name="msg">消息內容</param> /// <returns></returns> public async Task<Result<string>> SendWxWorkMsgAsync(string hrcodeStr, string msg) { Result<string> result = new Result<string>(); string funName = "【調用外部介面:微信企業消息推送介面】"; try { //具體的業務邏輯---根據自身業務來寫 MsgApiResult<WeiXinWorkMessageResponse> sendRet = await UPCHttpClientExtensions.PostData<MsgApiResult<WeiXinWorkMessageResponse>>(_client, _logger, "/api/weixin/work/messages/send", msgReq); if (sendRet != null) { _logger.LogInformation($"{funName},{hrcodeStr}推送消息成功"); result.state = true; result.data = sendRet.Return_data.MessageId; } else { result.state = false; result.error_msg = sendRet.Return_msg; _logger.LogError($"{funName}:{hrcodeStr}推送消息失敗:{sendRet.Return_msg}"); } } catch (Exception ex) { result.state = false; result.error_code = ErrorCode.OuterApiError; result.error_msg = funName + "調用外部介面異常:。" + ex.Message; _logger.LogError(ex, $"{funName}向{hrcodeStr}推送消息處理異常:{ex.Message}"); } return result; }
針對HttpClient的Post方法我特意封裝了一個通用方法,如下:(可以根據自身項目自行改造)
/// <summary> /// HttpClient擴展方法 /// </summary> public class UPCHttpClientExtensions { /// <summary> /// httpclient-post方法的簡單處理,封裝成一個方法,便於調用 /// </summary> /// <typeparam name="T"></typeparam> /// <param name="_client"></param> /// <param name="_logger"></param> /// <param name="actionUrl">http://xxx.com/後面之後的地址</param> /// <param name="param">一個對象</param> /// <param name="ContentType"></param> /// <param name="BearerToken"></param> /// <returns></returns> public async static Task<T> PostData<T>(HttpClient _client, ILogger _logger,string actionUrl, dynamic param, string ContentType = "application/json", string BearerToken = "") { string funName = "PostData"; string paramStr = JsonConvert.SerializeObject(param); string jrclientguid = Guid.NewGuid().ToString("n"); try { _logger.LogInformation($"{funName}開始,url={_client.BaseAddress},action={actionUrl},postData={paramStr} ,jrclientguid={jrclientguid}---------"); HttpResponseMessage response; using (HttpContent httpContent = new StringContent(paramStr, Encoding.UTF8)) { if (!string.IsNullOrWhiteSpace(BearerToken)) { _client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", BearerToken); } httpContent.Headers.ContentType = new System.Net.Http.Headers.MediaTypeHeaderValue(ContentType); response = await _client.PostAsync(actionUrl, httpContent); } if (response != null && response.IsSuccessStatusCode) { Type t = typeof(T); if (typeof(T) == typeof(string)) { string respStr = await response.Content.ReadAsStringAsync(); return (T)Convert.ChangeType(respStr, typeof(T)); } else { string respStr = response.Content.ReadAsStringAsync().Result; T resp = JsonConvert.DeserializeObject<T>(respStr); return resp; } } else { return default(T); } } catch (Exception ex) { _logger.LogError(ex,$"{funName}報錯啦,url={_client.BaseAddress},action={actionUrl},postData={paramStr} ,jrclientguid={jrclientguid}---,ex={ex.Message}" ); throw; } finally { _logger.LogInformation($"{funName}結束,url={_client.BaseAddress},action={actionUrl},postData={paramStr} ,jrclientguid={jrclientguid}---------"); } } }
第4步:將服務註入容器:代碼在Startup類的ConfigureServices方法里:
services.AddUpcMVC(Env)
.AddUPCDbContext(Configuration)
.AddMsgApiClient(Configuration.GetSection(nameof(MsgApiClient)))//就是這行
第5步:使用:在Controller或其他地方都可以使用;
(1)首先在構造函數里註冊:
private readonly ILogger _logger; private IMsgApiClient _IMsgApiClient; public HealthController(ILogger<HealthController> logger, IMsgApiClient iMsgApiClient) { _logger = logger; _IMsgApiClient = iMsgApiClient; }
(2)調用方法:
/// <summary> /// 發消息 /// </summary> /// <returns></returns> [HttpGet] public async Task<Result<string>> POk() { Result<string> result = new Result<string>(); result = await _IMsgApiClient.SendWxWorkMsgAsync("100001002", "我是沐雪,請明天來我辦公室一趟!"); //result.state = true; //result.data = "連接成功"; return result; }