添加发送验证码接口
This commit is contained in:
parent
0e690bba7f
commit
e081d46f0d
|
|
@ -0,0 +1,39 @@
|
|||
using CloudGaming.Api.Base;
|
||||
using CloudGaming.Code.Account;
|
||||
using CloudGaming.Code.AppExtend;
|
||||
using CloudGaming.Code.DataAccess;
|
||||
using CloudGaming.Code.MiddlewareExtend;
|
||||
using CloudGaming.DtoModel.Account;
|
||||
using CloudGaming.GameModel.Db.Db_Ext;
|
||||
|
||||
using HuanMeng.DotNetCore.AttributeExtend;
|
||||
using HuanMeng.DotNetCore.Base;
|
||||
|
||||
using HuanMeng.DotNetCore.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
namespace CloudGaming.Api.Controllers;
|
||||
|
||||
/// <summary>
|
||||
/// 账号管理
|
||||
/// </summary>
|
||||
public class AccountController : CloudGamingControllerBase
|
||||
{
|
||||
public AccountController(IServiceProvider _serviceProvider) : base(_serviceProvider)
|
||||
{
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送验证码
|
||||
/// </summary>
|
||||
/// <param name="phoneNumber"></param>
|
||||
/// <returns></returns>
|
||||
[HttpPost]
|
||||
[Message("发送成功")]
|
||||
public async Task<bool> SendPhoneNumber([FromBody] PhoneNumberRequest phoneNumber)
|
||||
{
|
||||
AccountBLL account = new AccountBLL(ServiceProvider);
|
||||
return await account.SendPhoneNumber(phoneNumber.PhoneNumber);
|
||||
}
|
||||
}
|
||||
|
|
@ -3,6 +3,7 @@ using CloudGaming.Code.Config;
|
|||
using CloudGaming.DtoModel;
|
||||
using CloudGaming.GameModel.Db.Db_Ext;
|
||||
|
||||
using HuanMeng.DotNetCore.AttributeExtend;
|
||||
using HuanMeng.DotNetCore.Base;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
|
|
@ -24,6 +25,7 @@ namespace CloudGaming.Api.Controllers
|
|||
/// </summary>
|
||||
/// <returns></returns>
|
||||
[HttpGet]
|
||||
[Message("发送成功")]
|
||||
public async Task<AppConfigDto> GetAppConfigAsync()
|
||||
{
|
||||
AppConfigBLL appConfigBLL = new AppConfigBLL(ServiceProvider);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ using Microsoft.AspNetCore.Mvc.Infrastructure;
|
|||
using Microsoft.Extensions.Options;
|
||||
using CloudGaming.GameModel.Db.Db_Ext;
|
||||
using CloudGaming.Code.MiddlewareExtend;
|
||||
using CloudGaming.Code.Filter;
|
||||
var builder = WebApplication.CreateBuilder(args);
|
||||
#region 日志
|
||||
// Add services to the container.
|
||||
|
|
@ -51,6 +52,7 @@ builder.Services.AddControllers(options =>
|
|||
{
|
||||
// 添加自定义的 ResultFilter 到全局过滤器中
|
||||
options.Filters.Add<CustomResultFilter>();
|
||||
options.Filters.Add<CustomExceptionFilter>();
|
||||
})
|
||||
.AddNewtonsoftJson(options =>
|
||||
{
|
||||
|
|
@ -68,7 +70,9 @@ builder.Services.AddControllers(options =>
|
|||
//options.SerializerSettings.Converters.Add()
|
||||
// 其他配置...
|
||||
});
|
||||
builder.Services.AddSingleton<ObjectResultExecutor, CustomObjectResultExecutor>();
|
||||
//CustomResultFilter
|
||||
//builder.Services.AddSingleton<ObjectResultExecutor, CustomObjectResultExecutor>();
|
||||
|
||||
#endregion
|
||||
// Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle
|
||||
builder.Services.AddEndpointsApiExplorer();
|
||||
|
|
|
|||
76
src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs
Normal file
76
src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
using CloudGaming.Code.Sms;
|
||||
|
||||
using HuanMeng.DotNetCore.Utility;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.Code.Account
|
||||
{
|
||||
/// <summary>
|
||||
/// 账号操作
|
||||
/// </summary>
|
||||
public class AccountBLL : CloudGamingBase
|
||||
{
|
||||
public AccountBLL(IServiceProvider serviceProvider) : base(serviceProvider)
|
||||
{
|
||||
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 发送手机号码
|
||||
/// </summary>
|
||||
/// <param name="PhoneNumber"></param>
|
||||
/// <returns></returns>
|
||||
public async Task<bool> SendPhoneNumber(string PhoneNumber)
|
||||
{
|
||||
if (!PhoneNumberValidator.IsPhoneNumber(PhoneNumber))
|
||||
{
|
||||
throw new MessageException(ResonseCode.PhoneNumberException, "手机号格式错误");
|
||||
}
|
||||
var day = int.Parse(DateTime.Now.ToString("yyyyMMdd"));
|
||||
var smsCount = await Dao.DaoExt.Context.T_Sms_Log.Where(it => it.SendTimeDay == day && it.PhoneNumber == PhoneNumber).CountAsync();
|
||||
if (smsCount >= 5)
|
||||
{
|
||||
throw new MessageException(ResonseCode.PhoneNumberMaxException, "当日发送以达到上限");
|
||||
}
|
||||
var phoneNumberCache = RedisCache.StringGetAsync($"App:sms:{PhoneNumber}");
|
||||
var verificationCode = new Random().Next(1000, 9999).ToString();
|
||||
var sms = AppConfig.AliyunConfig.GetPhoneNumberVerificationService();
|
||||
bool isSend = false;
|
||||
string exMsg = "";
|
||||
try
|
||||
{
|
||||
isSend = await sms.SendVerificationCodeAsync(PhoneNumber, verificationCode);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
|
||||
exMsg = ex.Message;
|
||||
if (exMsg.Length > 200)
|
||||
{
|
||||
exMsg = exMsg.Substring(0, 200);
|
||||
}
|
||||
}
|
||||
if (isSend)
|
||||
{
|
||||
await RedisCache.StringSetAsync($"App:sms:{PhoneNumber}", verificationCode, TimeSpan.FromMinutes(5));
|
||||
}
|
||||
T_Sms_Log t_Sms_Log = new T_Sms_Log()
|
||||
{
|
||||
VerificationCode = verificationCode,
|
||||
ErrorMessage = exMsg,
|
||||
PhoneNumber = PhoneNumber,
|
||||
SendStatus = isSend ? 1 : 0,
|
||||
SendTime = DateTime.Now,
|
||||
SendTimeDay = day
|
||||
};
|
||||
Dao.DaoExt.Context.T_Sms_Log.Add(t_Sms_Log);
|
||||
await Dao.DaoExt.Context.SaveChangesAsync();
|
||||
return isSend;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -6,7 +6,10 @@ using System.Threading.Tasks;
|
|||
|
||||
namespace CloudGaming.Code.AppExtend
|
||||
{
|
||||
public class AliyunOssConfig
|
||||
/// <summary>
|
||||
/// 阿里云配置
|
||||
/// </summary>
|
||||
public class AliyunConfig
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
|
|
@ -16,6 +19,8 @@ namespace CloudGaming.Code.AppExtend
|
|||
/// 配置环境变量
|
||||
/// </summary>
|
||||
public string AccessKeySecret { get; set; }
|
||||
|
||||
#region 阿里云OSS配置
|
||||
/// <summary>
|
||||
/// 替换为Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。
|
||||
/// </summary>
|
||||
|
|
@ -45,5 +50,18 @@ namespace CloudGaming.Code.AppExtend
|
|||
return this.DomainName; //+ this.UploadPath
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
/// <summary>
|
||||
/// 短信签名名称
|
||||
/// </summary>
|
||||
public string SmsSignName { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// string 短信模板配置
|
||||
/// </summary>
|
||||
public string SmsTemplateCode { get; set; }
|
||||
|
||||
}
|
||||
}
|
||||
|
|
@ -64,7 +64,7 @@ namespace CloudGaming.Code.AppExtend
|
|||
/// <summary>
|
||||
/// oss阿里云配置
|
||||
/// </summary>
|
||||
public AliyunOssConfig AliyunConfig { get; set; }
|
||||
public AliyunConfig AliyunConfig { get; set; }
|
||||
|
||||
|
||||
/// <summary>
|
||||
|
|
|
|||
|
|
@ -1,44 +0,0 @@
|
|||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc.Infrastructure;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.Extensions.Logging;
|
||||
using Microsoft.Extensions.Options;
|
||||
using Org.BouncyCastle.Asn1.Ocsp;
|
||||
|
||||
using System;
|
||||
using System.Buffers;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.Code.AppExtend
|
||||
{
|
||||
public class CustomObjectResultExecutor : ObjectResultExecutor
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
|
||||
public CustomObjectResultExecutor(OutputFormatterSelector formatterSelector, IHttpResponseStreamWriterFactory writerFactory, ILoggerFactory loggerFactory, IOptions<MvcOptions> mvcOptions):base(formatterSelector, writerFactory, loggerFactory, mvcOptions)
|
||||
|
||||
{
|
||||
//_httpContextAccessor = httpContextAccessor;
|
||||
}
|
||||
|
||||
public override Task ExecuteAsync(ActionContext context, ObjectResult result)
|
||||
{
|
||||
var httpContext = _httpContextAccessor.HttpContext;
|
||||
var user = httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous";
|
||||
|
||||
//// 动态修改返回的结果数据(示例:修改 Message 字段)
|
||||
//if (result.Value is ResponseData responseData)
|
||||
//{
|
||||
// if (user == "admin")
|
||||
// {
|
||||
// responseData.Message += " (admin)";
|
||||
// }
|
||||
//}
|
||||
|
||||
return base.ExecuteAsync(context, result);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -1,78 +0,0 @@
|
|||
using CloudGaming.Code.DataAccess;
|
||||
using CloudGaming.GameModel.Db.Db_Ext;
|
||||
|
||||
using HuanMeng.DotNetCore.AttributeExtend;
|
||||
using HuanMeng.DotNetCore.Base;
|
||||
using HuanMeng.DotNetCore.Utility;
|
||||
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using Microsoft.AspNetCore.Mvc;
|
||||
using Microsoft.AspNetCore.Mvc.Filters;
|
||||
using Microsoft.Identity.Client;
|
||||
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Specialized;
|
||||
using System.Diagnostics;
|
||||
using System.Net;
|
||||
using System.Reflection;
|
||||
|
||||
namespace CloudGaming.Code.AppExtend
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CustomResultFilter : IResultFilter
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly AppConfig _appConfig;
|
||||
public CustomResultFilter(IHttpContextAccessor httpContextAccessor, AppConfig appConfig, IServiceProvider serviceProvider)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_appConfig = appConfig;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
public void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
// 获取当前的 HttpContext
|
||||
var httpContext = context.HttpContext;
|
||||
var path = httpContext.Request.Path.Value ?? "";
|
||||
var apiPrefix = path.Replace('/', '.').TrimStart('.');
|
||||
var sw = Stopwatch.StartNew();
|
||||
//_appConfig.
|
||||
CloudGamingBase cloudGamingBase = new CloudGamingBase(_serviceProvider);
|
||||
// 获取当前用户的信息
|
||||
var user = httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous";
|
||||
//Dictionary<string, object> keyValuePairs = new Dictionary<string, object>();
|
||||
if (context.Result is ObjectResult objectResult && objectResult.Value != null)
|
||||
{
|
||||
var x = objectResult.Value.GetType();
|
||||
object? value = null;
|
||||
if (!x.FullName.Contains("HuanMeng.DotNetCore.Base.BaseResponse"))
|
||||
{
|
||||
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.Success, "", objectResult.Value);
|
||||
value = baseResponse;
|
||||
}
|
||||
else
|
||||
{
|
||||
value = objectResult.Value;
|
||||
}
|
||||
|
||||
var dic = value.ToDictionaryOrList(apiPrefix
|
||||
, it => cloudGamingBase.Cache.ImageEntityCache[it]
|
||||
);
|
||||
objectResult.Value = dic;
|
||||
sw.Stop();
|
||||
context.HttpContext.Response.Headers.TryAdd("X-Request-Duration-Filter", sw.Elapsed.TotalMilliseconds.ToString());
|
||||
}
|
||||
}
|
||||
public void OnResultExecuted(ResultExecutedContext context)
|
||||
{
|
||||
// 可在执行完结果后处理其他逻辑
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -10,6 +10,7 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="AgileConfig.Client" Version="1.7.3" />
|
||||
<PackageReference Include="AlibabaCloud.SDK.Dysmsapi20170525" Version="3.1.0" />
|
||||
<PackageReference Include="AutoMapper" Version="13.0.1" />
|
||||
<PackageReference Include="Bogus" Version="35.6.1" />
|
||||
<PackageReference Include="FastMember" Version="1.5.0" />
|
||||
|
|
|
|||
|
|
@ -0,0 +1,43 @@
|
|||
using HuanMeng.DotNetCore.AttributeExtend;
|
||||
|
||||
using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
|
||||
using System;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Reflection;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.Code.Extend;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class MessageAttributeExtend
|
||||
{
|
||||
private static readonly ConcurrentDictionary<MethodInfo, MessageAttribute?> _attributeCache = new ConcurrentDictionary<MethodInfo, MessageAttribute?>();
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="controllerActionDescriptor"></param>
|
||||
/// <returns></returns>
|
||||
public static MessageAttribute? GetMessageAttribute(ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
// 获取方法信息
|
||||
var methodInfo = controllerActionDescriptor.MethodInfo;
|
||||
|
||||
// 尝试从缓存中获取MessageAttribute
|
||||
if (!_attributeCache.TryGetValue(methodInfo, out var messageAttribute))
|
||||
{
|
||||
// 如果缓存中没有,则使用反射获取并存储到缓存中
|
||||
messageAttribute = methodInfo.GetCustomAttribute<MessageAttribute>();
|
||||
_attributeCache.TryAdd(methodInfo, messageAttribute);
|
||||
}
|
||||
|
||||
return messageAttribute;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,36 @@
|
|||
using Microsoft.AspNetCore.Mvc;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.Code.Filter
|
||||
{
|
||||
public class CustomExceptionFilter : IExceptionFilter
|
||||
{
|
||||
public void OnException(ExceptionContext context)
|
||||
{
|
||||
var sw = Stopwatch.StartNew();
|
||||
// 检查异常是否是特定的异常类型
|
||||
if (context.Exception is MessageException message)
|
||||
{
|
||||
var obj = new BaseResponse<object>(message.Code, message.Message, message.Data);
|
||||
//// 处理特定异常:记录日志、设置响应结果等
|
||||
//Console.WriteLine($"Custom exception caught: {message.Message}");
|
||||
|
||||
// 设置自定义的响应结果
|
||||
context.Result = new JsonResult(obj)
|
||||
{
|
||||
StatusCode = StatusCodes.Status200OK // 或者其他合适的HTTP状态码
|
||||
};
|
||||
|
||||
// 标记异常已经被处理
|
||||
context.ExceptionHandled = true;
|
||||
}
|
||||
sw.Stop();
|
||||
context.HttpContext.Response.Headers.TryAdd("X-Request-Duration-CustomExceptionFilter", sw.Elapsed.TotalMilliseconds.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,80 @@
|
|||
|
||||
|
||||
namespace CloudGaming.Code.Filter;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class CustomResultFilter : IResultFilter
|
||||
{
|
||||
private readonly IHttpContextAccessor _httpContextAccessor;
|
||||
private readonly IServiceProvider _serviceProvider;
|
||||
private readonly AppConfig _appConfig;
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="httpContextAccessor"></param>
|
||||
/// <param name="appConfig"></param>
|
||||
/// <param name="serviceProvider"></param>
|
||||
public CustomResultFilter(IHttpContextAccessor httpContextAccessor, AppConfig appConfig, IServiceProvider serviceProvider)
|
||||
{
|
||||
_httpContextAccessor = httpContextAccessor;
|
||||
_appConfig = appConfig;
|
||||
_serviceProvider = serviceProvider;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结果发送到客户端前
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResultExecuting(ResultExecutingContext context)
|
||||
{
|
||||
var httpContext = context.HttpContext;
|
||||
var path = httpContext.Request.Path.Value ?? "";
|
||||
var apiPrefix = path.Replace('/', '.').TrimStart('.');
|
||||
var sw = Stopwatch.StartNew();
|
||||
CloudGamingBase cloudGamingBase = new CloudGamingBase(_serviceProvider);
|
||||
//// 获取当前用户的信息
|
||||
//var user = httpContext.User.Identity.IsAuthenticated ? httpContext.User.Identity.Name : "Anonymous";
|
||||
if (context.Result is ObjectResult objectResult && objectResult.Value != null)
|
||||
{
|
||||
var x = objectResult.Value.GetType();
|
||||
object? value = null;
|
||||
if (!x.FullName.Contains("HuanMeng.DotNetCore.Base.BaseResponse"))
|
||||
{
|
||||
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.Success, "", objectResult.Value);
|
||||
value = baseResponse;
|
||||
// 获取当前执行的Action方法的信息并进行类型检查
|
||||
if (context.ActionDescriptor is ControllerActionDescriptor controllerActionDescriptor)
|
||||
{
|
||||
var messageAttribute = MessageAttributeExtend.GetMessageAttribute(controllerActionDescriptor);
|
||||
// 如果存在MessageAttribute,则设置响应消息
|
||||
if (messageAttribute != null)
|
||||
{
|
||||
baseResponse.Message = messageAttribute.Message;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
else
|
||||
{
|
||||
value = objectResult.Value;
|
||||
}
|
||||
|
||||
var dic = value.ToDictionaryOrList(apiPrefix, it => cloudGamingBase.Cache.ImageEntityCache[it]);
|
||||
objectResult.Value = dic;
|
||||
sw.Stop();
|
||||
context.HttpContext.Response.Headers.TryAdd("X-Request-Duration-Filter", sw.Elapsed.TotalMilliseconds.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 结果发送到客户端后
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public void OnResultExecuted(ResultExecutedContext context)
|
||||
{
|
||||
// 可在执行完结果后处理其他逻辑
|
||||
}
|
||||
}
|
||||
|
|
@ -1,4 +1,5 @@
|
|||
global using CloudGaming.Code.AppExtend;
|
||||
global using CloudGaming.Code.Extend;
|
||||
global using CloudGaming.GameModel.Db.Db_Ext;
|
||||
global using CloudGaming.GameModel.Db.Db_Game;
|
||||
global using CloudGaming.Model.DbSqlServer.Db_Phone;
|
||||
|
|
@ -7,9 +8,16 @@ global using CloudGaming.Model.DbSqlServer.Db_User;
|
|||
global using HuanMeng.DotNetCore.Base;
|
||||
global using HuanMeng.DotNetCore.MultiTenant;
|
||||
global using HuanMeng.DotNetCore.MultiTenant.Contract;
|
||||
global using HuanMeng.DotNetCore.Utility;
|
||||
|
||||
global using Microsoft.AspNetCore.Builder;
|
||||
global using Microsoft.AspNetCore.Http;
|
||||
global using Microsoft.AspNetCore.Mvc;
|
||||
global using Microsoft.AspNetCore.Mvc.Controllers;
|
||||
global using Microsoft.AspNetCore.Mvc.Filters;
|
||||
global using Microsoft.EntityFrameworkCore;
|
||||
global using Microsoft.Extensions.Configuration;
|
||||
global using Microsoft.Extensions.DependencyInjection;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
global using Microsoft.Extensions.Hosting;
|
||||
|
||||
global using System.Diagnostics;
|
||||
|
|
@ -111,7 +111,7 @@ namespace CloudGaming.Code.MiddlewareExtend
|
|||
}
|
||||
|
||||
/// <summary>
|
||||
/// Redis 缓存特性类
|
||||
/// Redis 缓存特性类
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)]
|
||||
public class RedisCacheAttribute : Attribute
|
||||
|
|
|
|||
|
|
@ -0,0 +1,113 @@
|
|||
using CloudGaming.Code.Sms.Contract;
|
||||
|
||||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
using Tea;
|
||||
|
||||
namespace CloudGaming.Code.Sms
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class AlibabaPhoneNumberVerificationService(AliyunConfig aliyunOssConfig) : IPhoneNumberVerificationService
|
||||
{
|
||||
|
||||
/// <term><b>Description:</b></term>
|
||||
/// <description>
|
||||
/// <para>使用AK&SK初始化账号Client</para>
|
||||
/// </description>
|
||||
///
|
||||
/// <returns>
|
||||
/// Client
|
||||
/// </returns>
|
||||
///
|
||||
/// <term><b>Exception:</b></term>
|
||||
/// Exception
|
||||
public AlibabaCloud.SDK.Dysmsapi20170525.Client CreateClient()
|
||||
{
|
||||
// 工程代码泄露可能会导致 AccessKey 泄露,并威胁账号下所有资源的安全性。以下代码示例仅供参考。
|
||||
// 建议使用更安全的 STS 方式,更多鉴权访问方式请参见:https://help.aliyun.com/document_detail/378671.html。
|
||||
AlibabaCloud.OpenApiClient.Models.Config config = new AlibabaCloud.OpenApiClient.Models.Config
|
||||
{
|
||||
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。
|
||||
AccessKeyId = "LTAI5tEMoHbcDC5d9CQWovJk",//aliyunOssConfig.AccessKeyId, //Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID"),
|
||||
// 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。
|
||||
AccessKeySecret = "gnYOJr0l9hTnl82vI4BxwVgtE1RdL"// aliyunOssConfig.AccessKeySecret,
|
||||
};
|
||||
// Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi
|
||||
config.Endpoint = "dysmsapi.aliyuncs.com";
|
||||
return new AlibabaCloud.SDK.Dysmsapi20170525.Client(config);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="phoneNumber"></param>
|
||||
/// <param name="code"></param>
|
||||
/// <returns></returns>
|
||||
/// <exception cref="NotImplementedException"></exception>
|
||||
public async Task<bool> SendVerificationCodeAsync(string phoneNumber, string code)
|
||||
{
|
||||
AlibabaCloud.SDK.Dysmsapi20170525.Client client = CreateClient();
|
||||
AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest sendSmsRequest = new AlibabaCloud.SDK.Dysmsapi20170525.Models.SendSmsRequest
|
||||
{
|
||||
SignName = aliyunOssConfig.SmsSignName,// "氢荷健康",
|
||||
TemplateCode = aliyunOssConfig.SmsTemplateCode,// "SMS_154950909",
|
||||
PhoneNumbers = phoneNumber,
|
||||
TemplateParam = "{\"code\":\"" + code + "\"}",
|
||||
};
|
||||
try
|
||||
{
|
||||
// 复制代码运行请自行打印 API 的返回值
|
||||
var response = await client.SendSmsWithOptionsAsync(sendSmsRequest, new AlibabaCloud.TeaUtil.Models.RuntimeOptions());
|
||||
}
|
||||
catch (TeaException error)
|
||||
{
|
||||
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
// 错误 message
|
||||
Console.WriteLine(error.Message);
|
||||
// 诊断地址
|
||||
Console.WriteLine(error.Data["Recommend"]);
|
||||
AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
|
||||
throw error;
|
||||
}
|
||||
catch (Exception _error)
|
||||
{
|
||||
TeaException error = new TeaException(new Dictionary<string, object>
|
||||
{
|
||||
{ "message", _error.Message }
|
||||
});
|
||||
// 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。
|
||||
// 错误 message
|
||||
Console.WriteLine(error.Message);
|
||||
// 诊断地址
|
||||
Console.WriteLine(error.Data["Recommend"]);
|
||||
AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message);
|
||||
throw error;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class SmsExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// 获取短信
|
||||
/// </summary>
|
||||
/// <param name="aliyunOssConfig"></param>
|
||||
/// <returns></returns>
|
||||
public static IPhoneNumberVerificationService GetPhoneNumberVerificationService(this AliyunConfig aliyunOssConfig)
|
||||
{
|
||||
return new AlibabaPhoneNumberVerificationService(aliyunOssConfig);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
|
@ -0,0 +1,22 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.Code.Sms.Contract
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送手机短信
|
||||
/// </summary>
|
||||
public interface IPhoneNumberVerificationService
|
||||
{
|
||||
/// <summary>
|
||||
/// 发送验证码到指定的手机号。
|
||||
/// </summary>
|
||||
/// <param name="phoneNumber">目标手机号。</param>
|
||||
/// <param name="code">验证码</param>
|
||||
/// <returns>返回操作是否成功。</returns>
|
||||
Task<bool> SendVerificationCodeAsync(string phoneNumber, string code);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,19 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace CloudGaming.DtoModel.Account
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class PhoneNumberRequest
|
||||
{
|
||||
/// <summary>
|
||||
/// 手机号码
|
||||
/// </summary>
|
||||
public string? PhoneNumber { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -54,6 +54,11 @@ public partial class CloudGamingCBTContext : DbContext
|
|||
/// </summary>
|
||||
public virtual DbSet<T_App_Image> T_App_Image { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送短信日志表
|
||||
/// </summary>
|
||||
public virtual DbSet<T_Sms_Log> T_Sms_Log { get; set; }
|
||||
|
||||
protected override void OnConfiguring(DbContextOptionsBuilder optionsBuilder)
|
||||
=> optionsBuilder.UseSqlServer("Server=192.168.1.17;Database=CloudGamingCBT;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;");
|
||||
|
||||
|
|
@ -139,6 +144,32 @@ public partial class CloudGamingCBTContext : DbContext
|
|||
|
||||
});
|
||||
|
||||
modelBuilder.Entity<T_Sms_Log>(entity =>
|
||||
{
|
||||
entity.HasKey(e => e.Id).HasName("PK__T_Sms_Lo__3214EC07AD037B45");
|
||||
|
||||
entity.ToTable(tb => tb.HasComment("发送短信日志表"));
|
||||
|
||||
entity.HasIndex(e => e.SendTimeDay, "T_Sms_Log_SendTimeDay_index_desc").IsDescending();
|
||||
|
||||
entity.Property(e => e.Id).HasComment("主键");
|
||||
entity.Property(e => e.ErrorMessage)
|
||||
.HasMaxLength(255)
|
||||
.HasComment("错误信息(如果发送失败)");
|
||||
entity.Property(e => e.PhoneNumber)
|
||||
.HasMaxLength(1)
|
||||
.HasComment("手机号码");
|
||||
entity.Property(e => e.SendStatus).HasComment("发送状态(0: 失败, 1: 成功)\r\n");
|
||||
entity.Property(e => e.SendTime)
|
||||
.HasComment("发送时间")
|
||||
.HasColumnType("datetime");
|
||||
entity.Property(e => e.SendTimeDay).HasComment("发送时间,天");
|
||||
entity.Property(e => e.VerificationCode)
|
||||
.HasMaxLength(1)
|
||||
.HasComment("发送的验证码");
|
||||
|
||||
});
|
||||
|
||||
OnModelCreatingPartial(modelBuilder);
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,47 @@
|
|||
using System;
|
||||
|
||||
namespace CloudGaming.GameModel.Db.Db_Ext;
|
||||
|
||||
/// <summary>
|
||||
/// 发送短信日志表
|
||||
/// </summary>
|
||||
public partial class T_Sms_Log
|
||||
{
|
||||
public T_Sms_Log() { }
|
||||
|
||||
/// <summary>
|
||||
/// 主键
|
||||
/// </summary>
|
||||
public virtual int Id { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 手机号码
|
||||
/// </summary>
|
||||
public virtual string PhoneNumber { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 发送的验证码
|
||||
/// </summary>
|
||||
public virtual string VerificationCode { get; set; } = null!;
|
||||
|
||||
/// <summary>
|
||||
/// 发送状态(0: 失败, 1: 成功)
|
||||
///
|
||||
/// </summary>
|
||||
public virtual int SendStatus { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送时间
|
||||
/// </summary>
|
||||
public virtual DateTime SendTime { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 发送时间,天
|
||||
/// </summary>
|
||||
public virtual int SendTimeDay { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 错误信息(如果发送失败)
|
||||
/// </summary>
|
||||
public virtual string? ErrorMessage { get; set; }
|
||||
}
|
||||
|
|
@ -0,0 +1,26 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HuanMeng.DotNetCore.AttributeExtend
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行成功后返回的消息
|
||||
/// </summary>
|
||||
[AttributeUsage(AttributeTargets.Method)]
|
||||
public class MessageAttribute : Attribute
|
||||
{
|
||||
/// <summary>
|
||||
/// 执行成功后返回的消息
|
||||
/// </summary>
|
||||
/// <param name="message">消息内容</param>
|
||||
public MessageAttribute(string message)
|
||||
{
|
||||
Message = message;
|
||||
}
|
||||
|
||||
public string Message { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -2,162 +2,161 @@ using Microsoft.EntityFrameworkCore;
|
|||
|
||||
using System.Linq.Expressions;
|
||||
|
||||
namespace HuanMeng.DotNetCore.Base
|
||||
namespace HuanMeng.DotNetCore.Base;
|
||||
|
||||
/// <summary>
|
||||
/// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational
|
||||
/// </summary>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
public class EfCoreDaoBase<TDbContext> where TDbContext : DbContext
|
||||
{
|
||||
private TDbContext _context;
|
||||
|
||||
public TDbContext Context => _context;
|
||||
|
||||
/// <summary>
|
||||
/// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <typeparam name="TDbContext"></typeparam>
|
||||
public class EfCoreDaoBase<TDbContext> where TDbContext : DbContext
|
||||
/// <param name="context"></param>
|
||||
public EfCoreDaoBase(TDbContext context)
|
||||
{
|
||||
private TDbContext _context;
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
}
|
||||
|
||||
public TDbContext Context => _context;
|
||||
/// <summary>
|
||||
/// 是否手动提交
|
||||
/// </summary>
|
||||
public bool IsManualSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 构造函数
|
||||
/// </summary>
|
||||
/// <param name="context"></param>
|
||||
public EfCoreDaoBase(TDbContext context)
|
||||
/// <summary>
|
||||
/// SqlQueryRaw
|
||||
/// </summary>
|
||||
public async Task<T?> SqlQueryAsync<T>(string sql, params object[] parameters) where T : class
|
||||
{
|
||||
return await Context.Database.SqlQueryRaw<T>(sql, parameters).FirstOrDefaultAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SqlQueryList
|
||||
/// </summary>
|
||||
public async Task<List<T>> SqlQueryListAsync<T>(string sql, params object[] parameters)
|
||||
{
|
||||
return await Context.Database.SqlQueryRaw<T>(sql, parameters).ToListAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExecuteSql
|
||||
/// </summary>
|
||||
public async Task<int> ExecuteSqlAsync(string sql, params object[] parameters)
|
||||
{
|
||||
var result = await Context.Database.ExecuteSqlRawAsync(sql, parameters);
|
||||
await AutoSaveChangesAsync();
|
||||
return result;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加实体
|
||||
/// </summary>
|
||||
public async Task AddAsync<T>(T entity) where T : class
|
||||
{
|
||||
Context.Set<T>().Add(entity);
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加实体
|
||||
/// </summary>
|
||||
public async Task AddRangeAsync<T>(IEnumerable<T> entities) where T : class
|
||||
{
|
||||
Context.Set<T>().AddRange(entities);
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除某个实体
|
||||
/// </summary>
|
||||
public async Task DeleteAsync<T>(T entity) where T : class
|
||||
{
|
||||
Context.Entry(entity).State = EntityState.Deleted;
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体
|
||||
/// </summary>
|
||||
public async Task UpdateAsync<T>(T entity) where T : class
|
||||
{
|
||||
if (Context.Entry(entity).State == EntityState.Detached)
|
||||
{
|
||||
_context = context ?? throw new ArgumentNullException(nameof(context));
|
||||
Context.Set<T>().Attach(entity);
|
||||
Context.Entry(entity).State = EntityState.Modified;
|
||||
}
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 是否手动提交
|
||||
/// </summary>
|
||||
public bool IsManualSubmit { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// SqlQueryRaw
|
||||
/// </summary>
|
||||
public async Task<T?> SqlQueryAsync<T>(string sql, params object[] parameters) where T : class
|
||||
/// <summary>
|
||||
/// 清除上下文跟踪
|
||||
/// </summary>
|
||||
public void RemoveTracking<T>(T entity) where T : class
|
||||
{
|
||||
if (Context.Entry(entity).State != EntityState.Detached)
|
||||
{
|
||||
return await Context.Database.SqlQueryRaw<T>(sql, parameters).FirstOrDefaultAsync();
|
||||
Context.Entry(entity).State = EntityState.Detached;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// SqlQueryList
|
||||
/// </summary>
|
||||
public async Task<List<T>> SqlQueryListAsync<T>(string sql, params object[] parameters)
|
||||
{
|
||||
return await Context.Database.SqlQueryRaw<T>(sql, parameters).ToListAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取实体,根据主键
|
||||
/// </summary>
|
||||
public async Task<T?> GetModelAsync<T>(params object[] keyValues) where T : class
|
||||
{
|
||||
return await Context.Set<T>().FindAsync(keyValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// ExecuteSql
|
||||
/// </summary>
|
||||
public async Task<int> ExecuteSqlAsync(string sql, params object[] parameters)
|
||||
{
|
||||
var result = await Context.Database.ExecuteSqlRawAsync(sql, parameters);
|
||||
await AutoSaveChangesAsync();
|
||||
return result;
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取实体,根据条件
|
||||
/// </summary>
|
||||
public async Task<T?> GetModelAsync<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
|
||||
{
|
||||
var query = Context.Set<T>().AsQueryable();
|
||||
if (isNoTracking)
|
||||
query = query.AsNoTracking();
|
||||
return await query.FirstOrDefaultAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 添加实体
|
||||
/// </summary>
|
||||
public async Task AddAsync<T>(T entity) where T : class
|
||||
{
|
||||
Context.Set<T>().Add(entity);
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取列表数据
|
||||
/// </summary>
|
||||
public IQueryable<T> GetList<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
|
||||
{
|
||||
var query = Context.Set<T>().Where(where);
|
||||
return isNoTracking ? query.AsNoTracking() : query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 批量添加实体
|
||||
/// </summary>
|
||||
public async Task AddRangeAsync<T>(IEnumerable<T> entities) where T : class
|
||||
{
|
||||
Context.Set<T>().AddRange(entities);
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// 获取记录数量
|
||||
/// </summary>
|
||||
public async Task<int> GetCountAsync<T>(Expression<Func<T, bool>> where) where T : class
|
||||
{
|
||||
return await Context.Set<T>().AsNoTracking().CountAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 删除某个实体
|
||||
/// </summary>
|
||||
public async Task DeleteAsync<T>(T entity) where T : class
|
||||
{
|
||||
Context.Entry(entity).State = EntityState.Deleted;
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
/// <summary>
|
||||
/// 判断是否存在记录
|
||||
/// </summary>
|
||||
public async Task<bool> ExistsAsync<T>(Expression<Func<T, bool>> where) where T : class
|
||||
{
|
||||
return await Context.Set<T>().AsNoTracking().AnyAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 更新实体
|
||||
/// </summary>
|
||||
public async Task UpdateAsync<T>(T entity) where T : class
|
||||
/// <summary>
|
||||
/// 根据提交状态决定是否自动保存更改
|
||||
/// </summary>
|
||||
private async Task AutoSaveChangesAsync()
|
||||
{
|
||||
if (!IsManualSubmit)
|
||||
{
|
||||
if (Context.Entry(entity).State == EntityState.Detached)
|
||||
{
|
||||
Context.Set<T>().Attach(entity);
|
||||
Context.Entry(entity).State = EntityState.Modified;
|
||||
}
|
||||
await AutoSaveChangesAsync();
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 清除上下文跟踪
|
||||
/// </summary>
|
||||
public void RemoveTracking<T>(T entity) where T : class
|
||||
{
|
||||
if (Context.Entry(entity).State != EntityState.Detached)
|
||||
{
|
||||
Context.Entry(entity).State = EntityState.Detached;
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体,根据主键
|
||||
/// </summary>
|
||||
public async Task<T?> GetModelAsync<T>(params object[] keyValues) where T : class
|
||||
{
|
||||
return await Context.Set<T>().FindAsync(keyValues);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取实体,根据条件
|
||||
/// </summary>
|
||||
public async Task<T?> GetModelAsync<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
|
||||
{
|
||||
var query = Context.Set<T>().AsQueryable();
|
||||
if (isNoTracking)
|
||||
query = query.AsNoTracking();
|
||||
return await query.FirstOrDefaultAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取列表数据
|
||||
/// </summary>
|
||||
public IQueryable<T> GetList<T>(Expression<Func<T, bool>> where, bool isNoTracking = false) where T : class
|
||||
{
|
||||
var query = Context.Set<T>().Where(where);
|
||||
return isNoTracking ? query.AsNoTracking() : query;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取记录数量
|
||||
/// </summary>
|
||||
public async Task<int> GetCountAsync<T>(Expression<Func<T, bool>> where) where T : class
|
||||
{
|
||||
return await Context.Set<T>().AsNoTracking().CountAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 判断是否存在记录
|
||||
/// </summary>
|
||||
public async Task<bool> ExistsAsync<T>(Expression<Func<T, bool>> where) where T : class
|
||||
{
|
||||
return await Context.Set<T>().AsNoTracking().AnyAsync(where);
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 根据提交状态决定是否自动保存更改
|
||||
/// </summary>
|
||||
private async Task AutoSaveChangesAsync()
|
||||
{
|
||||
if (!IsManualSubmit)
|
||||
{
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
await Context.SaveChangesAsync();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,23 +1,22 @@
|
|||
namespace XLib.DotNetCore.Base
|
||||
namespace XLib.DotNetCore.Base;
|
||||
|
||||
/// <summary>
|
||||
/// 接口和服务调用通用响应接口
|
||||
/// </summary>
|
||||
public interface IResponse
|
||||
{
|
||||
///// <summary>
|
||||
///// Http状态码
|
||||
///// </summary>
|
||||
//HttpStatusCode StatusCode { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 接口和服务调用通用响应接口
|
||||
/// 功能执行返回代码
|
||||
/// </summary>
|
||||
public interface IResponse
|
||||
{
|
||||
///// <summary>
|
||||
///// Http状态码
|
||||
///// </summary>
|
||||
//HttpStatusCode StatusCode { get; set; }
|
||||
int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 功能执行返回代码
|
||||
/// </summary>
|
||||
int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
string Message { get; set; }
|
||||
}
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
string Message { get; set; }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,74 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Linq;
|
||||
using System.Runtime.Serialization;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
|
||||
namespace HuanMeng.DotNetCore.Base
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public class MessageException : Exception
|
||||
{
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
public MessageException(ResonseCode code)
|
||||
{
|
||||
Code = (int)code;
|
||||
Message = "";
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="data"></param>
|
||||
public MessageException(ResonseCode code, string message, object? data = null)
|
||||
{
|
||||
Code = (int)code;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="code"></param>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="data"></param>
|
||||
public MessageException(int code, string message, object? data = null)
|
||||
{
|
||||
Code = code;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
/// <param name="message"></param>
|
||||
/// <param name="data"></param>
|
||||
public MessageException(string message, object? data = null)
|
||||
{
|
||||
Code = 0;
|
||||
Message = message;
|
||||
Data = data;
|
||||
}
|
||||
/// <summary>
|
||||
/// 功能执行返回代码
|
||||
/// </summary>
|
||||
public int Code { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 消息
|
||||
/// </summary>
|
||||
public string Message { get; set; }
|
||||
|
||||
/// <summary>
|
||||
/// 数据
|
||||
/// </summary>
|
||||
public object? Data { get; set; }
|
||||
}
|
||||
}
|
||||
|
|
@ -1,53 +1,61 @@
|
|||
namespace HuanMeng.DotNetCore.Base
|
||||
namespace HuanMeng.DotNetCore.Base;
|
||||
|
||||
/// <summary>
|
||||
/// 响应编码参考,实际的项目使用可以自行定义
|
||||
/// 基本规则:
|
||||
/// 成功:大于等于0
|
||||
/// 失败:小于0
|
||||
/// </summary>
|
||||
public enum ResonseCode
|
||||
{
|
||||
/// <summary>
|
||||
/// 响应编码参考,实际的项目使用可以自行定义
|
||||
/// 基本规则:
|
||||
/// 成功:大于等于0
|
||||
/// 失败:小于0
|
||||
/// Sign签名错误
|
||||
/// </summary>
|
||||
public enum ResonseCode
|
||||
{
|
||||
/// <summary>
|
||||
/// Sign签名错误
|
||||
/// </summary>
|
||||
SignError = -999,
|
||||
/// <summary>
|
||||
/// jwt用户签名错误
|
||||
/// </summary>
|
||||
TwtError = -998,
|
||||
SignError = -999,
|
||||
/// <summary>
|
||||
/// jwt用户签名错误
|
||||
/// </summary>
|
||||
TwtError = -998,
|
||||
|
||||
/// <summary>
|
||||
/// 用户验证失败
|
||||
/// </summary>
|
||||
Unauthorized = 401,
|
||||
/// <summary>
|
||||
/// 重复请求
|
||||
/// </summary>
|
||||
ManyRequests = 429,
|
||||
/// <summary>
|
||||
/// 用户验证失败
|
||||
/// </summary>
|
||||
Unauthorized = 401,
|
||||
|
||||
/// <summary>
|
||||
/// 正在处理中
|
||||
/// </summary>
|
||||
Processing = 102,
|
||||
/// <summary>
|
||||
/// 通用错误
|
||||
/// </summary>
|
||||
Error = -1,
|
||||
/// <summary>
|
||||
/// 重复请求
|
||||
/// </summary>
|
||||
ManyRequests = 429,
|
||||
|
||||
/// <summary>
|
||||
/// 参数错误
|
||||
/// </summary>
|
||||
ParamError = -2,
|
||||
/// <summary>
|
||||
/// 手机号异常
|
||||
/// </summary>
|
||||
PhoneNumberException = 530,
|
||||
/// <summary>
|
||||
/// 当日手机号发送已到达上限
|
||||
/// </summary>
|
||||
PhoneNumberMaxException = 531,
|
||||
/// <summary>
|
||||
/// 正在处理中
|
||||
/// </summary>
|
||||
Processing = 102,
|
||||
/// <summary>
|
||||
/// 通用错误
|
||||
/// </summary>
|
||||
Error = -1,
|
||||
|
||||
/// <summary>
|
||||
/// 没找到数据记录
|
||||
/// </summary>
|
||||
NotFoundRecord = -3,
|
||||
/// <summary>
|
||||
/// 参数错误
|
||||
/// </summary>
|
||||
ParamError = -2,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
}
|
||||
/// <summary>
|
||||
/// 没找到数据记录
|
||||
/// </summary>
|
||||
NotFoundRecord = -3,
|
||||
|
||||
/// <summary>
|
||||
/// 成功
|
||||
/// </summary>
|
||||
Success = 0,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,160 @@
|
|||
using HuanMeng.DotNetCore.AttributeExtend;
|
||||
|
||||
using System.Collections;
|
||||
using System.Collections.Concurrent;
|
||||
using System.Linq.Expressions;
|
||||
using System.Reflection;
|
||||
|
||||
namespace HuanMeng.DotNetCore.Utility;
|
||||
|
||||
|
||||
/// <summary>
|
||||
/// object转数据字典
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于存储每种类型的属性访问器数组的线程安全缓存。
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, PropertyAccessor[]> PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 缓存每个属性是否具有 ImagesAttribute 特性。
|
||||
/// </summary>
|
||||
public static readonly ConcurrentDictionary<PropertyInfo, bool> _PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 判断对象是否为原始类型或字符串类型。
|
||||
/// </summary>
|
||||
/// <param name="obj">要检查的对象。</param>
|
||||
/// <returns>如果对象是原始类型或字符串,返回 true;否则返回 false。</returns>
|
||||
public static bool IsPrimitiveType(object obj) =>
|
||||
obj is not null && (obj.GetType().IsPrimitive || obj is string or ValueType);
|
||||
|
||||
/// <summary>
|
||||
/// 判断对象是否为集合类型(不包括字符串)。
|
||||
/// </summary>
|
||||
/// <param name="obj">要检查的对象。</param>
|
||||
/// <returns>如果对象是集合类型(但不是字符串),返回 true;否则返回 false。</returns>
|
||||
public static bool IsCollectionType(object obj) => obj is IEnumerable && obj is not string;
|
||||
|
||||
/// <summary>
|
||||
/// 根据对象类型,将对象转换为字典或列表格式,支持可选的路径前缀。
|
||||
/// </summary>
|
||||
/// <param name="obj">要转换的对象。</param>
|
||||
/// <param name="prefix">属性路径的可选前缀。</param>
|
||||
/// <returns>对象的字典或列表表示形式。</returns>
|
||||
public static object ToDictionaryOrList(this object obj, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
return obj switch
|
||||
{
|
||||
_ when IsPrimitiveType(obj) => obj,
|
||||
IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc),
|
||||
_ => TransformObject(obj, prefix, imageFunc)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将集合对象转换为包含转换项的列表,每个项保留其路径前缀。
|
||||
/// </summary>
|
||||
/// <param name="enumerable">要转换的集合。</param>
|
||||
/// <param name="prefix">集合中每个属性路径的前缀。</param>
|
||||
/// <returns>转换后的项列表。</returns>
|
||||
private static List<object> TransformCollection(IEnumerable enumerable, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
var list = new List<object>(enumerable is ICollection collection ? collection.Count : 10);
|
||||
int index = 0;
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
list.Add(ToDictionaryOrList(item, $"{prefix}.[{index}]", imageFunc)); // 为集合中每个项添加路径
|
||||
index++;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象的属性转换为带有路径前缀的字典,并应用前缀规则。
|
||||
/// </summary>
|
||||
/// <param name="obj">要转换的对象。</param>
|
||||
/// <param name="prefix">每个属性路径的前缀。</param>
|
||||
/// <returns>包含属性名和属性值的字典。</returns>
|
||||
private static Dictionary<string, object> TransformObject(object obj, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var type = obj.GetType();
|
||||
var accessors = PropertyCache.GetOrAdd(type, CreatePropertyAccessors);
|
||||
var keyValuePairs = new Dictionary<string, object>(accessors.Length);
|
||||
|
||||
foreach (var accessor in accessors)
|
||||
{
|
||||
var propertyPath = $"{prefix}.{accessor.PropertyName}"; // 构建完整的属性路径
|
||||
|
||||
// 使用访问器获取属性值
|
||||
var propertyValue = accessor.Getter(obj);
|
||||
|
||||
// 如果属性是字符串,在其值前添加 "test"
|
||||
if (propertyValue is string stringValue)
|
||||
{
|
||||
keyValuePairs[accessor.PropertyName] = stringValue;
|
||||
//Console.WriteLine(propertyPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果属性具有 ImagesAttribute,在其值前添加 "image"
|
||||
// 否则,如果是集合类型,则递归转换
|
||||
keyValuePairs[accessor.PropertyName] = accessor.HasImagesAttribute
|
||||
? imageFunc?.Invoke((int)propertyValue) ?? ""
|
||||
: ToDictionaryOrList(propertyValue, propertyPath, imageFunc); // IsCollectionType(propertyValue) ?: propertyValue;
|
||||
}
|
||||
|
||||
return keyValuePairs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为给定类型创建属性访问器数组。
|
||||
/// </summary>
|
||||
/// <param name="type">要创建属性访问器的类型。</param>
|
||||
/// <returns>属性访问器数组。</returns>
|
||||
private static PropertyAccessor[] CreatePropertyAccessors(Type type)
|
||||
{
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
return properties.Select(property =>
|
||||
{
|
||||
// 创建用于访问属性值的委托
|
||||
var getter = CreatePropertyGetter(type, property);
|
||||
// 检查属性是否具有 ImagesAttribute,并将结果存储在缓存中
|
||||
var isImagesAttribute = _PropertyCache.GetOrAdd(property, p => p.GetCustomAttribute<ImagesAttribute>() != null);
|
||||
return new PropertyAccessor(property.Name, getter, isImagesAttribute);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Func<object, object> CreatePropertyGetter(Type type, PropertyInfo property)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "obj");
|
||||
var castParameter = Expression.Convert(parameter, type);
|
||||
var propertyAccess = Expression.Property(castParameter, property);
|
||||
var convertPropertyAccess = Expression.Convert(propertyAccess, typeof(object));
|
||||
return Expression.Lambda<Func<object, object>>(convertPropertyAccess, parameter).Compile();
|
||||
}
|
||||
|
||||
private class PropertyAccessor
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
public Func<object, object> Getter { get; }
|
||||
public bool HasImagesAttribute { get; }
|
||||
|
||||
public PropertyAccessor(string propertyName, Func<object, object> getter, bool hasImagesAttribute)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
Getter = getter;
|
||||
HasImagesAttribute = hasImagesAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -11,157 +11,6 @@ using Microsoft.IdentityModel.Tokens;
|
|||
namespace HuanMeng.DotNetCore.Utility;
|
||||
|
||||
|
||||
/// <summary>
|
||||
///
|
||||
/// </summary>
|
||||
public static class ObjectExtensions
|
||||
{
|
||||
/// <summary>
|
||||
/// 用于存储每种类型的属性访问器数组的线程安全缓存。
|
||||
/// </summary>
|
||||
private static readonly ConcurrentDictionary<Type, PropertyAccessor[]> PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 缓存每个属性是否具有 ImagesAttribute 特性。
|
||||
/// </summary>
|
||||
public static readonly ConcurrentDictionary<PropertyInfo, bool> _PropertyCache = new();
|
||||
|
||||
/// <summary>
|
||||
/// 判断对象是否为原始类型或字符串类型。
|
||||
/// </summary>
|
||||
/// <param name="obj">要检查的对象。</param>
|
||||
/// <returns>如果对象是原始类型或字符串,返回 true;否则返回 false。</returns>
|
||||
public static bool IsPrimitiveType(object obj) =>
|
||||
obj is not null && (obj.GetType().IsPrimitive || obj is string or ValueType);
|
||||
|
||||
/// <summary>
|
||||
/// 判断对象是否为集合类型(不包括字符串)。
|
||||
/// </summary>
|
||||
/// <param name="obj">要检查的对象。</param>
|
||||
/// <returns>如果对象是集合类型(但不是字符串),返回 true;否则返回 false。</returns>
|
||||
public static bool IsCollectionType(object obj) => obj is IEnumerable && obj is not string;
|
||||
|
||||
/// <summary>
|
||||
/// 根据对象类型,将对象转换为字典或列表格式,支持可选的路径前缀。
|
||||
/// </summary>
|
||||
/// <param name="obj">要转换的对象。</param>
|
||||
/// <param name="prefix">属性路径的可选前缀。</param>
|
||||
/// <returns>对象的字典或列表表示形式。</returns>
|
||||
public static object ToDictionaryOrList(this object obj, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
if (obj == null) return null;
|
||||
|
||||
return obj switch
|
||||
{
|
||||
_ when IsPrimitiveType(obj) => obj,
|
||||
IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc),
|
||||
_ => TransformObject(obj, prefix, imageFunc)
|
||||
};
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将集合对象转换为包含转换项的列表,每个项保留其路径前缀。
|
||||
/// </summary>
|
||||
/// <param name="enumerable">要转换的集合。</param>
|
||||
/// <param name="prefix">集合中每个属性路径的前缀。</param>
|
||||
/// <returns>转换后的项列表。</returns>
|
||||
private static List<object> TransformCollection(IEnumerable enumerable, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
var list = new List<object>(enumerable is ICollection collection ? collection.Count : 10);
|
||||
int index = 0;
|
||||
foreach (var item in enumerable)
|
||||
{
|
||||
list.Add(ToDictionaryOrList(item, $"{prefix}.[{index}]", imageFunc)); // 为集合中每个项添加路径
|
||||
index++;
|
||||
}
|
||||
return list;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 将对象的属性转换为带有路径前缀的字典,并应用前缀规则。
|
||||
/// </summary>
|
||||
/// <param name="obj">要转换的对象。</param>
|
||||
/// <param name="prefix">每个属性路径的前缀。</param>
|
||||
/// <returns>包含属性名和属性值的字典。</returns>
|
||||
private static Dictionary<string, object> TransformObject(object obj, string prefix = "", Func<int, string>? imageFunc = null)
|
||||
{
|
||||
if (obj == null)
|
||||
{
|
||||
return null;
|
||||
}
|
||||
var type = obj.GetType();
|
||||
var accessors = PropertyCache.GetOrAdd(type, CreatePropertyAccessors);
|
||||
var keyValuePairs = new Dictionary<string, object>(accessors.Length);
|
||||
|
||||
foreach (var accessor in accessors)
|
||||
{
|
||||
var propertyPath = $"{prefix}.{accessor.PropertyName}"; // 构建完整的属性路径
|
||||
|
||||
// 使用访问器获取属性值
|
||||
var propertyValue = accessor.Getter(obj);
|
||||
|
||||
// 如果属性是字符串,在其值前添加 "test"
|
||||
if (propertyValue is string stringValue)
|
||||
{
|
||||
keyValuePairs[accessor.PropertyName] = stringValue;
|
||||
//Console.WriteLine(propertyPath);
|
||||
continue;
|
||||
}
|
||||
|
||||
// 如果属性具有 ImagesAttribute,在其值前添加 "image"
|
||||
// 否则,如果是集合类型,则递归转换
|
||||
keyValuePairs[accessor.PropertyName] = accessor.HasImagesAttribute
|
||||
? imageFunc?.Invoke((int)propertyValue) ?? ""
|
||||
: ToDictionaryOrList(propertyValue, propertyPath, imageFunc); // IsCollectionType(propertyValue) ?: propertyValue;
|
||||
}
|
||||
|
||||
return keyValuePairs;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 为给定类型创建属性访问器数组。
|
||||
/// </summary>
|
||||
/// <param name="type">要创建属性访问器的类型。</param>
|
||||
/// <returns>属性访问器数组。</returns>
|
||||
private static PropertyAccessor[] CreatePropertyAccessors(Type type)
|
||||
{
|
||||
var properties = type.GetProperties(BindingFlags.Public | BindingFlags.Instance);
|
||||
return properties.Select(property =>
|
||||
{
|
||||
// 创建用于访问属性值的委托
|
||||
var getter = CreatePropertyGetter(type, property);
|
||||
// 检查属性是否具有 ImagesAttribute,并将结果存储在缓存中
|
||||
var isImagesAttribute = _PropertyCache.GetOrAdd(property, p => p.GetCustomAttribute<ImagesAttribute>() != null);
|
||||
return new PropertyAccessor(property.Name, getter, isImagesAttribute);
|
||||
}).ToArray();
|
||||
}
|
||||
|
||||
|
||||
|
||||
private static Func<object, object> CreatePropertyGetter(Type type, PropertyInfo property)
|
||||
{
|
||||
var parameter = Expression.Parameter(typeof(object), "obj");
|
||||
var castParameter = Expression.Convert(parameter, type);
|
||||
var propertyAccess = Expression.Property(castParameter, property);
|
||||
var convertPropertyAccess = Expression.Convert(propertyAccess, typeof(object));
|
||||
return Expression.Lambda<Func<object, object>>(convertPropertyAccess, parameter).Compile();
|
||||
}
|
||||
|
||||
private class PropertyAccessor
|
||||
{
|
||||
public string PropertyName { get; }
|
||||
public Func<object, object> Getter { get; }
|
||||
public bool HasImagesAttribute { get; }
|
||||
|
||||
public PropertyAccessor(string propertyName, Func<object, object> getter, bool hasImagesAttribute)
|
||||
{
|
||||
PropertyName = propertyName;
|
||||
Getter = getter;
|
||||
HasImagesAttribute = hasImagesAttribute;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
[Obsolete]
|
||||
public static class ObjectExtensions11
|
||||
{
|
||||
|
|
|
|||
Loading…
Reference in New Issue
Block a user