Pārlūkot izejas kodu

修改短信推送类型

tangjiang 10 mēneši atpakaļ
vecāks
revīzija
ff7e81e924

+ 10 - 4
src/Push.Share/Enums/EPushStatus.cs

@@ -7,21 +7,27 @@ namespace Push.Share.Enums;
 /// </summary>
 public enum EPushStatus
 {
+    /// <summary>
+    /// 
+    /// </summary>
+    [Description("待推送")]
+    Waiting = 0,
+
     /// <summary>
     /// 
     /// </summary>
     [Description("推送中")]
-    Pushing = 0,
+    Pushing = 1,
 
     /// <summary>
     /// 
     /// </summary>
     [Description("推送成功")]
-    Success = 1,
+    Success = 2,
 
     /// <summary>
     /// 
     /// </summary>
     [Description("推送失败")]
-    Failed = 2
-}
+    Failed = 3
+}

+ 14 - 0
src/yibin/Push.YiBin.Host/Controllers/PushMessageController.cs

@@ -3,6 +3,7 @@ using Microsoft.AspNetCore.Authorization;
 using Microsoft.AspNetCore.Mvc;
 using Push.Share.Dtos;
 using Push.Share.Dtos.FWMessage;
+using Push.YiBin.Dtos;
 using XF.Domain.Filters;
 using XF.Domain.Repository;
 
@@ -31,6 +32,19 @@ namespace Push.YiBin.Host.Controllers
 
         #endregion
 
+        /// <summary>
+        /// 新增代发短信
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <returns></returns>
+        [HttpPost("addwaitmsg")]
+        [AllowAnonymous]
+        public async Task<Reponse> AddWaitMessage([FromBody] WaitMessageDto dto)
+        {
+            return await _pushDomainService.AddWaitMessage(_mapper.Map<WaitMessage>(dto), HttpContext.RequestAborted);
+        }
+
+
         #region 新增
 
         /// <summary>

+ 1 - 0
src/yibin/Push.YiBin.Host/Push.YiBin.Host.csproj

@@ -8,6 +8,7 @@
 
   <ItemGroup>
     <PackageReference Include="Mapster.DependencyInjection" Version="1.0.1" />
+    <PackageReference Include="Quartz.AspNetCore" Version="3.9.0" />
     <PackageReference Include="Serilog.Sinks.Grafana.Loki" Version="8.1.0" />
     <PackageReference Include="Serilog.Sinks.MongoDB" Version="5.3.1" />
     <PackageReference Include="Swashbuckle.AspNetCore" Version="6.5.0" />

+ 4 - 0
src/yibin/Push.YiBin.Host/StartupExtensions.cs

@@ -26,6 +26,8 @@ internal static class StartupExtensions
 
         services.AddHttpClient();
 
+        services.Configure<ChannelConfiguration>(d => configuration.GetSection(nameof(ChannelConfiguration)).Bind(d));
+
         // services.Configure<ChannelConfiguration>(d => configuration.GetSection(nameof(ChannelConfiguration)).Bind(d));
 
         // Add services to the container.
@@ -80,6 +82,8 @@ internal static class StartupExtensions
         //mq
         services.AddMq(configuration);
 
+        services.RegisterJob();
+
         return builder.Build();
     }
 

+ 34 - 0
src/yibin/Push.YiBin.Host/StartupHelper.cs

@@ -3,6 +3,8 @@ using MapsterMapper;
 using Microsoft.OpenApi.Models;
 using XF.Domain.Entities;
 using XF.Domain.Repository;
