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
{
///
/// 短信工具
///
public class SmsUtils
{
private readonly IRepository rep;
private readonly IRepository repSmsSetting;
private readonly AliyunSmsService aliyunSmsService;
private readonly ChengLiYeSmsService chengLiYeSmsService;
public SmsUtils(
IRepository rep,
IRepository repSmsSetting,
AliyunSmsService aliyunSmsService,
ChengLiYeSmsService chengLiYeSmsService)
{
this.rep = rep;
this.repSmsSetting = repSmsSetting;
this.aliyunSmsService = aliyunSmsService;
this.chengLiYeSmsService = chengLiYeSmsService;
}
///
/// 发送短信
///
///
///
///
///
public async Task 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;
}
///
/// 发送验证码短信
///
///
///
///
public async Task 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);
}
///
/// 校验验证码
///
///
///
///
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);
}
///
/// 校验操作频繁
///
///
///
///
///
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);
}
}
///
/// 获取短信通道
///
///
///
///
private EnumSmsAccess? GetSmsAccess(SmsSetting setting, SmsLog fromEntity)
{
var accesses = setting.Accesses
.OrderBy(it => it.Sort)
.Where(it => !it.IsDisabled)
.ToList();
EnumSmsAccess? access = null;
if (fromEntity == null)
{
access = accesses
.FirstOrDefault()
?.Access;
}
else
{
var sort = accesses
.FirstOrDefault(it => it.Access == fromEntity.Access)
?.Sort;
if (sort.HasValue)
{
access = accesses
.Where(it => it.Sort > sort)
.FirstOrDefault()
?.Access;
}
}
return access;
}
///
/// 获取短信服务
///
///
///
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;
}
///
/// 发送短信
///
///
///
///
/// 模板参数
/// 取消令牌
///
///
public async Task 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.HasValue)
{
var smsService = GetSmsService(access.Value);
var entity = new SmsLog
{
ChannelId = setting.ChannelId,
ChannelCreatedUserId = model.ChannelCreatedUserId,
Access = access.Value,
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(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;
}
}
}
}