diff --git a/framework/NPin.Framework.Mapster/NPin.Framework.Mapster.csproj b/framework/NPin.Framework.Mapster/NPin.Framework.Mapster.csproj index 483ab23..cfa437a 100644 --- a/framework/NPin.Framework.Mapster/NPin.Framework.Mapster.csproj +++ b/framework/NPin.Framework.Mapster/NPin.Framework.Mapster.csproj @@ -3,6 +3,8 @@ + + diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/ConfigConst.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/ConfigConst.cs new file mode 100644 index 0000000..9fcbd61 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Consts/ConfigConst.cs @@ -0,0 +1,13 @@ +namespace NPin.Framework.Upms.Domain.Shared.Consts; + +public class ConfigConst +{ + /// + /// 系统配置前缀 + /// + public const string SysConfigPrefix = "Sys"; + + public const string AliyunConfigKey = "Aliyun"; + public const string TencentConfigKey = "Tencent"; + public const string SmsConfigKey = "Sms"; +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/SmsEnum.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/SmsEnum.cs new file mode 100644 index 0000000..2ef8710 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Enums/SmsEnum.cs @@ -0,0 +1,22 @@ +using System.ComponentModel; + +namespace NPin.Framework.Upms.Domain.Shared.Enums; + +/// +/// 短信提供商枚举 +/// +public enum SmsProviderEnum +{ + Aliyun, + Tencent +} + +/// +/// 短信类型枚举 +/// +public enum SmsTypeEnum +{ + [Description("登录")] Login, + [Description("注册")] Register, + [Description("找回密码(重置密码)")] ResetPassword, +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Model/AliyunConfigModel.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/AliyunConfigModel.cs new file mode 100644 index 0000000..0bfa438 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/AliyunConfigModel.cs @@ -0,0 +1,24 @@ +namespace NPin.Framework.Upms.Domain.Shared.Model; + +public class AliyunConfigModel +{ + /// + /// 访问密钥Key + /// + public string AccessKeyId { get; set; } + + /// + /// 访问密钥 + /// + public string AccessKeySecret { get; set; } + + /// + /// 默认 区域ID + /// + public string RegionId { get; set; } + + /// + /// 默认 短信访问 端点 + /// + public string SmsEndpoint { get; set; } = "dysmsapi.aliyuncs.com"; +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Model/SmsConfigModel.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/SmsConfigModel.cs new file mode 100644 index 0000000..34019fe --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/SmsConfigModel.cs @@ -0,0 +1,54 @@ +using NPin.Framework.Upms.Domain.Shared.Enums; + +namespace NPin.Framework.Upms.Domain.Shared.Model; + +public class SmsConfigModel +{ + /// + /// 是否启用(总控) + /// + public bool Enabled { get; set; } = false; + + /// + /// 设定集 + /// + public Dictionary Settings { get; set; } +} + +public class SmsSettings +{ + /// + /// 是否启用 + /// + public bool Enabled { get; set; } = false; + + /// + /// 服务提供商 + /// + public SmsProviderEnum Provider { get; set; } + + /// + /// 区域 + /// + public string Region { get; set; } + + /// + /// 短信访问 端点 + /// + public string Endpoint { get; set; } + + /// + /// 短信签名名称 + /// + public string SignName { get; set; } + + /// + /// 短信模板Code + /// + public string TemplateCode { get; set; } + + /// + /// 过期时间,单位:秒 + /// + public int Expires { get; set; } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Model/TencentConfigModel.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/TencentConfigModel.cs new file mode 100644 index 0000000..6bc6739 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/Model/TencentConfigModel.cs @@ -0,0 +1,31 @@ +using Newtonsoft.Json; + +namespace NPin.Framework.Upms.Domain.Shared.Model; + +public class TencentConfigModel +{ + /// + /// 访问密钥Key + /// + public string SecretId { get; set; } + + /// + /// 访问密钥 + /// + public string SecretKey { get; set; } + + /// + /// 默认区域 + /// + public string Region { get; set; } + + /// + /// 默认短信访问 端点 + /// + public string SmsEndpoint { get; set; } = "sms.tencentcloudapi.com"; + + /// + /// 短信SdkAppId + /// + public string SmsSdkAppId { get; set; } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj b/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj index 8fbf1da..86e96fc 100644 --- a/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj +++ b/module/upms/NPin.Framework.Upms.Domain.Shared/NPin.Framework.Upms.Domain.Shared.csproj @@ -7,8 +7,5 @@ - - - diff --git a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs b/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs deleted file mode 100644 index 24de15c..0000000 --- a/module/upms/NPin.Framework.Upms.Domain.Shared/Options/AliyunOptions.cs +++ /dev/null @@ -1,17 +0,0 @@ -namespace NPin.Framework.Upms.Domain.Shared.Options; - -/// -/// 阿里云SDK相关配置 -/// -public class AliyunOptions -{ - public string AccessKeyId { get; set; } - public string AccessKeySecret { get; set; } - public AliyunSms Sms { get; set; } -} - -public class AliyunSms -{ - public string SignName { get; set; } - public string TemplateCode { get; set; } -} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Entities/AnnouncementEntity.cs b/module/upms/NPin.Framework.Upms.Domain/Entities/AnnouncementEntity.cs new file mode 100644 index 0000000..8d55e4b --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Entities/AnnouncementEntity.cs @@ -0,0 +1,32 @@ +using NPin.Framework.SqlSugarCore.Abstractions.Data; +using SqlSugar; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; + +namespace NPin.Framework.Upms.Domain.Entities; + +[SugarTable("announcement", "系统公告表")] +public class AnnouncementEntity : Entity, ISoftDelete, IAuditedObject, IOrderNum, IEnabled +{ + [SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; } + + [SugarColumn(ColumnDescription = "公告标题")] + public string Title { get; set; } + + [SugarColumn(ColumnDescription = "公告分类")] + public string Category { get; set; } + + [SugarColumn(ColumnDescription = "公告行为")] + public string Action { get; set; } + + [SugarColumn(ColumnDescription = "公告内容", ColumnDataType = StaticConfig.CodeFirst_BigString)] + public string Content { get; set; } + + public bool IsDeleted { get; } + public DateTime CreationTime { get; } + public Guid? CreatorId { get; } + public DateTime? LastModificationTime { get; } + public Guid? LastModifierId { get; } + public int OrderNum { get; set; } + public bool IsEnabled { get; set; } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Entities/ConfigEntity.cs b/module/upms/NPin.Framework.Upms.Domain/Entities/ConfigEntity.cs new file mode 100644 index 0000000..1a11029 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Entities/ConfigEntity.cs @@ -0,0 +1,34 @@ +using Mapster; +using NPin.Framework.SqlSugarCore.Abstractions.Data; +using SqlSugar; +using Volo.Abp.Auditing; +using Volo.Abp.Domain.Entities; + +namespace NPin.Framework.Upms.Domain.Entities; + +[SugarTable("Config", "系统配置表")] +[SugarIndex($"index_{nameof(Key)}", nameof(Key), OrderByType.Asc, true)] +public class ConfigEntity : Entity, IEnabled, IOrderNum, ISoftDelete, IAuditedObject +{ + [SugarColumn(IsPrimaryKey = true)] public override Guid Id { get; protected set; } + + [SugarColumn(ColumnDescription = "配置名称")] + public string Name { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "配置键")] + public string Key { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "配置值")] + public string Value { get; set; } = string.Empty; + + [SugarColumn(ColumnDescription = "配置描述")] + public string? Remark { get; set; } = string.Empty; + + public bool IsEnabled { get; set; } + public int OrderNum { get; set; } + public bool IsDeleted { get; } + public DateTime CreationTime { get; } + public Guid? CreatorId { get; } + public DateTime? LastModificationTime { get; } + public Guid? LastModifierId { get; } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Managers/RoleManager.cs b/module/upms/NPin.Framework.Upms.Domain/Managers/RoleManager.cs new file mode 100644 index 0000000..446a558 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Managers/RoleManager.cs @@ -0,0 +1,15 @@ +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.Upms.Domain.Entities; +using Volo.Abp.Domain.Services; + +namespace NPin.Framework.Upms.Domain.Managers; + +public class RoleManager: DomainService +{ + private ISqlSugarRepository _repository; + + public RoleManager(ISqlSugarRepository repository) + { + _repository = repository; + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Managers/SmsManager.cs b/module/upms/NPin.Framework.Upms.Domain/Managers/SmsManager.cs new file mode 100644 index 0000000..2c698d1 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Managers/SmsManager.cs @@ -0,0 +1,129 @@ +using AlibabaCloud.OpenApiClient.Models; +using AlibabaCloud.SDK.Dysmsapi20170525; +using Microsoft.Extensions.Logging; +using Newtonsoft.Json; +using NPin.Framework.Upms.Domain.Repositories; +using NPin.Framework.Upms.Domain.Shared.Enums; +using NPin.Framework.Upms.Domain.Shared.Model; +using NPin.Framework.Upms.Domain.Shared.Options; +using TencentCloud.Common; +using TencentCloud.Common.Profile; +using TencentCloud.Sms.V20210111; +using TencentCloud.Sms.V20210111.Models; +using Volo.Abp.Caching; +using Volo.Abp.Domain.Services; + +namespace NPin.Framework.Upms.Domain.Managers; + +public class SmsManager : DomainService, ISms +{ + private ILogger _logger; + private IConfigRepository _configRepository; + private IDistributedCache _cache; + + public SmsManager(ILogger logger, IConfigRepository configRepository) + { + _logger = logger; + _configRepository = configRepository; + } + + public async Task SendSmsAsync(SmsTypeEnum smsType, string phoneNumbers, object templateParam) + { + + try + { + var smsSettings = SmsOptions.Config[smsType]; + if (!smsSettings.Enabled) + { + return; + } + + switch (smsSettings.Provider) + { + case SmsProviderEnum.Aliyun: + await SendAliyunSmsAsync(smsSettings, phoneNumbers, templateParam); + break; + case SmsProviderEnum.Tencent: + await SendTencentSmsAsync(smsSettings, phoneNumbers, templateParam); + break; + default: + throw new Exception("未实现该服务提供商"); + } + } + catch (Exception ex) + { + _logger.LogError(ex, $"短信发送失败: {ex.Message}"); + throw new UserFriendlyException($"短信发送失败: {ex.Message}"); + } + } + + private async Task SendTencentSmsAsync(SmsSettings settings, string phoneNumbers, + object templateParam) + { + var client = SmsClientProvider.CreateClient(TencentOptions, settings); + + var sendSmsRequest = new SendSmsRequest + { + PhoneNumberSet = phoneNumbers.Split(','), + SignName = settings.SignName, + TemplateId = settings.TemplateCode, + TemplateParamSet = templateParam as string[] + }; + + var response = await client.SendSms(sendSmsRequest); + } + + private async Task SendAliyunSmsAsync(SmsSettings settings, string phoneNumbers, + object templateParam) + { + var client = SmsClientProvider.CreateClient(AliyunOptions, settings); + + var sendSmsRequest = new AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest + { + PhoneNumbers = phoneNumbers, + SignName = settings.SignName, + TemplateCode = settings.TemplateCode, + TemplateParam = JsonConvert.SerializeObject(templateParam) + }; + + var response = await client.SendSmsAsync(sendSmsRequest); + } +} + +public static class SmsClientProvider +{ + public static Client CreateClient(AliyunOptions options, SmsSettings settings) + { + var config = new Config() + { + AccessKeyId = options.AccessKeyId, + AccessKeySecret = options.AccessKeySecret, + Endpoint = settings.Endpoint, + RegionId = settings.RegionId, + }; + + return new Client(config); + } + + public static SmsClient CreateClient(TencentOptions options, SmsSettings settings) + { + var cred = new Credential + { + SecretId = options.SecretId, + SecretKey = options.SecretKey, + }; + + var httpProfile = new HttpProfile + { + Endpoint = settings.Endpoint, + }; + + var clientProfile = new ClientProfile() + { + HttpProfile = httpProfile + }; + + + return new SmsClient(cred, settings.RegionId, clientProfile); + } +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs b/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs index 4ab292e..6f41c0c 100644 --- a/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs +++ b/module/upms/NPin.Framework.Upms.Domain/NPinFrameworkUpmsDomainModule.cs @@ -3,7 +3,6 @@ using NPin.Framework.Caching.FreeRedis; using NPin.Framework.Upms.Domain.Authorization; using NPin.Framework.Upms.Domain.OperLog; using NPin.Framework.Upms.Domain.Shared; -using NPin.Framework.Upms.Domain.Shared.Options; using Volo.Abp.AspNetCore.SignalR; using Volo.Abp.Caching; using Volo.Abp.Domain; @@ -29,8 +28,5 @@ public class NPinFrameworkUpmsDomainModule : AbpModule opts.Filters.Add(); opts.Filters.Add(); }); - - // 配置短信 - Configure(configuration.GetSection(nameof(AliyunOptions))); } } \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Repositories/IConfigRepository.cs b/module/upms/NPin.Framework.Upms.Domain/Repositories/IConfigRepository.cs new file mode 100644 index 0000000..905eb32 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Repositories/IConfigRepository.cs @@ -0,0 +1,9 @@ +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.Upms.Domain.Entities; + +namespace NPin.Framework.Upms.Domain.Repositories; + +public interface IConfigRepository : ISqlSugarRepository +{ + Task GetSingleByKeyAsync(string key) where TDestination : new(); +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.Domain/Sms/ISms.cs b/module/upms/NPin.Framework.Upms.Domain/Sms/ISms.cs new file mode 100644 index 0000000..0ac1525 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.Domain/Sms/ISms.cs @@ -0,0 +1,11 @@ +using NPin.Framework.Upms.Domain.Shared.Enums; + +namespace NPin.Framework.Upms.Domain.Managers; + +/// +/// 短信接口 +/// +public interface ISms +{ + Task SendSmsAsync(SmsTypeEnum smsType, string phoneNumbers, object templateParam); +} \ No newline at end of file diff --git a/module/upms/NPin.Framework.Upms.SqlSugarCore/ConfigRepository.cs b/module/upms/NPin.Framework.Upms.SqlSugarCore/ConfigRepository.cs new file mode 100644 index 0000000..2d6de93 --- /dev/null +++ b/module/upms/NPin.Framework.Upms.SqlSugarCore/ConfigRepository.cs @@ -0,0 +1,30 @@ +using Newtonsoft.Json; +using NPin.Framework.SqlSugarCore.Abstractions; +using NPin.Framework.SqlSugarCore.Repositories; +using NPin.Framework.Upms.Domain.Entities; +using NPin.Framework.Upms.Domain.Repositories; +using Volo.Abp.DependencyInjection; + +namespace NPin.Framework.Upms.SqlSugarCore; + +public class ConfigRepository : SqlSugarRepository, IConfigRepository, ITransientDependency +{ + public ConfigRepository(ISugarDbContextProvider sugarDbContextProvider) : base( + sugarDbContextProvider) + { + } + + public async Task GetSingleByKeyAsync(string key) where TDestination : new() + { + var config = await DbQueryable + .Where(e => e.Key.Equals(key)) + .FirstAsync(); + if (config is null) + { + return new TDestination(); + } + + var ret = JsonConvert.DeserializeObject(config.Value); + return ret ?? new TDestination(); + } +} \ No newline at end of file