+using Quartz;
+using Quartz.AspNetCore;
 namespace Push.YiBin.Host
 {
     public static class StartupHelper
@@ -112,6 +114,38 @@ namespace Push.YiBin.Host
             return services;
         }
 
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="services"></param>
+        /// <returns></returns>
+        public static IServiceCollection RegisterJob(this IServiceCollection services)
+        {
+            services.AddQuartz(d =>
+            {
+                d.SchedulerId = "scheduler1";
+                d.InterruptJobsOnShutdown = true;
+                d.InterruptJobsOnShutdownWithWait = true;
+                d.MaxBatchSize = 3;
 
+                //服务工单受理  
+                var taskSendMessageJobKey = new JobKey("task-send-message-job", "task send message job");
+                d.AddJob<TaskSendMessageJob>(taskSendMessageJobKey);
+                d.AddTrigger(t => t
+                    .WithIdentity("task-send-message-trigger")
+                    .ForJob(taskSendMessageJobKey)
+                    .StartNow()
+                    .WithCronSchedule("0/5 * * * * ? ")
+                );
+            });
+
+            services.AddQuartzServer(d =>
+            {
+                d.WaitForJobsToComplete = true;
+                d.StartDelay = TimeSpan.FromSeconds(5);
+            });
+
+            return services;
+        }
     }
 }

+ 6 - 1
src/yibin/Push.YiBin.Host/config/appsettings.Development.json

@@ -29,7 +29,7 @@
     "Origins": [ "http://localhost:8888", "http://admin.hotline.fw.com", "http://hotline.fw.com" ]
   },
   "DatabaseConfiguration": {
-    "ApplyDbMigrations": false,
+    "ApplyDbMigrations": true,
     "ApplySeed": false
   },
   "MqConfiguration": {
@@ -41,5 +41,10 @@
       "HostName": "110.188.24.182",
       "VirtualHost": "fwt-master"
     }
+  },
+  //系统配置
+  "ChannelConfiguration": {
+    "ProcessingServices": "Services1", //服务名称
+    "HotlineAddressUrl": "http://localhost:50100/" //推送地址
   }
 }

+ 6 - 0
src/yibin/Push.YiBin.Host/config/appsettings.json

@@ -41,5 +41,11 @@
       "HostName": "110.188.24.182",
       "VirtualHost": "fwt-master"
     }
+  },
+
+  //系统配置
+  "ChannelConfiguration": {
+    "ProcessingServices": "Services1", //服务名称
+    "HotlineAddressUrl": "http://localhost:50100/" //推送地址
   }
 }

+ 47 - 0
src/yibin/Push.YiBin/BaseHttpInvoker.cs

@@ -0,0 +1,47 @@
+using Microsoft.AspNetCore.Http;
+using System.Net.Http.Headers;
+using System.Text;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+
+namespace Push.YiBin
+{
+    public class BaseHttpInvoker : IHttpInvoker, IScopeDependency
+    {
+        private readonly IHttpClientFactory _httpClientFactory;
+
+        public BaseHttpInvoker(IHttpClientFactory httpClientFactory)
+        {
+            _httpClientFactory = httpClientFactory;
+        }
+
+        public async Task<TResponse?> RequestStringContentAsync<TResponse>(string url, string httpMethod, string? stringContent = null,
+     Action<HttpClient>? setHttpClient = null, CancellationToken cancellationToken = default)
+        {
+            var httpClient = _httpClientFactory.CreateClient();
+
+            if (setHttpClient != null)
+                setHttpClient.Invoke(httpClient);
+
+            var rsp = string.Empty;
+            if (HttpMethods.IsGet(httpMethod))
+            {
+                rsp = await httpClient.GetStringAsync(url, cancellationToken);
+            }
+            else if (HttpMethods.IsPost(httpMethod))
+            {
+                using var responseMessage = await httpClient.PostAsync(url, new StringContent(stringContent, Encoding.UTF8, new MediaTypeWithQualityHeaderValue("application/json")),
+                    cancellationToken);
+                responseMessage.EnsureSuccessStatusCode();
+                using var responseContent = responseMessage.Content;
+                rsp = await responseContent.ReadAsStringAsync(cancellationToken);
+            }
+            else
+            {
+                throw new UserFriendlyException("暂不支持该请求方式");
+            }
+
+            return System.Text.Json.JsonSerializer.Deserialize<TResponse>(rsp);
+        }
+    }
+}

+ 2 - 0
src/yibin/Push.YiBin/BaseRepository.cs

@@ -162,6 +162,8 @@ namespace Push.YiBin
         }
 
         public IUpdateable<TEntity> Updateable() => Db.Updateable<TEntity>();
