.net core實踐系列之短信服務-Sikiro.SMS.Api服務的實現 .net core實踐系列之短信服務-架構設計

前言

上篇《.net core實踐系列之短信服務-架構設計》介紹了我對短信服務的架構設計,同時針對場景解析了個人設計理念。本篇繼續講解Api服務的實現過程。html

源碼地址:https://github.com/SkyChenSky/Sikiro.SMSgit

此服務會使用.NET Core WebApi進行搭建,.NET Core WebApi基礎原型就是RESTful風格,然而什麼叫RESTful呢。github

REST API簡介

REST

Representational State Transfer的縮寫,翻譯爲「表現層狀態轉化」,是由Roy Thomas Fieding在他的博士論文《Architectural Styles and the Design of Network-based Software Architectures》中提出的一種架構思想。sql

而他的論文中提出了一個RESTful應用應該具有的幾點約束。mongodb

  • 每一個資源都應該有一個惟一的標識
    • 每個對象或資源均可以經過一個惟一的URI進行尋址,URI的結構應該是簡單的。
  • 使用標準的方法來更改資源的狀態
    • GET、POST、PUT、PATCH、DELETE
  • Request和Response的自描述
  • 資源多重表述
    • URI所訪問的每一個資源均可以使用不一樣的形式加以表示(XML或JSON)
  • 無狀態的服務
    • 不須要保存會話狀態(SESSION),資源自己就是自然的狀態,是須要被保存的。

RESTful

當某Web服務遵照了REST這些約束條件和原則,那麼咱們能夠稱它設計風格就是 RESTful。數據庫

三特色

REST有三大特色:windows

  • 資源(名詞)
  • 動做(動詞)
  • 表述(超文本)

資源

抽象的說他能夠是音頻、也能夠是視頻,更能夠是訂單。更俗講其實就是實體,更接近咱們日常說的「類(class)」。另外REST強調資源有惟一的URI。下面有對比api

動做

主要動做:服務器

  •   GET:檢索單個資源;
  •   POST:主要是建立資源,可是GET的參數長度受限,所以也能夠用在複雜參數的檢索資源場景;
  •   PUT:更新資源全部屬性,也能夠稱爲替換資源;
  •   PATCH:更新資源部分屬性;
  •   DELETE:刪除資源;

表述

對於Request與Response的自描述,而表述方式有多種:XML、JSON等,強調HTTP通訊的語義可見性。架構

對比

RPC

SMSApi.com/api/GetSMS

SMSApi.com/api/CreateSMS

傳統的接口設計面向過程的,每一個動做有特定的URI。

REST

SMSApi.com/api/SMS  GET

SMSApi.com/api/SMS  POST

REST API每一個資源只有惟一的URI,而資源能夠有不一樣的動做執行相應的接口

 

RPC的更加傾向於面向過程,而RESTful則以面向對象的思想進行設計。

接口定義

回到咱們的短信服務,以上面的三特色進行出發,SMS不須要由外部服務進行刪除、修改資源所以:

資源:SMS

動做:GET、POST

表述方式:咱們約定Request、Response爲JSON格式

 /// <summary>
    /// 短信接口
    /// </summary>
    [Route("api/[controller]")]
    [ApiController]
    public class SmsController : ControllerBase
    {
        private readonly SmsService _smsService;
        private readonly IBus _bus;

        public SmsController(SmsService smsService, IBus bus)
        {
            _smsService = smsService;
            _bus = bus;
        }

        /// <summary>
        /// 獲取一條短信記錄
        /// </summary>
        /// <param name="id">主鍵</param>
        /// <returns></returns>
        [HttpGet("{id}")]
        public ActionResult<SmsModel> Get(string id)
        {
            if (string.IsNullOrEmpty(id))
                return NotFound();

            var smsService = _smsService.Get(id);
            return smsService.Sms;
        }

        /// <summary>
        /// 發送短信
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost]
        public ActionResult Post([FromBody] List<PostModel> model)
        {
            _smsService.Add(model.MapTo<List<PostModel>, List<AddSmsModel>>());

            _smsService.SmsList.Where(a => a.TimeSendDateTime == null)
                .ToList().MapTo<List<SmsModel>, List<SmsQueueModel>>().ForEach(item =>
                {
                    _bus.Publish(item);
                });

            return Ok();
        }

        /// <summary>
        /// 查詢短信記錄
        /// </summary>
        /// <param name="model"></param>
        /// <returns></returns>
        [HttpPost("_search")]
        public ActionResult<List<SmsModel>> Post([FromBody] SearchModel model)
        {
            _smsService.Search(model.MapTo<SearchModel, SearchSmsModel>());

            return _smsService.SmsList;
        }
    }

