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; } } } }