+        public IUpdateable<TEntity> Updateable(TEntity entity) => Db.Updateable(entity);
+        public IUpdateable<TEntity> Updateable(List<TEntity> entities) => Db.Updateable(entities);
 
         public IDeleteable<TEntity> Removeable() => Db.Deleteable<TEntity>();
 

+ 15 - 0
src/yibin/Push.YiBin/ChannelConfiguration.cs

@@ -0,0 +1,15 @@
+namespace Push.YiBin
+{
+    public class ChannelConfiguration
+    {
+        /// <summary>
+        /// 服务名称
+        /// </summary>
+        public string ProcessingServices { get; set; }
+
+        /// <summary>
+        /// 短信推送地址
+        /// </summary>
+        public string HotlineAddressUrl { get; set; }
+    }
+}

+ 37 - 0
src/yibin/Push.YiBin/ChannelConfigurationManager.cs

@@ -0,0 +1,37 @@
+using Microsoft.Extensions.Options;
+using XF.Domain.Dependency;
+
+namespace Push.YiBin
+{
+    public class ChannelConfigurationManager : IChannelConfigurationManager, IScopeDependency
+    {
+        private readonly IOptionsSnapshot<ChannelConfiguration> _channelOption;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="channelOption"></param>
+        public ChannelConfigurationManager(IOptionsSnapshot<ChannelConfiguration> channelOption)
+        {
+            _channelOption = channelOption;
+        }
+
+        /// <summary>
+        /// 获取服务名称
+        /// </summary>
+        /// <returns></returns>
+        public string GetConfigurationProcessingServices()
+        {
+            return _channelOption.Value.ProcessingServices;
+        }
+
+        /// <summary>
+        /// 获取短信推送地址
+        /// </summary>
+        /// <returns></returns>
+        public string GetConfigurationHotlineAddressUrl()
+        {
+            return _channelOption.Value.HotlineAddressUrl;
+        }
+    }
+}

+ 11 - 0
src/yibin/Push.YiBin/Dtos/Reponse.cs

@@ -0,0 +1,11 @@
+namespace Push.YiBin.Dtos
+{
+    public class Reponse
+    {
+        public int Code { get; set; }
+
+        public string Message { get; set; }
+
+        public string Error { get; set; }
+    }
+}

+ 36 - 0
src/yibin/Push.YiBin/Dtos/WaitMessageDto.cs

@@ -0,0 +1,36 @@
+namespace Push.YiBin.Dtos
+{
+    public class WaitMessageDto
+    {
+        /// <summary>
+        /// 业务类型(区分从哪个平台来的短信数据)
+        /// </summary>
+        public string ClientId { get; set; }
+
+        /// <summary>
+        /// 外部业务唯一编号
+        /// </summary>
+        public string? ExternalId { get; set; }
+
+        /// <summary>
+        /// 内容
+        /// </summary>
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        public string? Remark { get; set; }
+
+        /// <summary>
+        /// 接收姓名
+        /// </summary>
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 接收手机号码
+        /// </summary>
+        public string TelNumber { get; set; }
+
+    }
+}

+ 58 - 0
src/yibin/Push.YiBin/FwClient.cs