功能描述

由上可見一共定義了三個接口

  • GET   http://localhost:port/api/sms/id 獲取一條短信記錄
  • POST http://localhost:port/api/sms 發送短信
  • POST http://localhost:port/api/sms/_search 查詢短信記錄

獲取一條短信記錄就很少解析了

查詢短信記錄

動做我使用了POST,有人會問檢索資源不是用GET麼?對,可是GET的參數在URL裏是受限的,所以在複雜參數的場景下應該選擇POST,然而我是模仿elasticsearch的複雜查詢時定義,添加多一個節點/_search申明此URI是作查詢的。

發送短信

此接口的實現邏輯主要兩件事,持久化到MongoDB,過濾出及時發送的短信記錄發送到RabbitMQ。

在持久化以前我作了一個分頁的動做,咱們提供出去的接口,同一條短信內容支持N個手機號,可是不一樣的短信運營商的所支持一次性發送的手機數量是有限的。

開始實現時,我把分頁發送寫到隊列消費服務的發送短信邏輯裏,可是這裏有個問題,若是分頁後部分發送成功,部分發送失敗,那麼這個聚合究竟以失敗仍是成功的狀態標示呢?換句話來講咱們沒法保證聚合內的數據一致性。

所以個人作法就是優先在分頁成多個文檔存儲,那麼就能夠避免從數據庫取出後分頁致使部分紅功、失敗。

public void Add(List<AddSmsModel> smsModels)
        {
            DateTime now = DateTime.Now;

            var smsModel = new List<SmsModel>();
            foreach (var sms in smsModels)
            {
                var maxCount = _smsFactory.Create(sms.Type).MaxCount;
                sms.Mobiles = sms.Mobiles.Distinct().ToList();
                var page = GetPageCount(sms.Mobiles.Count, maxCount);

                var index = 0;
                do
                {
                    var toBeSendPhones = sms.Mobiles.Skip(index * maxCount).Take(maxCount).ToList();
                    smsModel.Add(new SmsModel
                    {
                        Content = sms.Content,
                        CreateDateTime = now,
                        Mobiles = toBeSendPhones,
                        TimeSendDateTime = sms.TimeSendDateTime,
                        Type = sms.Type
                    });
                    index++;
                } while (index < page);
            }

            SmsList = smsModel;

            _mongoProxy.BatchAddAsync(SmsList);
        }

項目相關開源框架

EasyNetQ

EasyNetQ.DI.Microsoft

Sikiro.Nosql.Mongo

log4net

Mapster

EasyNetQ

這個開源框架是針對RabbitMQ.Client的封裝,隱藏了不少實現細節,簡化使用方式。並提供了多種IOC注入方式

源碼地址:https://github.com/EasyNetQ/EasyNetQ

Sikiro.Nosql.Mongo

這個是我本身針對mongo驅動的經常使用的基礎操做的封裝庫

源碼地址:https://github.com/SkyChenSky/Sikiro.Nosql.Mongo

Mapster

實體映射框架,看評測數據比AutoMapper等之類的效率要高,並且易用性也很是高。

