diff --git a/src/1.datas/ATS.NonCustodial.Domain/Entities/Admins/AppSMS.cs b/src/1.datas/ATS.NonCustodial.Domain/Entities/Admins/AppSMS.cs new file mode 100644 index 0000000..f2a49d4 --- /dev/null +++ b/src/1.datas/ATS.NonCustodial.Domain/Entities/Admins/AppSMS.cs @@ -0,0 +1,42 @@ +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"; + } +} diff --git a/src/2.services/ATS.NonCustodial.Application/Impl/Admins/AuthService.cs b/src/2.services/ATS.NonCustodial.Application/Impl/Admins/AuthService.cs index 7f1df81..ce7f938 100644 --- a/src/2.services/ATS.NonCustodial.Application/Impl/Admins/AuthService.cs +++ b/src/2.services/ATS.NonCustodial.Application/Impl/Admins/AuthService.cs @@ -35,6 +35,7 @@ using NPOI.Util; using StackExchange.Profiling; using System.Diagnostics; using System.Security.Claims; +using static System.Runtime.InteropServices.JavaScript.JSType; namespace ATS.NonCustodial.Application.Impl.Admins { @@ -387,6 +388,17 @@ namespace ATS.NonCustodial.Application.Impl.Admins return result; } + /// + /// 根据身份证号获取电话号码 + /// + /// + /// + public async Task GetPhoneByIDCard(string idCard) + { + var user = await _appUserRepository.FindAsync(a => a.ChatPersonType == ChatPersonTypeEnum.SupervisedPerson && a.IdCard == idCard); + return ResultOutput.Ok(user?.Phone); + } + #region Private /// diff --git a/src/2.services/ATS.NonCustodial.Application/Impl/Admins/SMSService.cs b/src/2.services/ATS.NonCustodial.Application/Impl/Admins/SMSService.cs new file mode 100644 index 0000000..c868967 --- /dev/null +++ b/src/2.services/ATS.NonCustodial.Application/Impl/Admins/SMSService.cs @@ -0,0 +1,170 @@ +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 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.UtcNow, + expiresTime = DateTime.UtcNow.AddMinutes(5), // 5分钟有效期 + ipAddress = ipAddress, + type = type + }; + + // 发送短信 + var sendResult = SendSMS(phone, code); + if (!sendResult) + { + return ResultOutput.NotOk("短信发送失败,请稍后重试"); + } + + var sms = await _appSMSRepository.InsertAsync(addSMS); + + return ResultOutput.Ok(true); + } + + /// + /// 校验验证码 + /// + /// + /// + /// + /// + public async Task CheckCodeAsync(string phoneNumber, string code, string type = "default") + { + var now = DateTime.UtcNow; + + // 查找有效的验证码 + 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.UtcNow.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 bool SendSMS(string phone, string msg) + { + return true; + } + + #endregion + } +} diff --git a/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/Auth/IAuthService.cs b/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/Auth/IAuthService.cs index 74a736e..9c56d63 100644 --- a/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/Auth/IAuthService.cs +++ b/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/Auth/IAuthService.cs @@ -79,5 +79,12 @@ namespace ATS.NonCustodial.Application.Contracts.Interfaces.Admins.Auth /// 用户信息 /// Task GetToken(AuthLoginOutput? user); + + /// + /// 根据身份证号获取电话号码 + /// + /// + /// + Task GetPhoneByIDCard(string idCard); } } \ No newline at end of file diff --git a/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/SMS/ISMSService.cs b/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/SMS/ISMSService.cs new file mode 100644 index 0000000..f9d3fcd --- /dev/null +++ b/src/3.contracts/ATS.NonCustodial.Application.Contracts/Interfaces/Admins/SMS/ISMSService.cs @@ -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 + { + /// + /// 发送验证码 + /// + /// + /// + Task SendCheckCodeSMS(string phone, string ipAddress = "", string type = "CheckCode"); + + /// + /// 校验验证码 + /// + /// + /// + /// + /// + Task CheckCodeAsync(string phoneNumber, string code, string type = "default"); + + + /// + /// 发送前校验 + /// + /// + /// + Task CanSendCodeAsync(string phone); + } +}