@@ -0,0 +1,58 @@
+using Microsoft.Extensions.DependencyInjection;
+using XF.Domain.Dependency;
+using XF.Domain.Exceptions;
+
+namespace Push.YiBin
+{
+    public class FwClient : ISingletonDependency, ISelfDependency
+    {
+        private readonly IServiceScopeFactory _scopeFactory;
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="scopeFactory"></param>
+        public FwClient(IServiceScopeFactory scopeFactory)
+        {
+            _scopeFactory = scopeFactory;
+        }
+
+        /// <summary>
+        /// 请求,不带token
+        /// </summary>
+        /// <typeparam name="TResponse"></typeparam>
+        /// <param name="url"></param>
+        /// <param name="httpMethod"></param>
+        /// <param name="stringContent"></param>
+        /// <param name="cancellationToken"></param>
+        /// <returns></returns>
+        public async Task<TResponse?> RequestNoTokenAsync<TResponse>(string url, string httpMethod, string? stringContent = null, CancellationToken cancellationToken = default)
+        {
+            using var scope = _scopeFactory.CreateScope();
+            var provider = scope.ServiceProvider;
+            var channelconfigManager = provider.GetRequiredService<IChannelConfigurationManager>();
+            var httpInvoker = provider.GetRequiredService<IHttpInvoker>();
+
+            var configHotlineClient = channelconfigManager.GetConfigurationHotlineAddressUrl();
+            if (!configHotlineClient.EndsWith('/'))
+                configHotlineClient += "/";
+            var postUrl = configHotlineClient + url;
+            return await httpInvoker.RequestStringContentAsync<TResponse>(postUrl, httpMethod, stringContent,
+                d => d.SetHttpClient(configHotlineClient), cancellationToken);
+        }
+    }
+
+    public static class HttpClientExtensions
+    {
+        public static HttpClient SetHttpClient(this HttpClient httpClient, string baseAddress) =>
+          httpClient.SetBaseAddress(baseAddress);
+
+        public static HttpClient SetBaseAddress(this HttpClient httpClient, string baseAddress)
+        {
+            if (string.IsNullOrEmpty(baseAddress))
+                throw new UserFriendlyException("无效 base address");
+            httpClient.BaseAddress = new Uri(baseAddress);
+            return httpClient;
+        }
+    }
+}

+ 17 - 0
src/yibin/Push.YiBin/IChannelConfigurationManager.cs

@@ -0,0 +1,17 @@
+namespace Push.YiBin
+{
+    public interface IChannelConfigurationManager
+    {
+        /// <summary>
+        /// 获取服务名称
+        /// </summary>
+        /// <returns></returns>
+        string GetConfigurationProcessingServices();
+
+        /// <summary>
+        /// 获取短信推送地址
+        /// </summary>
+        /// <returns></returns>
+        string GetConfigurationHotlineAddressUrl();
+    }
+}

+ 9 - 0
src/yibin/Push.YiBin/IHttpInvoker.cs

@@ -0,0 +1,9 @@
+namespace Push.YiBin
+{
+    public partial interface IHttpInvoker
+    {
+        Task<TResponse?> RequestStringContentAsync<TResponse>(
+            string url, string httpMethod, string? stringContent = null,
+            Action<HttpClient>? setHttpClient = null, CancellationToken cancellationToken = default);
+    }
+}

+ 16 - 0
src/yibin/Push.YiBin/IPushDomainService.cs

@@ -1,11 +1,27 @@
 using DotNetCore.CAP;
 using Push.Share.Dtos;
 using Push.Share.Dtos.FWMessage;
