using Azure.Core;
|
using Furion;
|
using Furion.DatabaseAccessor;
|
using Furion.DataEncryption;
|
using Furion.FriendlyException;
|
using Furion.HttpRemote;
|
using Furion.UnifyResult;
|
using Mapster;
|
using MediatR;
|
using Microsoft.AspNetCore.Routing.Template;
|
using Microsoft.EntityFrameworkCore;
|
using Microsoft.Extensions.Hosting;
|
using Microsoft.Extensions.Options;
|
using pingan.openbank.api.sdk.enums;
|
using System;
|
using System.Collections.Generic;
|
using System.Linq;
|
using System.Text;
|
using System.Threading;
|
using System.Threading.Tasks;
|
using static System.Runtime.InteropServices.JavaScript.JSType;
|
|
namespace ApiTools.Core
|
{
|
/// <summary>
|
/// 短信工具
|
/// </summary>
|
public class SmsUtils
|
{
|
private readonly IRepository<SmsLog> rep;
|
private readonly IRepository<SmsSetting> repSmsSetting;
|
private readonly AliyunSmsService aliyunSmsService;
|
private readonly ChengLiYeSmsService chengLiYeSmsService;
|
|
public SmsUtils(
|
IRepository<SmsLog> rep,
|
IRepository<SmsSetting> repSmsSetting,
|
AliyunSmsService aliyunSmsService,
|
ChengLiYeSmsService chengLiYeSmsService)
|
{
|
this.rep = rep;
|
this.repSmsSetting = repSmsSetting;
|
this.aliyunSmsService = aliyunSmsService;
|
this.chengLiYeSmsService = chengLiYeSmsService;
|
}
|
|
/// <summary>
|
/// 发送短信
|
/// </summary>
|
/// <param name="model"></param>
|
/// <param name="templateParam"></param>
|
/// <param name="cancellationToken"></param>
|
/// <returns></returns>
|
public async Task<Guid> Send(SendSmsModel model, object templateParam, CancellationToken cancellationToken = default)
|
{
|
var logier = JwtUtils.GetCurrentLogier();
|
var setting = await repSmsSetting.AsQueryable().AsNoTracking()
|
.Include(it => it.Accesses)
|
.Where(it => it.ChannelId == logier.ChannelId)
|
.FirstOrDefaultAsync();
|
if (setting == null) throw Oops.Oh(EnumErrorCodeType.s401, "短信配置");
|
await CheckOperationTooFrequent(logier, setting, model);
|
var entity = await Send(setting, null, model, templateParam, cancellationToken);
|
if (entity == null) throw Oops.Oh(EnumErrorCodeType.s404, "短信通道");
|
return entity.Id;
|
}
|
|
/// <summary>
|
/// 发送验证码短信
|
/// </summary>
|
/// <param name="model"></param>
|
/// <param name="cancellationToken"></param>
|
/// <returns></returns>
|
public async Task<Guid> SendVerifyCode(SendVerifyCodeModel model, CancellationToken cancellationToken = default)
|
{
|
return await Send(new SendSmsModel
|
{
|
PhoneNumber = model.PhoneNumber,
|
TemplateCode = model.TemplateCode,
|
Expiry = DateTime.Now.AddMinutes(5)
|
},
|
new
|
{
|
code = new Random().Next(100000, 999999).ToString()
|
},
|
cancellationToken);
|
}
|
|
/// <summary>
|
/// 校验验证码
|
/// </summary>
|
/// <param name="model"></param>
|
/// <param name="cancellationToken"></param>
|
/// <returns></returns>
|
public async Task CheckVerifyCode(CheckVerifyCodeModel model, CancellationToken cancellationToken = default)
|
{
|
var logier = JwtUtils.GetCurrentLogier();
|
var templateCode = model.TemplateCode.ToString();
|
var templateParam = new { code = model.VerifyCode }.ToJson();
|
var now = DateTime.Now;
|
var entity = await rep.AsQueryable().AsNoTracking()
|
.Where(it =>
|
it.ChannelId == logier.ChannelId
|
&& it.PhoneNumber == model.PhoneNumber
|
&& it.TemplateCode == templateCode
|
&& it.TemplateParam == templateParam
|
&& (it.Expiry == null || it.Expiry > now)
|
&& !it.IsUsed
|
&& (it.Status == EnumSmsStatus.InProcess || it.Status == EnumSmsStatus.Success))
|
.FirstOrDefaultAsync(cancellationToken);
|
if (entity == null) throw Oops.Oh(EnumErrorCodeType.s400, "验证码无效");
|
entity.IsUsed = true;
|
await rep.UpdateAsync(entity);
|
}
|
|
/// <summary>
|
/// 校验操作频繁
|
/// </summary>
|
/// <param name="logier"></param>
|
/// <param name="setting"></param>
|
/// <param name="model"></param>
|
/// <returns></returns>
|
private async Task CheckOperationTooFrequent(CurrentLogier logier, SmsSetting setting, SendSmsModel model)
|
{
|
var now = DateTimeOffset.Now;
|
var templateCode = model.TemplateCode.ToString();
|
var times = await rep.AsQueryable().AsNoTracking()
|
.Where(it =>
|
it.ChannelId == logier.ChannelId
|
&& it.TemplateCode == templateCode
|
&& it.PhoneNumber == model.PhoneNumber
|
&& it.CreatedTime.Date == now.Date
|
&& (it.Status == EnumSmsStatus.InProcess || it.Status == EnumSmsStatus.Success))
|
.Select(it => it.CreatedTime)
|
.ToListAsync();
|
if (times.Count(it => now.AddMinutes(-1) <= it && it <= now) >= setting.MinutelyMaxCount)
|
{
|
UnifyContext.Fill(new
|
{
|
setting.MinutelyMaxCount
|
});
|
throw Oops.Oh(EnumErrorCodeType.s429);
|
}
|
else if (times.Count(it => now.AddHours(-1) <= it && it <= now) >= setting.HourlyMaxCount)
|
{
|
UnifyContext.Fill(new
|
{
|
setting.HourlyMaxCount
|
});
|
throw Oops.Oh(EnumErrorCodeType.s429);
|
}
|
else if (times.Count >= setting.DailyMaxCount)
|
{
|
UnifyContext.Fill(new
|
{
|
setting.DailyMaxCount
|
});
|
throw Oops.Oh(EnumErrorCodeType.s429);
|
}
|
}
|
|
/// <summary>
|
/// 获取短信通道
|
/// </summary>
|
/// <param name="setting"></param>
|
/// <param name="fromEntity"></param>
|
/// <returns></returns>
|
private SmsSettingAccess GetSmsAccess(SmsSetting setting, SmsLog fromEntity)
|
{
|
var accesses = setting.Accesses
|
.OrderBy(it => it.Sort)
|
.Where(it => !it.IsDisabled)
|
.ToList();
|
SmsSettingAccess? access = null;
|
if (fromEntity == null)
|
{
|
access = accesses
|
.FirstOrDefault();
|
}
|
else
|
{
|
var sort = accesses
|
.FirstOrDefault(it => it.Access == fromEntity.Access)
|
?.Sort;
|
if (sort.HasValue)
|
{
|
access = accesses
|
.Where(it => it.Sort > sort)
|
.FirstOrDefault();
|
}
|
}
|
return access;
|
}
|
|
/// <summary>
|
/// 获取短信服务
|
/// </summary>
|
/// <param name="access"></param>
|
/// <returns></returns>
|
private ISmsService GetSmsService(EnumSmsAccess access)
|
{
|
ISmsService smsService;
|
switch (access)
|
{
|
case EnumSmsAccess.AliyunSms:
|
smsService = aliyunSmsService;
|
break;
|
case EnumSmsAccess.ChengLiYe:
|
smsService = chengLiYeSmsService;
|
break;
|
default:
|
throw Oops.Oh(EnumErrorCodeType.s510, "不支持的短信通道");
|
}
|
return smsService;
|
}
|
|
/// <summary>
|
/// 发送短信
|
/// </summary>
|
/// <param name="setting"></param>
|
/// <param name="fromEntity"></param>
|
/// <param name="model"></param>
|
/// <param name="templateParam">模板参数</param>
|
/// <param name="cancellationToken">取消令牌</param>
|
/// <returns></returns>
|
/// <exception cref="Oops"></exception>
|
public async Task<SmsLog> Send(
|
SmsSetting setting,
|
SmsLog fromEntity,
|
SendSmsModel model,
|
object templateParam,
|
CancellationToken cancellationToken)
|
{
|
if (setting.IsDisabled)
|
{
|
var entity = new SmsLog
|
{
|
ChannelId = setting.ChannelId,
|
ChannelCreatedUserId = model.ChannelCreatedUserId,
|
Access = EnumSmsAccess.None,
|
PhoneNumber = model.PhoneNumber,
|
TemplateCode = model.TemplateCode.ToString(),
|
TemplateParam = templateParam.ToJson(),
|
Expiry = model.Expiry,
|
Status = EnumSmsStatus.Success,
|
};
|
if (setting.WithoutParams)
|
{
|
UnifyContext.Fill(templateParam);
|
}
|
await rep.InsertNowAsync(entity);
|
return entity;
|
}
|
else
|
{
|
var access = GetSmsAccess(setting, fromEntity);
|
if (access != null)
|
{
|
var smsService = GetSmsService(access.Access);
|
var entity = new SmsLog
|
{
|
ChannelId = setting.ChannelId,
|
ChannelCreatedUserId = model.ChannelCreatedUserId,
|
Access = access.Access,
|
PhoneNumber = model.PhoneNumber,
|
TemplateCode = model.TemplateCode.ToString(),
|
TemplateParam = templateParam.ToJson(),
|
Expiry = model.Expiry,
|
Status = EnumSmsStatus.Wait
|
};
|
await rep.InsertNowAsync(entity);
|
var response = await smsService.SendAsync(access.SignName, model.PhoneNumber, model.TemplateCode, templateParam, cancellationToken);
|
if (response != null)
|
{
|
entity.Status = response.Status;
|
entity.Code = response.Code;
|
entity.Message = response.Message;
|
entity.RequestId = response.RequestId;
|
await rep.UpdateNowAsync(entity);
|
|
if (response.Status == EnumSmsStatus.Fail)
|
{
|
var newEntity = await Send(setting, entity, model, templateParam, cancellationToken);
|
if (newEntity == null) return entity;
|
|
if (setting.WithoutParams)
|
{
|
UnifyContext.Fill(templateParam);
|
}
|
return newEntity;
|
}
|
}
|
if (setting.WithoutParams)
|
{
|
UnifyContext.Fill(templateParam);
|
}
|
return entity;
|
}
|
return null;
|
}
|
}
|
}
|
}
|