|
@@ -1,15 +1,11 @@
|
|
|
using DotNetCore.CAP;
|
|
|
-using Hotline.Push.Notifies;
|
|
|
using Hotline.Share.Dtos.Push;
|
|
|
using Hotline.Share.Dtos.Push.FWMessage;
|
|
|
using Hotline.Share.Enums.Push;
|
|
|
using MapsterMapper;
|
|
|
using MediatR;
|
|
|
using Microsoft.Extensions.Configuration;
|
|
|
-using System.Net;
|
|
|
using System.Text.RegularExpressions;
|
|
|
-using System.Xml;
|
|
|
-using XF.Domain.Cache;
|
|
|
using XF.Domain.Dependency;
|
|
|
using XF.Domain.Exceptions;
|
|
|
using XF.Domain.Repository;
|
|
@@ -28,8 +24,8 @@ public class PushDomainService : IPushDomainService, IScopeDependency
|
|
|
private readonly IConfiguration _config;
|
|
|
private readonly SmsAccountInfo? accountInfo = null;
|
|
|
private readonly IMediator _mediator;
|
|
|
- private readonly ITypedCache<CacheWaitSendId> _cacheWaitSendId;
|
|
|
private readonly IRepository<MessageTemplate> _messageTemplateRepository;
|
|
|
+ private readonly ICapPublisher _capPublisher;
|
|
|
|
|
|
/// <summary>
|
|
|
///
|
|
@@ -40,10 +36,11 @@ public class PushDomainService : IPushDomainService, IScopeDependency
|
|
|
/// <param name="config"></param>
|
|
|
/// <param name="mediator"></param>
|
|
|
/// <param name="cacheWaitSendId"></param>
|
|
|
+ /// <param name="messageTemplateRepository"></param>
|
|
|
+ /// <param name="capPublisher"></param>
|
|
|
public PushDomainService(IRepository<Message> messageRepository, IHttpClientFactory httpClientFactory
|
|
|
, IMapper mapper, IConfiguration config, IMediator mediator,
|
|
|
- ITypedCache<CacheWaitSendId> cacheWaitSendId,
|
|
|
- IRepository<MessageTemplate> messageTemplateRepository)
|
|
|
+ IRepository<MessageTemplate> messageTemplateRepository, ICapPublisher capPublisher)
|
|
|
{
|
|
|
_messageRepository = messageRepository;
|
|
|
_httpClientFactory = httpClientFactory;
|
|
@@ -51,370 +48,106 @@ public class PushDomainService : IPushDomainService, IScopeDependency
|
|
|
_config = config;
|
|
|
accountInfo = _config.GetSection("SmsAccountInfo").Get<SmsAccountInfo>();
|
|
|
_mediator = mediator;
|
|
|
- _cacheWaitSendId = cacheWaitSendId;
|
|
|
_messageTemplateRepository = messageTemplateRepository;
|
|
|
+ _capPublisher = capPublisher;
|
|
|
}
|
|
|
#endregion
|
|
|
|
|
|
-
|
|
|
-
|
|
|
- #region 短信发送
|
|
|
/// <summary>
|
|
|
/// 短信发送
|
|
|
/// </summary>
|
|
|
/// <param name="messageDto"></param>
|
|
|
/// <param name="cancellation"></param>
|
|
|
/// <returns></returns>
|
|
|
- public async Task PushAsync(MessageDto messageDto, CancellationToken cancellation)
|
|
|
+ public async Task PushMsgAsync(MessageDto messageDto, CancellationToken cancellation)
|
|
|
{
|
|
|
if (messageDto == null)
|
|
|
throw UserFriendlyException.SameMessage("消息不能为空!");
|
|
|
|
|
|
- if (string.IsNullOrEmpty(messageDto.Template) && string.IsNullOrEmpty(messageDto.Content))
|
|
|
+ if (string.IsNullOrEmpty(messageDto.TemplateCode) && string.IsNullOrEmpty(messageDto.Content))
|
|
|
throw UserFriendlyException.SameMessage("模板和内容不能同时为空!");
|
|
|
|
|
|
- Message message = new();
|
|
|
- int WaitSendId = 0;
|
|
|
- var sendData = await _messageRepository.GetAsync(p => p.ExternalId == messageDto.ExternalId && p.PushBusiness == messageDto.PushBusiness && p.TelNumber == messageDto.TelNumber);
|
|
|
- if (sendData != null)//处理重复数据
|
|
|
- {
|
|
|
- WaitSendId = sendData.WaitSendId;
|
|
|
- messageDto = _mapper.Map<MessageDto>(sendData);
|
|
|
- message = sendData;
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
-
|
|
|
- #region 替换模板内容
|
|
|
- string reason = null;
|
|
|
- //如果模板为空,参数为空则直接用短信内容
|
|
|
- if (!string.IsNullOrEmpty(messageDto.Template) && messageDto.Params.Count > 0)
|
|
|
- {
|
|
|
- //查询模板信息
|
|
|
- var messageTemplate = await _messageTemplateRepository.GetAsync(p => p.Code == messageDto.Template, cancellation);
|
|
|
- if (messageTemplate == null || string.IsNullOrEmpty(messageTemplate.Content))
|
|
|
- throw UserFriendlyException.SameMessage("未找到模板短信!");
|
|
|
-
|
|
|
- string Template = messageTemplate.Content;
|
|
|
- //正则查询模板中需要替换的内容
|
|
|
- Regex regex = new(@"\{[a-zA-Z0-9]{1,}\}");
|
|
|
- var matches = regex.Matches(Template);
|
|
|
- if (matches != null && matches.Count != messageDto.Params.Count)
|
|
|
- //参数与需要替换的字符数不匹配
|
|
|
- throw UserFriendlyException.SameMessage("模板需要参数与实际传递参数个数不匹配!");
|
|
|
- //reason = "模板需要参数与实际传递参数个数不匹配!";
|
|
|
-
|
|
|
- //根据正则查询出来的匹配项替换内容
|
|
|
- for (int i = 0; i < matches.Count; i++)
|
|
|
- Template = Template.Replace(matches[i].ToString(), messageDto.Params[i]);
|
|
|
-
|
|
|
- messageDto.Content = Template;
|
|
|
- }
|
|
|
- #endregion
|
|
|
- message = _mapper.Map<Message>(messageDto);
|
|
|
- WaitSendId = GenerateWaitSendId();
|
|
|
- message.WaitSendId = WaitSendId;
|
|
|
- message.Reason = reason;
|
|
|
- }
|
|
|
-
|
|
|
- //调用短信发送业务
|
|
|
- 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;
|
|
|
- }
|
|
|
- if (sendData != null)
|
|
|
- await _messageRepository.UpdateAsync(message);//跟新本地数据库
|
|
|
- else
|
|
|
- await _messageRepository.AddAsync(message);//写入本地数据库
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region 查询账号短信剩余量
|
|
|
- /// <summary>
|
|
|
- /// 查询账号短信剩余量
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- public async Task<string> GetAccountNum()
|
|
|
- {
|
|
|
- // 账户,密码
|
|
|
- string strAcceptContent = $"{accountInfo.AccountUser},{accountInfo.AccountPwd}";
|
|
|
- // 参数
|
|
|
- Dictionary<string, string> dicParam = new()
|
|
|
- {
|
|
|
- { "AcceptType", "fwAccountNum" },
|
|
|
- { "AcceptContent", strAcceptContent }
|
|
|
+ #region 替换模板内容
|
|
|
+ //如果模板为空,参数为空则直接用短信内容
|
|
|
+ if (!string.IsNullOrEmpty(messageDto.TemplateCode) && messageDto.Params.Count > 0)
|
|
|
+ {
|
|
|
+ //查询模板信息
|
|
|
+ var messageTemplate = await _messageTemplateRepository.GetAsync(p => p.Code == messageDto.TemplateCode, cancellation);
|
|
|
+ if (messageTemplate == null || string.IsNullOrEmpty(messageTemplate.Content))
|
|
|
+ throw UserFriendlyException.SameMessage("未找到模板短信!");
|
|
|
+
|
|
|
+ string Template = messageTemplate.Content;
|
|
|
+ //正则查询模板中需要替换的内容
|
|
|
+ Regex regex = new(@"\{[a-zA-Z0-9]{1,}\}");
|
|
|
+ var matches = regex.Matches(Template);
|
|
|
+ if (matches != null && matches.Count != messageDto.Params.Count)
|
|
|
+ //参数与需要替换的字符数不匹配
|
|
|
+ throw UserFriendlyException.SameMessage("模板需要参数与实际传递参数个数不匹配!");
|
|
|
+ //reason = "模板需要参数与实际传递参数个数不匹配!";
|
|
|
+
|
|
|
+ //根据正则查询出来的匹配项替换内容
|
|
|
+ for (int i = 0; i < matches.Count; i++)
|
|
|
+ Template = Template.Replace(matches[i].ToString(), messageDto.Params[i]);
|
|
|
+
|
|
|
+ messageDto.Content = Template;
|
|
|
+ }
|
|
|
+ #endregion
|
|
|
+ var message = _mapper.Map<Message>(messageDto);
|
|
|
+ var id = await _messageRepository.AddAsync(message);//写入本地数据库
|
|
|
+
|
|
|
+ PushMessageDto pushMessage = new()
|
|
|
+ {
|
|
|
+ ClientId = "Hotline",
|
|
|
+ ExternalId = id,
|
|
|
+ Content = message.Content,
|
|
|
+ Remark = message.Remark,
|
|
|
+ Name = message.Name,
|
|
|
+ TelNumber = message.TelNumber
|
|
|
};
|
|
|
- // 将参数转化为HttpContent
|
|
|
- HttpContent content = new FormUrlEncodedContent(dicParam);
|
|
|
- // ok,9
|
|
|
- // error,查询异常
|
|
|
- string strResult = await PostHelper(accountInfo.MessageServerUrl, content);
|
|
|
- var strArray = strResult.Split(",");
|
|
|
- if (2 == strArray.Length)
|
|
|
- {
|
|
|
- strResult = strArray[1];
|
|
|
- }
|
|
|
- else
|
|
|
- {
|
|
|
- strResult = "查询异常";
|
|
|
- }
|
|
|
- return strResult;
|
|
|
- }
|
|
|
- #endregion
|
|
|
|
|
|
- #region 短信发送状态回调接口
|
|
|
- /// <summary>
|
|
|
- /// 短信发送状态回调接口
|
|
|
- /// </summary>
|
|
|
- /// <param name="receiveMessageDto"></param>
|
|
|
- /// <returns></returns>
|
|
|
- public async Task<string> ReceiveObtain(ReceiveMessageDto receiveMessageDto)
|
|
|
- {
|
|
|
- string strResult = "error,缺少参数";
|
|
|
- if (receiveMessageDto.account != accountInfo.ReturnAccountUser || receiveMessageDto.pswd != accountInfo.ReturnAccountPwd)
|
|
|
- {
|
|
|
- strResult = "error,参数错误";
|
|
|
- return strResult;
|
|
|
- }
|
|
|
- if (true == string.IsNullOrEmpty(receiveMessageDto.account) || true == string.IsNullOrEmpty(receiveMessageDto.pswd)
|
|
|
- || true == string.IsNullOrEmpty(receiveMessageDto.msgid) || true == string.IsNullOrEmpty(receiveMessageDto.sendtime)
|
|
|
- || receiveMessageDto.msgcount <= 0 || receiveMessageDto.state < 0)
|
|
|
- {
|
|
|
- strResult = "error,参数错误";
|
|
|
- return strResult;
|
|
|
- }
|
|
|
- //修改数据
|
|
|
- var data = await _messageRepository.GetAsync(p => p.SmsWaitSendingId == receiveMessageDto.msgid);
|
|
|
- if (data != null)
|
|
|
- {
|
|
|
- data.SendTime = Convert.ToDateTime(receiveMessageDto.sendtime);
|
|
|
- data.MsgCount = receiveMessageDto.msgcount;
|
|
|
- data.SendState = (ESendState)receiveMessageDto.state;
|
|
|
- data.SmsSendingCompletedId = receiveMessageDto.sfid + "";
|
|
|
- data.Reason = receiveMessageDto.errormsg;
|
|
|
- await _messageRepository.UpdateAsync(data);
|
|
|
- // 成功返回值必须是ok
|
|
|
- strResult = "ok";
|
|
|
- }
|
|
|
- else
|
|
|
- strResult = "error,调用失败";
|
|
|
- return strResult;
|
|
|
+ await _capPublisher.PublishAsync(Hotline.Share.Mq.EventNames.SendSms, pushMessage, cancellationToken: cancellation);
|
|
|
}
|
|
|
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region 短信接收
|
|
|
/// <summary>
|
|
|
- /// 短信接收
|
|
|
+ /// 修改短信推送状态
|
|
|
/// </summary>
|
|
|
- /// <param name="receiveMessageDto"></param>
|
|
|
+ /// <param name="dto"></param>
|
|
|
+ /// <param name="cancellation"></param>
|
|
|
/// <returns></returns>
|
|
|
- public async Task<string> ReceiveSms(ReceiveMessageDto receiveMessageDto)
|
|
|
+ public async Task PushMsgUpdateStateAsync(PushReceiveMessageDto dto, CancellationToken cancellation)
|
|
|
{
|
|
|
- string strResult = "error,缺少参数";
|
|
|
- if (receiveMessageDto.account != accountInfo.ReturnAccountUser || receiveMessageDto.pswd != accountInfo.ReturnAccountPwd)
|
|
|
- {
|
|
|
- strResult = "error,参数错误";
|
|
|
- return strResult;
|
|
|
- }
|
|
|
- if (true == string.IsNullOrEmpty(receiveMessageDto.account) || true == string.IsNullOrEmpty(receiveMessageDto.pswd)
|
|
|
- || true == string.IsNullOrEmpty(receiveMessageDto.msg) || true == string.IsNullOrEmpty(receiveMessageDto.mobile)
|
|
|
- || true == string.IsNullOrEmpty(receiveMessageDto.motime))
|
|
|
- {
|
|
|
- strResult = "error,参数错误";
|
|
|
- return strResult;
|
|
|
- }
|
|
|
-
|
|
|
- var data = await _messageRepository.GetAsync(p => p.TelNumber == receiveMessageDto.mobile && p.IsSmsReply == false && p.SmsReplyTime == null);
|
|
|
+ var data = await _messageRepository.GetAsync(p => p.Id == dto.ExternalId, cancellation);
|
|
|
if (data != null)
|
|
|
{
|
|
|
- data.IsSmsReply = true;
|
|
|
- data.SmsReplyTime = Convert.ToDateTime(receiveMessageDto.motime);
|
|
|
- data.SmsReplyContent = receiveMessageDto.msg;
|
|
|
- await _messageRepository.UpdateAsync(data);
|
|
|
-
|
|
|
- //消息处理
|
|
|
- await _mediator.Publish(new PushMessageNotify(_mapper.Map<PushMessageNotifyDto>(data)));
|
|
|
-
|
|
|
- }
|
|
|
- // 成功返回值必须是ok
|
|
|
- strResult = "ok";
|
|
|
-
|
|
|
- return strResult;
|
|
|
-
|
|
|
- }
|
|
|
-
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region 私有方法
|
|
|
-
|
|
|
- #region 短信发送
|
|
|
- /// <summary>
|
|
|
- /// 短信发送
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private async Task<string> SmsSend(MessageDto messageDto, int WaitSendId)
|
|
|
- {
|
|
|
- string strResult;
|
|
|
- try
|
|
|
- {
|
|
|
- SendSmsModelDto tSms = new()
|
|
|
+ if (dto.Type == "0")
|
|
|
{
|
|
|
- // 业务系统短信ID
|
|
|
- nWaitID = WaitSendId,
|
|
|
- // 短信类型1:默认2:营销
|
|
|
- strType = "1",
|
|
|
- // 业务系统工单ID
|
|
|
- nFlowID = 0,
|
|
|
- // 姓名
|
|
|
- strPhoneName = messageDto.Name,
|
|
|
- // 手机号码
|
|
|
- strPhoneNumber = messageDto.TelNumber,
|
|
|
- // 短信内容
|
|
|
- strPhoneConnent = messageDto.Content,
|
|
|
- // 添加时间
|
|
|
- dAddTime = DateTime.Now,
|
|
|
- strPhoneNumberAll = "",
|
|
|
- strRemark = "",
|
|
|
- // 业务系统添加人ID
|
|
|
- nUserID = 0,
|
|
|
- // 业务系统添加人姓名
|
|
|
- strUserName = "",
|
|
|
- // 业务系统添加部门ID
|
|
|
- nBMID = 0,
|
|
|
- // 业务系统添加部门名称
|
|
|
- strBMName = "",
|
|
|
- // 短信系统账号
|
|
|
- strAccountUser = accountInfo.AccountUser,
|
|
|
- // 短信系统密码
|
|
|
- strAccountPwd = accountInfo.AccountPwd
|
|
|
- };
|
|
|
- // 序列化
|
|
|
- string strSendJson = Newtonsoft.Json.JsonConvert.SerializeObject(tSms);//System.Text.Json.JsonSerializer.Serialize(tSms);//
|
|
|
-
|
|
|
- // 参数加密
|
|
|
- string strKey = await FwEncrypt(strSendJson);
|
|
|
- // 短信发送参数
|
|
|
- Dictionary<string, string> dicParam = new Dictionary<string, string>
|
|
|
- {
|
|
|
- { "AcceptType", "fwSms" },
|
|
|
- { "AcceptContent", strKey }
|
|
|
- };
|
|
|
- // 将参数转化为HttpContent
|
|
|
- HttpContent content = new FormUrlEncodedContent(dicParam);
|
|
|
- strResult = await PostHelper(accountInfo.MessageServerUrl, content);
|
|
|
- }
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- strResult = ex.Message;
|
|
|
- }
|
|
|
- return strResult;
|
|
|
- }
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region 参数加密
|
|
|
- /// <summary>
|
|
|
- /// 参数加密
|
|
|
- /// </summary>
|
|
|
- /// <param name="planText"></param>
|
|
|
- /// <returns></returns>
|
|
|
- private async Task<string> FwEncrypt(string planText)
|
|
|
- {
|
|
|
- // 参数
|
|
|
- Dictionary<string, string> dicParam = new Dictionary<string, string>();
|
|
|
- dicParam.Add("AcceptType", "fwEncrypt");
|
|
|
- dicParam.Add("AcceptContent", planText);
|
|
|
- // 将参数转化为HttpContent
|
|
|
- HttpContent content = new FormUrlEncodedContent(dicParam);
|
|
|
- string strResult = await PostHelper(accountInfo.MessageServerUrl, content);
|
|
|
- string strValue = "";
|
|
|
- // ok,XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX
|
|
|
- if (true == strResult.Contains("ok,"))
|
|
|
- {
|
|
|
- string[] strArray = strResult.Split(',');
|
|
|
- if (2 == strArray.Length)
|
|
|
+ if (dto.SendState == ESendState.Sending)
|
|
|
+ {
|
|
|
+ data.SmsWaitSendingId = dto.SmsWaitSendingId;
|
|
|
+ data.SendState = ESendState.Sending;
|
|
|
+ data.Status = EPushStatus.Success;
|
|
|
+ }
|
|
|
+ else
|
|
|
+ data.Status = EPushStatus.Failed;
|
|
|
+ }
|
|
|
+ else if (dto.Type == "1")
|
|
|
{
|
|
|
- strValue = strArray[1];
|
|
|
+ data.SendTime = Convert.ToDateTime(dto.SendTime);
|
|
|
+ data.SendState = dto.SendState;
|
|
|
+ data.MsgCount = dto.MsgCount;
|
|
|
+ data.SmsSendingCompletedId = dto.SmsSendingCompletedId;
|
|
|
}
|
|
|
- }
|
|
|
- return strValue;
|
|
|
- }
|
|
|
- #endregion
|
|
|
-
|
|
|
- #region http请求
|
|
|
- /// <summary>
|
|
|
- /// 封装使用HttpClient调用WebService
|
|
|
- /// </summary>
|
|
|
- /// <param name="url">URL地址</param>
|
|
|
- /// <param name="content">参数</param>
|
|
|
- /// <returns></returns>
|
|
|
- private async Task<string> PostHelper(string url, HttpContent content)
|
|
|
- {
|
|
|
- var result = string.Empty;
|
|
|
- try
|
|
|
- {
|
|
|
- using (var client = _httpClientFactory.CreateClient())
|
|
|
- using (var response = await client.PostAsync(url, content))
|
|
|
+ else if (dto.Type == "2")
|
|
|
{
|
|
|
- if (response.StatusCode == HttpStatusCode.OK)
|
|
|
- {
|
|
|
- result = await response.Content.ReadAsStringAsync();
|
|
|
- XmlDocument doc = new XmlDocument();
|
|
|
- doc.LoadXml(result);
|
|
|
- result = doc.InnerText;
|
|
|
- }
|
|
|
+ data.IsSmsReply = dto.IsSmsReply;
|
|
|
+ data.SmsReplyTime = Convert.ToDateTime(dto.SmsReplyTime);
|
|
|
+ data.SmsReplyContent = dto.SmsReplyContent;
|
|
|
}
|
|
|
+
|
|
|
+ await _messageRepository.UpdateAsync(data, cancellation);
|
|
|
}
|
|
|
- catch (Exception ex)
|
|
|
- {
|
|
|
- result = ex.Message;
|
|
|
- }
|
|
|
- return result;
|
|
|
}
|
|
|
- #endregion
|
|
|
|
|
|
- #region 生成待发短信ID缓存
|
|
|
- /// <summary>
|
|
|
- /// 生成待发短信ID缓存
|
|
|
- /// </summary>
|
|
|
- /// <returns></returns>
|
|
|
- private int GenerateWaitSendId()
|
|
|
- {
|
|
|
- var cacheKey = "PushMessageWaitSendId";
|
|
|
- var cacheWaitSend = _cacheWaitSendId.GetOrSet(cacheKey, f =>
|
|
|
- {
|
|
|
- var waitSendId = _messageRepository.Queryable()
|
|
|
- .MaxAsync(p => p.WaitSendId)
|
|
|
- .GetAwaiter()
|
|
|
- .GetResult();
|
|
|
- return new CacheWaitSendId { WaitSendId = waitSendId };
|
|
|
- }, TimeSpan.FromDays(1));
|
|
|
- cacheWaitSend!.WaitSendId += 1;
|
|
|
|
|
|
- _cacheWaitSendId.Set(cacheKey, cacheWaitSend);
|
|
|
- return cacheWaitSend.WaitSendId;
|
|
|
- }
|
|
|
|
|
|
- public class CacheWaitSendId
|
|
|
- {
|
|
|
- public int WaitSendId { get; set; }
|
|
|
- }
|
|
|
- #endregion
|
|
|
- #endregion
|
|
|
}
|