+using Push.YiBin.Dtos;
 
 namespace Push.YiBin
 {
     public interface IPushDomainService
     {
+        /// <summary>
+        /// 短信写入待发表
+        /// </summary>
+        /// <param name="dto"></param>
+        /// <param name="cancellation"></param>
+        /// <returns></returns>
+        Task<Reponse> AddWaitMessage(WaitMessage dto, CancellationToken cancellation);
+
+        /// <summary>
+        /// 短信发送
+        /// </summary>
+        /// <param name="messageDto"></param>
+        /// <returns></returns>
+        Task<Message> PushNewAsync(Share.Dtos.MessageDto messageDto);
+
         /// <summary>
         /// 短信发送
         /// </summary>

+ 2 - 1
src/yibin/Push.YiBin/Push.YiBin.csproj

@@ -9,8 +9,9 @@
     <ItemGroup>
         <PackageReference Include="Mapster" Version="7.3.0" />
         <PackageReference Include="MediatR" Version="12.0.1" />
+        <PackageReference Include="Quartz.Jobs" Version="3.9.0" />
         <PackageReference Include="System.Linq.Dynamic.Core" Version="1.3.7" />
-      <PackageReference Include="XF.Domain.Repository" Version="1.0.5" />
+      <PackageReference Include="XF.Domain.Repository" Version="1.0.6" />
       <PackageReference Include="XF.EasyCaching" Version="1.0.4" />
     </ItemGroup>
 

+ 115 - 8
src/yibin/Push.YiBin/PushDomainService.cs

@@ -2,12 +2,13 @@
 using MapsterMapper;
 using MediatR;
 using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.Logging;
 using Push.Share.Dtos;
 using Push.Share.Dtos.FWMessage;
 using Push.Share.Enums;
+using Push.YiBin.Dtos;
 using System.Net;
 using System.Xml;
-using Microsoft.Extensions.Logging;
 using XF.Domain.Cache;
 using XF.Domain.Dependency;
 using XF.Domain.Exceptions;
@@ -30,6 +31,8 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     private readonly ITypedCache<CacheWaitSendId> _cacheWaitSendId;
     private readonly ICapPublisher _capPublisher;
     private readonly ILogger<PushDomainService> _logger;
+    private readonly IRepository<WaitMessage> _waitMessageRepository;
+    private readonly FwClient _fwClient;
 
     /// <summary>
     /// 
@@ -45,7 +48,9 @@ public class PushDomainService : IPushDomainService, IScopeDependency
         , IMapper mapper, IConfiguration config, IMediator mediator,
         ITypedCache<CacheWaitSendId> cacheWaitSendId,
         ICapPublisher capPublisher,
-        ILogger<PushDomainService> logger)
+        ILogger<PushDomainService> logger,
+        IRepository<WaitMessage> waitMessageRepository,
+       FwClient fwClient)
     {
         _messageRepository = messageRepository;
         _httpClientFactory = httpClientFactory;
@@ -56,10 +61,104 @@ public class PushDomainService : IPushDomainService, IScopeDependency
         _cacheWaitSendId = cacheWaitSendId;
         _capPublisher = capPublisher;
         _logger = logger;
+        _waitMessageRepository = waitMessageRepository;
+        _fwClient = fwClient;
     }
 
     #endregion
 
+    /// <summary>
+    /// 短信写入待发表
+    /// </summary>
+    /// <param name="dto"></param>
+    /// <param name="cancellation"></param>
+    /// <returns></returns>
+    public async Task<Reponse> AddWaitMessage(WaitMessage dto, CancellationToken cancellation)
+    {
+        Reponse reponse = new Reponse() { Code = 1 };
+        if (dto == null)
+        {
+            reponse.Message = "消息不能为空!";
+            return reponse;
+        }
+
+
+        if (string.IsNullOrEmpty(dto.ClientId))
+        {
+            reponse.Message = "发送平台不能为空!";
+            return reponse;
+        }
+
+        if (string.IsNullOrEmpty(dto.TelNumber))
+        {
+            reponse.Message = "短信接收号码不能为空!";
+            return reponse;
+        }
+
+        if (string.IsNullOrEmpty(dto.Name))
+        {
+            reponse.Message = "接收姓名不能为空!";
+            return reponse;
+        }
+
+        if (string.IsNullOrEmpty(dto.Content))
+        {
+            reponse.Message = "短信内容不能为空!";
+            return reponse;
+        }
+
+        var id = await _waitMessageRepository.AddAsync(dto, cancellation);
+        if (string.IsNullOrEmpty(id))
+        {
+            reponse.Message = "新增失败!";
+            return reponse;
+        }
+        else
+        {
+            reponse.Code = 0;
+            reponse.Message = "新增成功!";
+            return reponse;
+
+        }
+    }
+
+    /// <summary>
+    /// 短信发送
+    /// </summary>
+    /// <param name="messageDto"></param>
+    /// <returns></returns>
+    public async Task<Message> PushNewAsync(Share.Dtos.MessageDto messageDto)
+    {
+        var message = _mapper.Map<Message>(messageDto);
+        int WaitSendId = GenerateWaitSendId();
+        message.WaitSendId = WaitSendId;
+
+        _logger.LogInformation($"取到WaitSendId值:{WaitSendId}");
+
+        //调用短信发送业务
+        var strResult = await SmsSend(messageDto, WaitSendId);
+
+        //处理返回值
+        if (true == strResult.Contains("ok,")) // ok,35797257
+        {
+            string[] strArray = strResult.Split(',');
+            if (2 == strArray.Length)
+            {
+                // 短信服务中心返回 IFSFWID,用于短信发送状态接收时匹配
+                message.SmsWaitSendingId = strArray[1];
+                message.SendState = ESendState.Sending;
+                message.Status = EPushStatus.Success;
+            }
+        }
+        else
+        {
+            message.Reason = strResult;
+            message.Status = EPushStatus.Failed;
+        }
+        return message;
+    }
+
+
 
     #region 短信发送
 
