Compare commits
38 Commits
| Author | SHA1 | Date |
|---|---|---|
|
|
8c38a8e2e5 | 3 months ago |
|
|
cb5071bffb | 3 months ago |
|
|
474d481b5e | 3 months ago |
|
|
24ea9e097e | 3 months ago |
|
|
0d3c190ad7 | 3 months ago |
|
|
55d0c24d99 | 3 months ago |
|
|
bbac947787 | 3 months ago |
|
|
157056efb8 | 3 months ago |
|
|
82264d99fc | 3 months ago |
|
|
a86a5aa087 | 3 months ago |
|
|
f7764bf88a | 3 months ago |
|
|
639a6fb3a4 | 3 months ago |
|
|
1560949db5 | 3 months ago |
|
|
5b924548c0 | 3 months ago |
|
|
109a0ceeac | 3 months ago |
|
|
1b878776b8 | 3 months ago |
|
|
136ff1f656 | 3 months ago |
|
|
94f5feaac8 | 3 months ago |
|
|
241450ddf1 | 3 months ago |
|
|
46a26db1f8 | 3 months ago |
|
|
64c5ca915e | 3 months ago |
|
|
496946447e | 3 months ago |
|
|
8b48feaba4 | 3 months ago |
|
|
9fa91adcfc | 3 months ago |
|
|
70e44379a2 | 3 months ago |
|
|
f5472e4fea | 3 months ago |
|
|
848fd84f84 | 3 months ago |
|
|
c80799db6d | 3 months ago |
|
|
e67dd34993 | 3 months ago |
|
|
ceb68d34e8 | 3 months ago |
|
|
b8808ffee1 | 3 months ago |
|
|
ec3758b0bf | 3 months ago |
|
|
33214a4252 | 3 months ago |
|
|
a392df1ccb | 3 months ago |
|
|
30b6a27ec5 | 3 months ago |
|
|
3b4e13dbd2 | 3 months ago |
|
|
c2d3a2f012 | 3 months ago |
|
|
819d13915c | 3 months ago |
36 changed files with 910 additions and 28547 deletions
@ -0,0 +1,35 @@
|
||||
{ |
||||
"version": "0.2.0", |
||||
"configurations": [ |
||||
{ |
||||
// 使用 IntelliSense 找出 C# 调试存在哪些属性 |
||||
// 将悬停用于现有属性的说明 |
||||
// 有关详细信息,请访问 https://github.com/dotnet/vscode-csharp/blob/main/debugger-launchjson.md。 |
||||
"name": ".NET Core Launch (web)", |
||||
"type": "coreclr", |
||||
"request": "launch", |
||||
"preLaunchTask": "build", |
||||
// 如果已更改目标框架,请确保更新程序路径。 |
||||
"program": "${workspaceFolder}/src/4.apps/ATS.NonCustodial.Admin.Api/bin/Debug/net9.0/ATS.NonCustodial.Admin.Api.dll", |
||||
"args": [], |
||||
"cwd": "${workspaceFolder}/src/4.apps/ATS.NonCustodial.Admin.Api", |
||||
"stopAtEntry": false, |
||||
// 启用在启动 ASP.NET Core 时启动 Web 浏览器。有关详细信息: https://aka.ms/VSCode-CS-LaunchJson-WebBrowser |
||||
"serverReadyAction": { |
||||
"action": "openExternally", |
||||
"pattern": "\\bNow listening on:\\s+(https?://\\S+)" |
||||
}, |
||||
"env": { |
||||
"ASPNETCORE_ENVIRONMENT": "Development" |
||||
}, |
||||
"sourceFileMap": { |
||||
"/Views": "${workspaceFolder}/Views" |
||||
} |
||||
}, |
||||
{ |
||||
"name": ".NET Core Attach", |
||||
"type": "coreclr", |
||||
"request": "attach" |
||||
} |
||||
] |
||||
} |
||||
@ -0,0 +1,41 @@
|
||||
{ |
||||
"version": "2.0.0", |
||||
"tasks": [ |
||||
{ |
||||
"label": "build", |
||||
"command": "dotnet", |
||||
"type": "process", |
||||
"args": [ |
||||
"build", |
||||
"${workspaceFolder}/ATS.NonCustodial.Admin.sln", |
||||
"/property:GenerateFullPaths=true", |
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign" |
||||
], |
||||
"problemMatcher": "$msCompile" |
||||
}, |
||||
{ |
||||
"label": "publish", |
||||
"command": "dotnet", |
||||
"type": "process", |
||||
"args": [ |
||||
"publish", |
||||
"${workspaceFolder}/ATS.NonCustodial.Admin.sln", |
||||
"/property:GenerateFullPaths=true", |
||||
"/consoleloggerparameters:NoSummary;ForceNoAlign" |
||||
], |
||||
"problemMatcher": "$msCompile" |
||||
}, |
||||
{ |
||||
"label": "watch", |
||||
"command": "dotnet", |
||||
"type": "process", |
||||
"args": [ |
||||
"watch", |
||||
"run", |
||||
"--project", |
||||
"${workspaceFolder}/ATS.NonCustodial.Admin.sln" |
||||
], |
||||
"problemMatcher": "$msCompile" |
||||
} |
||||
] |
||||
} |
||||
@ -0,0 +1,45 @@
|
||||
using ATS.NonCustodial.Domain.Shared.AggRootEntities; |
||||
using ATS.NonCustodial.Domain.Shared.Constants; |
||||
using ATS.NonCustodial.Domain.Shared.Enums; |
||||
using ATS.NonCustodial.Shared.Common.Enums; |
||||
using ATS.NonCustodial.Shared.Common.Enums.IM; |
||||
using Microsoft.EntityFrameworkCore; |
||||
using System.ComponentModel.DataAnnotations; |
||||
using System.ComponentModel.DataAnnotations.Schema; |
||||
using System.Numerics; |
||||
|
||||
namespace ATS.NonCustodial.Domain.Entities.Admins |
||||
{ |
||||
[Table("app_sms")] |
||||
public class AppSMS: EntityFull |
||||
{ |
||||
public AppSMS() |
||||
{ } |
||||
|
||||
public AppSMS(long id) |
||||
{ |
||||
Id = id; |
||||
} |
||||
|
||||
public string phone { get; set; } = string.Empty; |
||||
|
||||
public string content { get; set; } = string.Empty; |
||||
|
||||
public string? code { get; set; } |
||||
|
||||
public DateTime sendTime { get; set; } |
||||
|
||||
public DateTime expiresTime { get; set; } |
||||
|
||||
public bool isUsed { get; set; } = false; |
||||
|
||||
public DateTime? useTime { get; set; } |
||||
|
||||
public string ipAddress { get; set; } = string.Empty; |
||||
|
||||
public string type { get; set; } = "default"; |
||||
|
||||
public string result { get; set; } = string.Empty; |
||||
|
||||
} |
||||
} |
||||
@ -0,0 +1,250 @@
|
||||
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 |
||||
{ |
||||
/// <summary> |
||||
/// 短信服务 |
||||
/// </summary> |
||||
[DynamicApi(Area = "admin")] |
||||
public class SMSService : AdminAppServiceBase, ISMSService, IDynamicApi |
||||
{ |
||||
private readonly IEfRepository<AppSMS?, long> _appSMSRepository; |
||||
|
||||
public SMSService( |
||||
IEfRepository<AppSMS?, long> appSMSRepository) |
||||
{ |
||||
_appSMSRepository = appSMSRepository; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 发送验证码 |
||||
/// </summary> |
||||
/// <param name="phone"></param> |
||||
/// <param name="ipAddress"></param> |
||||
/// <param name="type"></param> |
||||
/// <returns></returns> |
||||
[HttpGet] |
||||
[AllowAnonymous] |
||||
public async Task<IResultOutput> 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); |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 校验验证码 |
||||
/// </summary> |
||||
/// <param name="phoneNumber"></param> |
||||
/// <param name="code"></param> |
||||
/// <param name="type"></param> |
||||
/// <returns></returns> |
||||
public async Task<bool> 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; |
||||
} |
||||
|
||||
/// <summary> |
||||
/// 发送前校验 |
||||
/// </summary> |
||||
/// <param name="phone"></param> |
||||
/// <returns></returns> |
||||
public async Task<bool> 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 |
||||
|
||||
/// <summary> |
||||
/// 发送短信 |
||||
/// </summary> |
||||
/// <param name="phone"></param> |
||||
/// <param name="msg"></param> |
||||
/// <param name="expires"></param> |
||||
/// <returns></returns> |
||||
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 |
||||
} |
||||
} |
||||
@ -0,0 +1,36 @@
|
||||
using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Input; |
||||
using ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth.Output; |
||||
using ATS.NonCustodial.Domain.Shared.Enums; |
||||
using ATS.NonCustodial.Shared.Captcha.Dto; |
||||
using ATS.NonCustodial.Shared.Common.Dtos; |
||||
using ATS.NonCustodial.Shared.Common.UnifiedResults; |
||||
|
||||
namespace ATS.NonCustodial.Application.Contracts.Interfaces.Admins.SMS |
||||
{ |
||||
public interface ISMSService |
||||
{ |
||||
/// <summary> |
||||
/// 发送验证码 |
||||
/// </summary> |
||||
/// <param name="phone"></param> |
||||
/// <returns></returns> |
||||
Task<IResultOutput> SendCheckCodeSMS(string phone, string ipAddress = "", string type = "CheckCode"); |
||||
|
||||
/// <summary> |
||||
/// 校验验证码 |
||||
/// </summary> |
||||
/// <param name="phoneNumber"></param> |
||||
/// <param name="code"></param> |
||||
/// <param name="type"></param> |
||||
/// <returns></returns> |
||||
Task<bool> CheckCodeAsync(string phoneNumber, string code, string type = "default"); |
||||
|
||||
|
||||
/// <summary> |
||||
/// 发送前校验 |
||||
/// </summary> |
||||
/// <param name="phone"></param> |
||||
/// <returns></returns> |
||||
Task<bool> CanSendCodeAsync(string phone); |
||||
} |
||||
} |
||||
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
File diff suppressed because it is too large
Load Diff
Loading…
Reference in new issue