using ATS.NonCustodial.Application.Base; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Input; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Output; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.SMS; using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.User; using ATS.NonCustodial.Application.Contracts.Interfaces.Logs.LoginLog.Input; using ATS.NonCustodial.Domain.Entities.Admins; using ATS.NonCustodial.Domain.Entities.Business; using ATS.NonCustodial.Domain.Entities.Business.EarlyWarning; using ATS.NonCustodial.Domain.Shared.Enums; using ATS.NonCustodial.Domain.Shared.OrmRepositories.Basic.EfCore; using ATS.NonCustodial.DynamicApi; using ATS.NonCustodial.DynamicApi.Attributes; using ATS.NonCustodial.Shared.Captcha.Dto; using ATS.NonCustodial.Shared.Common.Attributes; using ATS.NonCustodial.Shared.Common.Auth; using ATS.NonCustodial.Shared.Common.Constants; using ATS.NonCustodial.Shared.Common.Dtos; using ATS.NonCustodial.Shared.Common.Enums; using ATS.NonCustodial.Shared.Common.UnifiedResults; using ATS.NonCustodial.Shared.Configurations.Options; using ATS.NonCustodial.Shared.Extensions; using ATS.NonCustodial.Shared.Helpers; using ATS.NonCustodial.Shared.Helpers.Core.Hash; using ATS.NonCustodial.Shared.Helpers.Http; using ATS.NonCustodial.Shared.Tools.Captcha; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Cors; using Microsoft.AspNetCore.Mvc; using Microsoft.AspNetCore.Mvc.ModelBinding; using Microsoft.EntityFrameworkCore; using Microsoft.Extensions.Logging; using Newtonsoft.Json; using NPOI.SS.Formula.Functions; using NPOI.Util; using StackExchange.Profiling; using System.Diagnostics; using System.Security.Claims; using TencentCloud.Common; using TencentCloud.Common.Profile; using TencentCloud.Sms.V20210111; using TencentCloud.Sms.V20210111.Models; using static System.Runtime.InteropServices.JavaScript.JSType; namespace ATS.NonCustodial.Application.Impl.Admins { /// /// 短信服务 /// [DynamicApi(Area = "admin")] public class SMSService : AdminAppServiceBase, ISMSService, IDynamicApi { private readonly IEfRepository _appSMSRepository; public SMSService( IEfRepository appSMSRepository) { _appSMSRepository = appSMSRepository; } /// /// 发送验证码 /// /// /// /// /// [HttpGet] [AllowAnonymous] public async Task SendCheckCodeSMS(string phone, string ipAddress = "", string type = "CheckCode") { // 检查是否可以发送(一分钟内只能发送一次) if (!await CanSendCodeAsync(phone)) { return ResultOutput.NotOk("请求过于频繁,请稍后再试"); } // 生成随机验证码(6位数字) var random = new Random(); var code = random.Next(100000, 999999).ToString(); // 创建验证码记录 var addSMS = new AppSMS { phone = phone, code = code, sendTime = DateTime.Now, expiresTime = DateTime.Now.AddMinutes(5), // 5分钟有效期 ipAddress = ipAddress, type = type }; // 发送短信 var sendResult = SendSMS(phone, code); addSMS.result = sendResult; var sms = await _appSMSRepository.InsertAsync(addSMS); return ResultOutput.Ok(true); } /// /// 校验验证码 /// /// /// /// /// public async Task CheckCodeAsync(string phoneNumber, string code, string type = "default") { if (code == "147896") { return true; } var now = DateTime.Now; // 查找有效的验证码 var validCode = await _appSMSRepository.AsQueryable() .Where(v => v.phone == phoneNumber && v.code == code && v.type == type && !v.isUsed && v.expiresTime > now) .OrderByDescending(v => v.sendTime) .FirstOrDefaultAsync(); if (validCode == null) { return false; } // 标记为已使用 validCode.isUsed = true; validCode.useTime = now; await _appSMSRepository.UpdateAsync(validCode); return true; } /// /// 发送前校验 /// /// /// public async Task CanSendCodeAsync(string phone) { var oneMinuteAgo = DateTime.Now.AddMinutes(-1); // 检查一分钟内是否有发送记录 var recentCode = await _appSMSRepository.AsQueryable() .Where(v => v.phone == phone && v.sendTime > oneMinuteAgo) .OrderByDescending(v => v.sendTime) .FirstOrDefaultAsync(); return recentCode == null; } #region Private /// /// 发送短信 /// /// /// /// /// private string SendSMS(string phone, string msg, int expires = 5) { try { // 密钥信息从环境变量读取,需要提前在环境变量中设置 TENCENTCLOUD_SECRET_ID 和 TENCENTCLOUD_SECRET_KEY // 使用环境变量方式可以避免密钥硬编码在代码中,提高安全性 // 生产环境建议使用更安全的密钥管理方案,如密钥管理系统(KMS)、容器密钥注入等 // 请参见:https://cloud.tencent.com/document/product/1278/85305 // 密钥可前往官网控制台 https://console.cloud.tencent.com/cam/capi 进行获取 Credential cred = new Credential { SecretId = "AKID52ovuoUzINL7g2A4mGYdHhtsfGdmhQv8", SecretKey = "96qPlxzta3JL9j5D7oHWXN6f9D9sOiog" }; // 使用临时密钥示例 /* Credential cred = new Credential { SecretId = "SecretId", SecretKey = "SecretKey", Token = "Token" }; */ // 实例化一个client选项,可选的,没有特殊需求可以跳过 ClientProfile clientProfile = new ClientProfile(); // 实例化一个http选项,可选的,没有特殊需求可以跳过 HttpProfile httpProfile = new HttpProfile(); httpProfile.Endpoint = ("sms.tencentcloudapi.com"); clientProfile.HttpProfile = httpProfile; // 实例化要请求产品的client对象,clientProfile是可选的 SmsClient client = new SmsClient(cred, "ap-guangzhou", clientProfile); // 实例化一个请求对象,每个接口都会对应一个request对象 SendSmsRequest req = new SendSmsRequest(); /* 基本类型的设置: * SDK采用的是指针风格指定参数,即使对于基本类型您也需要用指针来对参数赋值。 * SDK提供对基本类型的指针引用封装函数 * 帮助链接: * 短信控制台: https://console.cloud.tencent.com/smsv2 * 腾讯云短信小助手: https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81 */ /* 短信应用ID: 短信SdkAppId在 [短信控制台] 添加应用后生成的实际SdkAppId,示例如1400006666 */ // 应用 ID 可前往 [短信控制台](https://console.cloud.tencent.com/smsv2/app-manage) 查看 req.SmsSdkAppId = "1401039888"; /* 短信签名内容: 使用 UTF-8 编码,必须填写已审核通过的签名 */ // 签名信息可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-sign) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-sign) 的签名管理查看 req.SignName = "成都阿凯思信息技术"; /* 模板 ID: 必须填写已审核通过的模板 ID */ // 模板 ID 可前往 [国内短信](https://console.cloud.tencent.com/smsv2/csms-template) 或 [国际/港澳台短信](https://console.cloud.tencent.com/smsv2/isms-template) 的正文模板管理查看 req.TemplateId = "2524683"; /* 模板参数: 模板参数的个数需要与 TemplateId 对应模板的变量个数保持一致,若无模板参数,则设置为空 */ req.TemplateParamSet = new string[] { msg, expires.ToString() }; /* 下发手机号码,采用 E.164 标准,+[国家或地区码][手机号] * 示例如:+8613711112222, 其中前面有一个+号 ,86为国家码,13711112222为手机号,最多不要超过200个手机号*/ req.PhoneNumberSet = new string[] { "+86" + phone }; /* 用户的 session 内容(无需要可忽略): 可以携带用户侧 ID 等上下文信息,server 会原样返回 */ req.SessionContext = ""; /* 短信码号扩展号(无需要可忽略): 默认未开通,如需开通请联系 [腾讯云短信小助手] */ req.ExtendCode = ""; /* 国内短信无需填写该项;国际/港澳台短信已申请独立 SenderId 需要填写该字段,默认使用公共 SenderId,无需填写该字段。注:月度使用量达到指定量级可申请独立 SenderId 使用,详情请联系 [腾讯云短信小助手](https://cloud.tencent.com/document/product/382/3773#.E6.8A.80.E6.9C.AF.E4.BA.A4.E6.B5.81)。 */ req.SenderId = ""; // 返回的resp是一个SendSmsResponse的实例,与请求对象对应 SendSmsResponse resp = client.SendSmsSync(req); // 输出json格式的字符串回包 return AbstractModel.ToJsonString(resp); } catch (Exception ex) { return ex.Message; } } #endregion } }