@@ -69,7 +168,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     /// <param name="messageDto"></param>
     /// <param name="cancellation"></param>
     /// <returns></returns>
-    public async Task PushAsync(MessageDto messageDto, CancellationToken cancellation)
+    public async Task PushAsync(Share.Dtos.MessageDto messageDto, CancellationToken cancellation)
     {
         if (messageDto == null)
             throw UserFriendlyException.SameMessage("消息不能为空!");
@@ -196,11 +295,19 @@ public class PushDomainService : IPushDomainService, IScopeDependency
             data.Reason = receiveMessageDto.errormsg;
             await _messageRepository.UpdateAsync(data);
 
-            if (data.ClientId == "Hotline")
+            try
             {
-                var dataPush = _mapper.Map<PushMessageDto>(data);
-                dataPush.Type = "1";
-                await _capPublisher.PublishAsync(Push.Share.EventNames.UpdateSendSmsState, dataPush, cancellationToken: default);
+                if (data.ClientId == "Hotline")
+                {
+                    var dataPush = _mapper.Map<PushMessageDto>(data);
+                    dataPush.Type = "1";
+                    await _fwClient.RequestNoTokenAsync<Reponse>("api/v1/PushMessage/update-send-sms-state", "Post", System.Text.Json.JsonSerializer.Serialize(dataPush), cancellationToken: default);
+                }
+            }
+            catch (Exception)
+            {
+
+                throw;
             }
         }
         // 成功返回值必须是ok
@@ -266,7 +373,7 @@ public class PushDomainService : IPushDomainService, IScopeDependency
     /// 短信发送
     /// </summary>
     /// <returns></returns>