https://github.com/MapsterMapper/Mapster

全局異常日誌記錄

public class GolbalExceptionAttribute : ExceptionFilterAttribute
    {
        public override void OnException(ExceptionContext context)
        {
            if (!context.ExceptionHandled)
            {
                context.Exception.WriteToFile();
            }

            base.OnException(context);
        }
    }

 public void ConfigureServices(IServiceCollection services)
        {
            services.AddMvc(option =>
                {
                    option.Filters.Add<GolbalExceptionAttribute>();
                })
                .SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
        }

LoggerHelper

上面的WriteToFile是我對Exception的擴展方法,使用了Log4Net日誌框架對異常進行記錄,若是有須要也能夠寫到mongodb或者elasticsearch

/// <summary>
    /// 日誌幫助類
    /// </summary>
    public static class LoggerHelper
    {
        private static readonly ILoggerRepository Repository = LogManager.CreateRepository("NETCoreRepository");
        public static readonly ILog Log = LogManager.GetLogger(Repository.Name, typeof(LoggerHelper));

        static LoggerHelper()
        {
            XmlConfigurator.Configure(Repository, new FileInfo("log4net.config"));
        }

        #region 文本日誌

        /// <summary>
        /// 文本日誌
        /// </summary>
        /// <param name="message"></param>
        /// <param name="ex"></param>
        public static void WriteToFile(this Exception ex, string message = null)
        {
            if (string.IsNullOrEmpty(message))
                message = ex.Message;

            Log.Error(message, ex);
        }
        #endregion
    }

工具庫的封裝

框架與工具庫都是以庫的形式提供咱們使用,並且都是可複用,可是他們區別在於:工具庫開箱即用,大多數以靜態方法提供調用,只調用少許甚至一個方法則完成使用。

而框架定義,爲了實現某個軟件組件規範時,提供規範所要求之基礎功能的軟件產品,而他具備約束性、可複用性、規範性。他是一個半成品,可重寫。

所以爲了簡化框架的使用,對經常使用設置、構建組合進行封裝,以一個擴展類或者幫助類的形式提供,簡化使用、增長可讀性。

Swagger的使用

Http協議的好處是輕量、跨平臺,如此良好的靈活性然而須要接口描述對外暴露。Swagger是一個很好的選擇,不須要本身手寫文檔並提供後臺管理界面,還能夠測試,簡化很多工做。

我選擇了NSwag.AspNetCore開源組件,他的使用很是簡單。只須要兩步:

1.配置Swagger:

public void Configure(IApplicationBuilder app, IHostingEnvironment env)
        {
            if (env.IsDevelopment())
            {
                app.UseDeveloperExceptionPage();
            }

            app.UseSwaggerUiWithApiExplorer(settings =>
           {
               settings.GeneratorSettings.DefaultPropertyNameHandling =
                   PropertyNameHandling.CamelCase;
               settings.PostProcess = document =>
               {
                   document.Info.Version = "v1";
                   document.Info.Title = "Sikiro.SMS.API";
                   document.Info.Description = "短信服務API";
                   document.Info.TermsOfService = "None";
               };
           });
            app.UseMvc();
        }

2.設置站點項目

此設置爲了把接口、參數註釋顯示到Swagger頁面

NSwag還有多個版本的UI選擇:

  • UseSwaggerReDoc
  • UseSwaggerUi
  • UseSwaggerUi3

訪問http://localhost:port/swagger就能夠見到API文檔了

部署

 由於我公司仍是使用windows server 2008。所以部署前應準備環境安裝包:

.NET Core 2.1.3 windows-hosting

安裝完成後重啓服務器,再把文件發佈到服務器,編輯應用程序池爲無託管代碼。就能夠訪問了

 

結尾

本篇介紹Sikiro.SMS.Api的設計與實現,下篇會針對API調用進行封裝SDK。若是有任何建議,請在下方評論反饋給我。

相關文章
相關標籤/搜索