-    private async Task<string> SmsSend(MessageDto messageDto, int WaitSendId)
+    private async Task<string> SmsSend(Share.Dtos.MessageDto messageDto, int WaitSendId)
     {
         _logger.LogInformation("准备短信推送");
         string strResult;

+ 83 - 0
src/yibin/Push.YiBin/TaskSendMessageJob.cs

@@ -0,0 +1,83 @@
+using MapsterMapper;
+using Microsoft.Extensions.Logging;
+using Push.Share.Dtos;
+using Push.Share.Enums;
+using Push.YiBin.Dtos;
+using Quartz;
+using static System.Runtime.InteropServices.JavaScript.JSType;
+
+namespace Push.YiBin
+{
+    public class TaskSendMessageJob : IJob, IDisposable
+    {
+        private readonly IMapper _mapper;
+        private readonly ILogger<TaskSendMessageJob> _logger;
+        private readonly IRepository<Message> _messageRepository;
+        private readonly IRepository<WaitMessage> _waitMessageRepository;
+        private readonly IPushDomainService _pushDomainService; private readonly FwClient _fwClient;
+
+        public TaskSendMessageJob(IMapper mapper,
+            ILogger<TaskSendMessageJob> logger,
+            IRepository<Message> messageRepository,
+            IRepository<WaitMessage> waitMessageRepository,
+            IPushDomainService pushDomainService,
+            FwClient fwClient)
+        {
+            _mapper = mapper;
+            _logger = logger;
+            _messageRepository = messageRepository;
+            _waitMessageRepository = waitMessageRepository;
+            _pushDomainService = pushDomainService;
+            _fwClient = fwClient;
+        }
+
+        /// <summary>
+        /// 
+        /// </summary>
+        /// <param name="context"></param>
+        /// <returns></returns>
+        public async Task Execute(IJobExecutionContext context)
+        {
+            _logger.LogWarning("进来了,时间是:" + DateTime.Now);
+            var tasks = await _waitMessageRepository.Queryable()
+                  .Where(d => d.Status == EPushStatus.Waiting && d.SendTimes <= 3)
+                  .OrderBy(d => d.CreationTime)
+                  .Take(50)
+                  .ToListAsync(context.CancellationToken);
+
+            if (tasks.Count != 0)
+            {
+                foreach (var sendTask in tasks)
+                {
+                    sendTask.Status = EPushStatus.Pushing;
+                    if (await _waitMessageRepository.Updateable(sendTask).ExecuteCommandWithOptLockAsync() > 0)
+                    {
+                        var result = await _pushDomainService.PushNewAsync(_mapper.Map<Share.Dtos.MessageDto>(sendTask));
+                        if (result.SendState == ESendState.Sending && result.Status == EPushStatus.Success)
+                        {
+                            await _waitMessageRepository.RemoveAsync(sendTask, cancellationToken: context.CancellationToken);
+                            await _messageRepository.AddAsync(result, context.CancellationToken); //写入本地数据库
+
+                        }
+                        else
+                        {
+                            sendTask.SendTimes = sendTask.SendTimes + 1;
+                            sendTask.Reason = result.Reason;
+                            if (sendTask.SendTimes >= 4)
+                                sendTask.Status = EPushStatus.Failed;
+                            else
+                                sendTask.Status = EPushStatus.Waiting;
+                            await _waitMessageRepository.UpdateAsync(sendTask, context.CancellationToken);
+                        }
+                    }
+                }
+            }
+        }
+
+
+        public void Dispose()
+        {
+
+        }
+    }
+}

+ 76 - 0
src/yibin/Push.YiBin/WaitMessage.cs

@@ -0,0 +1,76 @@
+using Push.Share.Enums;
+
+namespace Push.YiBin
+{
+    /// <summary>
+    /// 待发表
+    /// </summary>
+    public class WaitMessage : CreationEntity
+    {
+        /// <summary>
+        /// 业务类型(区分从哪个平台来的短信数据)
+        /// </summary>
+        [SugarColumn(ColumnDescription = "业务类型(区分从哪个平台来的短信数据)")]
+        public string ClientId { get; set; }
+
+        /// <summary>
+        /// 外部业务唯一编号
+        /// </summary>
+        [SugarColumn(ColumnDescription = "外部业务唯一编号")]
+        public string? ExternalId { get; set; }
+
+        /// <summary>
+        /// 业务系统短信ID
+        /// </summary>
+        [SugarColumn(ColumnDescription = "业务系统短信ID")]
+        public int WaitSendId { get; set; } = 0;
+
+        /// <summary>
+        /// 推送状态
+        /// </summary>
+        [SugarColumn(ColumnDescription = "推送状态")]
+        public EPushStatus Status { get; set; } = EPushStatus.Waiting;
+
+        /// <summary>
+        /// 推送次数
+        /// </summary>
+        [SugarColumn(ColumnDescription = "推送次数")]
+        public int SendTimes { get; set; }
+
+        /// <summary>
+        /// 短信内容
+        /// </summary>
+        [SugarColumn(ColumnDescription = "短信内容")]
+        public string Content { get; set; }
+
+        /// <summary>
+        /// 备注
+        /// </summary>
+        [SugarColumn(ColumnDescription = "备注", ColumnDataType = "varchar(500)", IsNullable = true)]
+        public string? Remark { get; set; }
+
+        /// <summary>
+        /// 接收姓名
+        /// </summary>
+        [SugarColumn(ColumnDescription = "接收姓名")]
+        public string Name { get; set; }
+
+        /// <summary>
+        /// 接收手机号码
+        /// </summary>
+        [SugarColumn(ColumnDescription = "接收手机号码")]
+        public string TelNumber { get; set; }
+
+        /// <summary>
+        /// 发送失败原因等
+        /// </summary>
+        [SugarColumn(ColumnDescription = "发送失败原因等", ColumnDataType = "varchar(200)", IsNullable = true)]
+        public string? Reason { get; set; }
+
+        /// <summary>
+        /// 标识版本字段
+        /// </summary>
+        [SqlSugar.SugarColumn(IsEnableUpdateVersionValidation = true)]
+        public Guid Ver { get; set; }
+    }
+}