diff --git a/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj b/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj index 567d147..687f39b 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj +++ b/src/CloudGaming/Api/CloudGaming.Api/CloudGaming.Api.csproj @@ -8,6 +8,7 @@ Linux ..\.. True + true diff --git a/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs new file mode 100644 index 0000000..9e60b6c --- /dev/null +++ b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs @@ -0,0 +1,68 @@ +using Bogus.DataSets; + +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.DtoModel.Account.Login; +using CloudGaming.GameModel.Db.Db_Ext; + +using HuanMeng.DotNetCore.AttributeExtend; +using HuanMeng.DotNetCore.Base; + +using HuanMeng.DotNetCore.Utility; + +using Microsoft.AspNetCore.Authorization; +using Microsoft.AspNetCore.Mvc; + +namespace CloudGaming.Api.Controllers; + +/// +/// 账号管理 +/// +public class AccountController : CloudGamingControllerBase +{ + public AccountController(IServiceProvider _serviceProvider) : base(_serviceProvider) + { + } + + /// + /// 发送验证码 + /// + /// + /// + [HttpPost] + [Message("发送成功")] + public async Task SendPhoneNumber([FromBody] PhoneNumberRequest phoneNumber) + { + AccountBLL account = new AccountBLL(ServiceProvider); + return await account.SendPhoneNumber(phoneNumber.PhoneNumber); + } + + /// + /// 登录接口 + /// 登录数据格式(设备号的目的是用于以后的多设备登录) + /// 短信登录:{"phoneNumber":"手机号","verificationCode":"验证码","deviceNumber":"设备号"} + /// token登录(请求标头需要带上Authorized的token) {"deviceNumber":"设备号"} + /// + /// + [HttpPost] + public async Task LoginAsync([FromBody] BaseLoginParams baseLogin) + { + AccountBLL account = new AccountBLL(ServiceProvider); + return await account.LoginAsync(); + } + + /// + /// + /// + /// + [HttpGet] + [Authorize] + public async Task GetUserInfo() + { + return true; + } +} diff --git a/src/CloudGaming/Api/CloudGaming.Api/Controllers/AppController.cs b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AppController.cs index 907168e..eafa2b7 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Controllers/AppController.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AppController.cs @@ -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 /// /// [HttpGet] + [Message("发送成功")] public async Task GetAppConfigAsync() { AppConfigBLL appConfigBLL = new AppConfigBLL(ServiceProvider); diff --git a/src/CloudGaming/Api/CloudGaming.Api/Controllers/GameController.cs b/src/CloudGaming/Api/CloudGaming.Api/Controllers/GameController.cs index 798b456..4117bce 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Controllers/GameController.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Controllers/GameController.cs @@ -74,5 +74,18 @@ namespace CloudGaming.Api.Controllers GameBLL gamebll = new GameBLL(this.ServiceProvider); return gamebll.GameRecommendations(gameId); } + /// + /// 游戏搜索 + /// + /// + /// + [HttpGet] + public List GameSearch([FromQuery] string? gameId) + { + + GameBLL gamebll = new GameBLL(this.ServiceProvider); + return gamebll.GameSearch(gameId); + } + } } diff --git a/src/CloudGaming/Api/CloudGaming.Api/Controllers/HomeController.cs b/src/CloudGaming/Api/CloudGaming.Api/Controllers/HomeController.cs index 1f6ce5c..d158d86 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Controllers/HomeController.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Controllers/HomeController.cs @@ -8,6 +8,9 @@ using Microsoft.AspNetCore.Mvc; namespace CloudGaming.Api.Controllers; +/// +/// 首页 +/// public class HomeController : CloudGamingControllerBase { public HomeController(IServiceProvider _serviceProvider) : base(_serviceProvider) diff --git a/src/CloudGaming/Api/CloudGaming.Api/Dockerfile b/src/CloudGaming/Api/CloudGaming.Api/Dockerfile index 9f8f60f..68c5cf0 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Dockerfile +++ b/src/CloudGaming/Api/CloudGaming.Api/Dockerfile @@ -4,8 +4,7 @@ FROM mcr.microsoft.com/dotnet/aspnet:8.0 AS base USER app WORKDIR /app -EXPOSE 8080 -EXPOSE 8081 +EXPOSE 80 # 此阶段用于生成服务项目 @@ -14,8 +13,10 @@ ARG BUILD_CONFIGURATION=Release WORKDIR /src COPY ["Api/CloudGaming.Api/CloudGaming.Api.csproj", "Api/CloudGaming.Api/"] COPY ["Code/CloudGaming.Code/CloudGaming.Code.csproj", "Code/CloudGaming.Code/"] -COPY ["Model/CloudGaming.Model/CloudGaming.Model.csproj", "Model/CloudGaming.Model/"] +COPY ["Model/CloudGaming.DtoModel/CloudGaming.DtoModel.csproj", "Model/CloudGaming.DtoModel/"] +COPY ["Model/CloudGaming.GameModel/CloudGaming.GameModel.csproj", "Model/CloudGaming.GameModel/"] COPY ["Utile/HuanMeng.DotNetCore/HuanMeng.DotNetCore.csproj", "Utile/HuanMeng.DotNetCore/"] +COPY ["Model/CloudGaming.Model/CloudGaming.Model.csproj", "Model/CloudGaming.Model/"] RUN dotnet restore "./Api/CloudGaming.Api/CloudGaming.Api.csproj" COPY . . WORKDIR "/src/Api/CloudGaming.Api" diff --git a/src/CloudGaming/Api/CloudGaming.Api/Program.cs b/src/CloudGaming/Api/CloudGaming.Api/Program.cs index 4265974..0bbf42c 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Program.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Program.cs @@ -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(); + options.Filters.Add(); }) .AddNewtonsoftJson(options => { @@ -68,7 +70,9 @@ builder.Services.AddControllers(options => //options.SerializerSettings.Converters.Add() // 其他配置... }); -builder.Services.AddSingleton(); +//CustomResultFilter +//builder.Services.AddSingleton(); + #endregion // Learn more about configuring Swagger/OpenAPI at https://aka.ms/aspnetcore/swashbuckle builder.Services.AddEndpointsApiExplorer(); diff --git a/src/CloudGaming/Api/CloudGaming.Api/appsettings.Development.json b/src/CloudGaming/Api/CloudGaming.Api/appsettings.Development.json index bb5fb81..1f3aad1 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/appsettings.Development.json +++ b/src/CloudGaming/Api/CloudGaming.Api/appsettings.Development.json @@ -5,6 +5,15 @@ "Microsoft.AspNetCore": "Warning" } }, + "AgileConfig": { + "appId": "CloudGaming", + "secret": "95BB717C61D1ECB0E9FB82C932CC77FF", + "nodes": "http://124.220.55.158:94", //多个节点使用逗号分隔 + "url": "http://124.220.55.158:94", + "env": "DEV", + "UserName": "admin", + "Password": "dbt@com@1234" + }, //服务器配置 "Kestrel": { "Endpoints": { diff --git a/src/CloudGaming/Api/CloudGaming.Api/appsettings.json b/src/CloudGaming/Api/CloudGaming.Api/appsettings.json index 9d8e2c3..5cadeec 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/appsettings.json +++ b/src/CloudGaming/Api/CloudGaming.Api/appsettings.json @@ -74,7 +74,7 @@ "secret": "95BB717C61D1ECB0E9FB82C932CC77FF", "nodes": "http://124.220.55.158:94", //多个节点使用逗号分隔 "url": "http://124.220.55.158:94", - "env": "DEV", + "env": "TEST", "UserName": "admin", "Password": "dbt@com@1234" }, diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs new file mode 100644 index 0000000..0b46f8c --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs @@ -0,0 +1,294 @@ +using Alipay.EasySDK.Kernel; + +using CloudGaming.Code.Account.Contract; +using CloudGaming.Code.Sms; +using CloudGaming.DtoModel.Account; +using CloudGaming.DtoModel.Account.Login; + +using HuanMeng.DotNetCore.JwtInfrastructure; +using HuanMeng.DotNetCore.Redis; +using HuanMeng.DotNetCore.Utility; + +using Newtonsoft.Json.Linq; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Claims; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.Account +{ + /// + /// 账号操作 + /// + public class AccountBLL : CloudGamingBase + { + public AccountBLL(IServiceProvider serviceProvider) : base(serviceProvider) + { + + } + + /// + /// 发送手机号码 + /// + /// + /// + public async Task SendPhoneNumber(string PhoneNumber) + { + if (!PhoneNumberValidator.IsPhoneNumber(PhoneNumber)) + { + throw MessageBox.Show(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 MessageBox.Show(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(); + if (!isSend) + { + throw MessageBox.ErrorShow("发送失败"); + } + return isSend; + } + + /// + /// 登录接口 + /// + /// + public async Task LoginAsync() + { + this.HttpContextAccessor.HttpContext.Request.Body.Position = 0; + var json = await new StreamReader(this.HttpContextAccessor.HttpContext.Request.Body).ReadToEndAsync(); + if (string.IsNullOrEmpty(json)) + { + throw MessageBox.Show(ResonseCode.NullOrEmpty, "登录方式不合格"); + } + + var account = AccountExtend.GetUserAccount(json, this); + if (account == null) + { + throw MessageBox.Show(ResonseCode.NullOrEmpty, "未找到登录方式"); + } + + var userId = await account.LoginAsync(); + bool isNew = false; + if (userId == 0) + { + isNew = true; + } + var ip = this.HttpContextAccessor.HttpContext.GetClientIpAddress(); + //登录或者注册用户 + var user = await RegisterOrUpdateUserAsync(userId, account, ip); + //注册用户详细信息 + var userData = await EnsureUserDataExistsAsync(user.Id, account); + //创建jwt登录 + var jwt = GenerateJwtToken(user); + var accountLogIn = new AccountLogInResponse + { + NickName = user.NickName, + Token = jwt.AccessToken, + UserId = user.Id, + }; + //创建设备号 + var dev = await ManageUserDevicesAsync(user, account, jwt.AccessToken); + var key = $"user:login:{user.Id}"; + var accountUserLoginInfos = await RedisCache.StringGetAsync>(key); + accountUserLoginInfos ??= new List(); + //创建redis缓存 + await RedisCache.StringSetAsync(key, $"{DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss")}", TimeSpan.FromHours(1)); + //accountUserLoginInfos.Add(new AccountUserLoginInfo()); + T_User_Login_Log login_Log = new T_User_Login_Log() + { + Channel = this.AppRequestInfo.Channel, + CreateTime = DateTime.Now, + CreateTimeDay = 0, + CreateTimeHour = 0, + AppVersion = this.AppRequestInfo.Version, + Ip = ip, + IsNew = isNew, + LoginType = account.LastLoginType, + PlatformType = this.AppRequestInfo.Platform, + UserId = user.Id, + }; + await Dao.DaoExt.Context.AddAsync(login_Log); + await Dao.DaoExt.Context.SaveChangesAsync(); + return accountLogIn; + } + #region 注册用户 + /// + /// 注册新用户或更新现有用户信息 + /// + /// + /// + /// + /// + private async Task RegisterOrUpdateUserAsync(int userId, IUserAccount account, string ip) + { + var user = userId > 0 + ? await Dao.DaoUser.Context.T_User.FirstOrDefaultAsync(it => it.Id == userId) + : null; + if (user == null) + { + user = new T_User + { + CreatedAt = DateTime.Now, + LastLoginAt = DateTime.Now, + UpdatedAt = DateTime.Now, + IsTest = false, + LastLoginType = account.LastLoginType, + RegisterType = account.LastLoginType, + State = 0, + UserIconUrl = AppConfig.UserConfig.UserIconUrl, + NickName = $"{AppConfig.UserConfig.NickName}{new Random().Next(1000, 9999)}", + Ip = ip + }; + await Dao.DaoUser.Context.T_User.AddAsync(user); + } + + user.LastLoginAt = DateTime.Now; + user.UpdatedAt = DateTime.Now; + user.Ip = ip; + await Dao.DaoUser.Context.SaveChangesAsync(); + return user; + } + + // 确保用户数据存在 + private async Task EnsureUserDataExistsAsync(int userId, IUserAccount account) + { + var userData = await Dao.DaoUser.Context.T_User_Data.FirstOrDefaultAsync(it => it.UserId == userId); + if (userData == null) + { + userData = new T_User_Data + { + CreateAt = DateTime.Now, + UpdateAt = DateTime.Now, + PhoneNum = account.GetUserDataProperty(UserDataPropertyEnum.PhoneNum), + UserId = userId, + Email = account.GetUserDataProperty(UserDataPropertyEnum.Email) + }; + await Dao.DaoUser.Context.T_User_Data.AddAsync(userData); + await Dao.DaoUser.Context.SaveChangesAsync(); + } + return userData; + } + + // 生成JWT令牌 + private JwtAuthResult GenerateJwtToken(T_User user) + { + var claims = new[] + { + new Claim("NickName", user.NickName), + new Claim("UserId", user.Id.ToString()), + }; + return JwtAuthManager.GenerateTokens(user.NickName, claims, DateTime.Now); + } + + // 管理用户设备 + private async Task ManageUserDevicesAsync(T_User user, IUserAccount account, string accessToken) + { + var currentTime = DateTime.Now; + var dev = string.IsNullOrEmpty(account.DeviceNumber) + ? MD5Encryption.ComputeMD5Hash($"{user.Id}:{account.LastLoginType}") + : account.DeviceNumber; + + var userLoginList = await Dao.DaoUser.Context.T_User_Token + .Where(it => it.UserId == user.Id) + .OrderBy(it => it.LastLoginAt) + .ToListAsync(); + + // 删除多余的设备记录 + if (userLoginList.Count > AppConfig.UserConfig.MaxDeviceCount) + { + var excessDevices = userLoginList.Take(userLoginList.Count - AppConfig.UserConfig.MaxDeviceCount).ToList(); + Dao.DaoUser.Context.T_User_Token.RemoveRange(excessDevices); + await Dao.DaoUser.Context.SaveChangesAsync(); + } + + var existingDevice = userLoginList.FirstOrDefault(it => it.DeviceNumber == dev); + + if (existingDevice == null) + { + if (userLoginList.Count == AppConfig.UserConfig.MaxDeviceCount) + { + // 替换最早登录的设备 + existingDevice = userLoginList.First(); + UpdateDeviceToken(existingDevice, dev, accessToken, currentTime); + } + else + { + // 新增设备记录 + existingDevice = new T_User_Token + { + CreateAt = currentTime, + ExpiresAt = currentTime.AddDays(5), + LastLoginAt = currentTime, + Token = accessToken, + UserId = user.Id, + DeviceNumber = dev, + TokenMd5 = MD5Encryption.ComputeMD5Hash(accessToken) + }; + await Dao.DaoUser.Context.T_User_Token.AddAsync(existingDevice); + + } + } + else + { + // 更新现有设备信息 + UpdateDeviceToken(existingDevice, dev, accessToken, currentTime); + } + + await Dao.DaoUser.Context.SaveChangesAsync(); + return existingDevice; + } + + // 更新设备令牌信息 + private void UpdateDeviceToken(T_User_Token device, string deviceNumber, string accessToken, DateTime currentTime) + { + device.DeviceNumber = deviceNumber; + device.TokenMd5 = MD5Encryption.ComputeMD5Hash(accessToken); + device.LastLoginAt = currentTime; + device.ExpiresAt = currentTime.AddDays(5); + device.Token = accessToken; + } + + + #endregion + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs new file mode 100644 index 0000000..5f60f12 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountExtend.cs @@ -0,0 +1,42 @@ +using CloudGaming.Code.Account.Contract; +using CloudGaming.Code.Account.Login; +using CloudGaming.DtoModel.Account.Login; + +using Newtonsoft.Json; +using Newtonsoft.Json.Linq; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.Account +{ + public static class AccountExtend + { + + /// + /// 返回对应的登录方式 + /// + /// + /// + public static IUserAccount? GetUserAccount(string jsonString, CloudGamingBase cloudGamingBase) + { + JObject jsonObject = JObject.Parse(jsonString); + if (AllKeysExistIgnoreCase(jsonObject, "PhoneNumber", "VerificationCode")) + { + var loginParams = JsonConvert.DeserializeObject(jsonString); + PhoneUserLogin phoneUserLogin = new PhoneUserLogin(loginParams, cloudGamingBase); + return phoneUserLogin; + } + + return null; + } + static bool AllKeysExistIgnoreCase(JObject jsonObject, params string[] keysToCheck) + { + var propertie = jsonObject.Properties(); + return keysToCheck.All(key => propertie.Any(p => string.Equals(p.Name, key, StringComparison.OrdinalIgnoreCase))); + } + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/Contract/IUserAccount.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/Contract/IUserAccount.cs new file mode 100644 index 0000000..413de82 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/Contract/IUserAccount.cs @@ -0,0 +1,49 @@ +using CloudGaming.DtoModel.Account; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection.Metadata; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.Account.Contract +{ + /// + /// 登录接口 + /// + public interface IUserAccount + { + + /// + /// 登录方式 + /// + public int LastLoginType { get; set; } + + /// + /// 设备号 + /// + public string DeviceNumber { get; } + /// + /// 登录 + /// + /// + Task LoginAsync(); + + + /// + /// 创建账号 + /// + /// + /// + Task CreateLoginAsync(T_User user); + + + /// + /// 获取用户属性 + /// + /// + /// + string GetUserDataProperty(UserDataPropertyEnum userDataPropertyEnum); + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs new file mode 100644 index 0000000..ed596f8 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/Login/PhoneUserLogin.cs @@ -0,0 +1,116 @@ + + +using Bogus.DataSets; + +using CloudGaming.Code.Account.Contract; +using CloudGaming.DtoModel.Account; +using CloudGaming.DtoModel.Account.Login; + +using HuanMeng.DotNetCore.Redis; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.Account.Login +{ + /// + /// 手机号登录 + /// + /// + /// + public class PhoneUserLogin(PhoneLoginParams loginParams, CloudGamingBase cloudGamingBase) : IUserAccount + { + public async Task LoginAsync() + { + if (string.IsNullOrEmpty(loginParams.PhoneNumber)) + { + throw MessageBox.Show(ResonseCode.ParamError, "手机号不能为空"); + } + if (string.IsNullOrEmpty(loginParams.VerificationCode)) + { + throw MessageBox.Show(ResonseCode.ParamError, "验证码不能为空"); + } + //判断是否是测试账号 + if (!loginParams.PhoneNumber.Contains("999999999") && loginParams.VerificationCode == "1112") + { + if (!PhoneNumberValidator.IsPhoneNumber(loginParams.PhoneNumber)) + { + throw MessageBox.Show(ResonseCode.PhoneNumberException, "手机号格式错误"); + } + var verificationCodeRedis = await cloudGamingBase.RedisCache.StringGetAsync($"App:sms:{loginParams.PhoneNumber}"); + if (verificationCodeRedis.IsNullOrEmpty) + { + throw MessageBox.Show(ResonseCode.ParamError, "验证码已过期,请重新发送!"); + } + var verificationCode = (string)verificationCodeRedis; + if (verificationCode != loginParams.VerificationCode) + { + throw MessageBox.Show(ResonseCode.ParamError, "验证码错误!"); + } + await cloudGamingBase.RedisCache.KeyDeleteAsync($"App:sms:{loginParams.PhoneNumber}"); + } + + var userAccount = await cloudGamingBase.Dao.DaoUser.Context.T_User_Phone_Account.AsNoTracking().Where(it => it.PhoneNum == loginParams.PhoneNumber && !it.IsLogout).FirstOrDefaultAsync(); + return userAccount?.UserId ?? 0; + } + + /// + /// 创建登录账号 + /// + /// + /// + public async Task CreateLoginAsync(T_User user) + { + + T_User_Phone_Account t_User_Phone_Account = new T_User_Phone_Account() + { + UserId = user.Id, + IsLogout = false, + NikeName = user.NickName, + PhoneNum = loginParams.PhoneNumber, + UpdatedAt = DateTime.Now, + CreatedAt = DateTime.Now, + }; + await cloudGamingBase.Dao.DaoUser.Context.T_User_Phone_Account.AddAsync(t_User_Phone_Account); + } + + /// + /// + /// + public int LastLoginType { get; set; } = 1; + + /// + /// 返回登录信息 + /// + /// + /// + public string GetUserDataProperty(UserDataPropertyEnum userDataPropertyEnum) + { + if (userDataPropertyEnum == UserDataPropertyEnum.PhoneNum) + { + return loginParams.PhoneNumber; + } + return ""; + } + + /// + /// 设备号 + /// + public string DeviceNumber + { + get + { + if (loginParams.DeviceNumber.Length > 100) + { + loginParams.DeviceNumber = loginParams.DeviceNumber.Substring(0, 100); + } + return loginParams.DeviceNumber; + } + + + } + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs deleted file mode 100644 index db5c9bb..0000000 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunOssConfig.cs +++ /dev/null @@ -1,49 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CloudGaming.Code.AppExtend -{ - public class AliyunOssConfig - { - /// - /// - /// - public string AccessKeyId { get; set; } - /// - /// 配置环境变量 - /// - public string AccessKeySecret { get; set; } - /// - /// 替换为Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。 - /// - public string EndPoint { get; set; } - /// - /// Bucket名称。 - /// - public string BucketName { get; set; } - - /// - /// 上传路径 - /// - public string UploadPath { get; set; } - - /// - /// 域名 - /// - public string? DomainName { get; set; } - - /// - /// 前缀 - /// - public string ImagePrefix - { - get - { - return this.DomainName + this.UploadPath; - } - } - } -} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs index 301b08c..1deee68 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs @@ -1,117 +1,122 @@ +using CloudGaming.Code.AppExtend.Config; + using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; -namespace CloudGaming.Code.AppExtend +namespace CloudGaming.Code.AppExtend; + +/// +/// 项目配置 +/// +public class AppConfig { + public AppConfig() { } /// - /// 项目配置 + /// 用户数据库连接字符串 /// - public class AppConfig + public string UserConnectionString { get; set; } + /// + /// 游戏 + /// + public string GameConnectionString { get; set; } + /// + /// 扩展 + /// + public string ExtConnectionString { get; set; } + + /// + /// 手机app配置 + /// + public string PhoneConnectionString { get; set; } + + /// + /// redis连接字符串 + /// + public string RedisConnectionString { get; set; } + + /// + /// 域名 + /// + public string DomainName { get; set; } + /// + /// 标识 + /// + public string Identifier { get; set; } + /// + /// 名称 + /// + public string Name { get; set; } + + /// + /// 默认语言 + /// + public string DefaultLanguage { get; set; } + /// + /// 租户 + /// + public Guid TenantId { get; set; } + /// + /// 项目支付数据 + /// + //public PaymentModel? Payment { get; set; } + + /// + /// oss阿里云配置 + /// + public AliyunConfig AliyunConfig { get; set; } + + /// + /// 用户默认配置 + /// + public UserConfig UserConfig { get; set; } + + /// + /// 获取数据库连接字符串 + /// + /// user,game,ext,phone + /// + /// + public string GetConnectionString(AppDataBaseType appDataBaseType) { - public AppConfig() { } - /// - /// 用户数据库连接字符串 - /// - public string UserConnectionString { get; set; } - /// - /// 游戏 - /// - public string GameConnectionString { get; set; } - /// - /// 扩展 - /// - public string ExtConnectionString { get; set; } - - /// - /// 手机app配置 - /// - public string PhoneConnectionString { get; set; } - - /// - /// redis连接字符串 - /// - public string RedisConnectionString { get; set; } - - /// - /// 域名 - /// - public string DomainName { get; set; } - /// - /// 标识 - /// - public string Identifier { get; set; } - /// - /// 名称 - /// - public string Name { get; set; } - - /// - /// 默认语言 - /// - public string DefaultLanguage { get; set; } - /// - /// 租户 - /// - public Guid TenantId { get; set; } - /// - /// 项目支付数据 - /// - //public PaymentModel? Payment { get; set; } - - /// - /// oss阿里云配置 - /// - public AliyunOssConfig AliyunConfig { get; set; } - - - /// - /// 获取数据库连接字符串 - /// - /// user,game,ext,phone - /// - /// - public string GetConnectionString(AppDataBaseType appDataBaseType) + switch (appDataBaseType) { - switch (appDataBaseType) - { - case AppDataBaseType.User: - return UserConnectionString; - case AppDataBaseType.Game: - return GameConnectionString; - case AppDataBaseType.Ext: - return ExtConnectionString; - case AppDataBaseType.App: - return PhoneConnectionString; - default: - throw new NotImplementedException("数据库连接字符串不存在"); - } + case AppDataBaseType.User: + return UserConnectionString; + case AppDataBaseType.Game: + return GameConnectionString; + case AppDataBaseType.Ext: + return ExtConnectionString; + case AppDataBaseType.App: + return PhoneConnectionString; + default: + throw new NotImplementedException("数据库连接字符串不存在"); } } - - /// - /// 数据库选项 - /// - - public enum AppDataBaseType - { - /// - /// 用户数据库 - /// - User, - /// - /// 游戏数据库 - /// - Game, - /// - /// 扩展数据库 - /// - Ext, - /// - /// app数据库 - /// - App - } +} + +/// +/// 数据库选项 +/// + +public enum AppDataBaseType +{ + /// + /// 用户数据库 + /// + User, + /// + /// 游戏数据库 + /// + Game, + /// + /// 扩展数据库 + /// + Ext, + /// + /// app数据库 + /// + App } diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs index ae0e434..92115ad 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfigurationExtend.cs @@ -176,6 +176,11 @@ namespace CloudGaming.Code.AppExtend newAppConfig.PhoneConnectionString = appConfig.PhoneConnectionString; newAppConfig.AliyunConfig = appConfig.AliyunConfig; newAppConfig.DefaultLanguage = appConfig.DefaultLanguage; + if (appConfig.UserConfig == null) + { + appConfig.UserConfig = new UserConfig(); + } + newAppConfig.UserConfig = appConfig.UserConfig; return newAppConfig; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/AliyunConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/AliyunConfig.cs new file mode 100644 index 0000000..fb7a6a0 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/AliyunConfig.cs @@ -0,0 +1,67 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.AppExtend.Config; + + +/// +/// 阿里云配置 +/// +public class AliyunConfig +{ + /// + /// + /// + public string AccessKeyId { get; set; } + /// + /// 配置环境变量 + /// + public string AccessKeySecret { get; set; } + + #region 阿里云OSS配置 + /// + /// 替换为Bucket所在地域对应的Endpoint。以华东1(杭州)为例,Endpoint填写为https://oss-cn-hangzhou.aliyuncs.com。 + /// + public string EndPoint { get; set; } + /// + /// Bucket名称。 + /// + public string BucketName { get; set; } + + /// + /// 上传路径 + /// + public string UploadPath { get; set; } + + /// + /// 域名 + /// + public string? DomainName { get; set; } + + /// + /// 前缀 + /// + public string ImagePrefix + { + get + { + return this.DomainName; //+ this.UploadPath + } + } + + #endregion + + /// + /// 短信签名名称 + /// + public string SmsSignName { get; set; } + + /// + /// string 短信模板配置 + /// + public string SmsTemplateCode { get; set; } + +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/UserConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/UserConfig.cs new file mode 100644 index 0000000..7d047b6 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/Config/UserConfig.cs @@ -0,0 +1,27 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.AppExtend.Config; + +/// +/// 用户默认配置 +/// +public class UserConfig +{ + /// + /// 用户默认头像 + /// + public string UserIconUrl { get; set; } + /// + /// 用户默认昵称 + /// + public string NickName { get; set; } + + /// + /// 用户最大登录设备 + /// + public int MaxDeviceCount { get; set; } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomObjectResultExecutor.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomObjectResultExecutor.cs deleted file mode 100644 index 45d4d7d..0000000 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomObjectResultExecutor.cs +++ /dev/null @@ -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):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); - } - } -} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs deleted file mode 100644 index 42fd786..0000000 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/CustomResultFilter.cs +++ /dev/null @@ -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 -{ - /// - /// - /// - 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 keyValuePairs = new Dictionary(); - 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 baseResponse = new BaseResponse(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) - { - // 可在执行完结果后处理其他逻辑 - } - } -} diff --git a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs index c47534b..b5f26aa 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/JwtTokenManageExtension.cs @@ -8,6 +8,7 @@ using System.Text; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication.JwtBearer; using Microsoft.IdentityModel.Tokens; +using System.Text.Json; namespace CloudGaming.Code.AppExtend { @@ -65,6 +66,57 @@ namespace CloudGaming.Code.AppExtend //指定允许令牌的时钟偏移。允许令牌的过期时间与实际时间之间存在的时间差。在这里设置为 5 分钟,表示允许令牌的时钟偏移为 5 分钟。 ClockSkew = TimeSpan.FromMinutes(5) }; + options.Events = new JwtBearerEvents + { + OnTokenValidated = async context => + { + var userId = context.Principal.FindFirst("userId")?.Value; + if (userId == null || true) + { + context.Fail("Token missing userId claim."); + return; + } + + //// 你可以调用数据库或其他服务来验证用户状态 + //var userService = context.HttpContext.RequestServices.GetRequiredService(); + //var user = await userService.GetUserByIdAsync(userId); + //if (user == null || !user.IsActive) + //{ + // context.Fail("User is inactive or not found."); + //} + }, + // 处理认证失败的事件 + OnAuthenticationFailed = context => + { + // 在这里可以根据需要设置自定义的HTTP状态码 + context.Response.StatusCode = StatusCodes.Status403Forbidden; // 设置为 403 Forbidden + context.Response.ContentType = "application/json"; + + // 返回自定义的错误消息 + var result = JsonSerializer.Serialize(new { error = context.Exception?.Message }); + return context.Response.WriteAsync(result); + }, + // 在认证失败并被 Challenge 时触发该事件 + OnChallenge = context => + { + // 如果已经有错误消息,则根据错误原因返回自定义状态码 + if (context.AuthenticateFailure != null) + { + context.HandleResponse(); // 确保不再执行默认的挑战响应 + context.Response.Clear(); // 清空现有响应内容 + context.Response.StatusCode = StatusCodes.Status200OK; // 设置状态码为403 Forbidden + context.Response.ContentType = "application/json"; + + // 构建自定义的 JSON 响应 + var result = JsonSerializer.Serialize(new { error = context.AuthenticateFailure.Message }); + return context.Response.WriteAsync(result); // 写入响应 + } + + // 默认情况下返回 401 Unauthorized + return Task.CompletedTask; + } + }; + }); //注册一个JwtAuthManager的单例服务 builder.Services.AddSingleton(); diff --git a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs index 53cbd72..8f49d1a 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Cache/Special/GameEntityCache.cs @@ -69,14 +69,12 @@ namespace CloudGaming.Code.Cache.Special string chineseName = faker.Internet.UserName(); NickName = chineseName; } - gameInfo.GameShare = $"此游戏由{NickName}分享。"; + gameInfo.GameShare = $"{NickName}"; gameInfo.GameShareUserIcon = 90001; if (gameInfo.ConsumeDiamondNumHour == 0) { gameInfo.ConsumeDiamondNumHour = defaultConsumeDiamondNumHour; } - - if (gameInfo.ConsumeDiamondNumHour > 0) { gameInfo.GameDetailsofCharges = $"游戏资费:游玩按分钟计费,{gameInfo.ConsumeDiamondNumHour}钻石/小时。"; @@ -178,7 +176,11 @@ namespace CloudGaming.Code.Cache.Special { return null; } - return GameInfoDic[gameId] ?? null; + if (GameInfoDic.TryGetValue(gameId, out var gameInfo)) + { + return gameInfo; + } + return null; } } diff --git a/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj b/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj index 7ccf7ec..7904fc4 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj +++ b/src/CloudGaming/Code/CloudGaming.Code/CloudGaming.Code.csproj @@ -10,6 +10,7 @@ + diff --git a/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs index ee1b59e..504bb07 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Config/AppConfigBLL.cs @@ -29,6 +29,7 @@ namespace CloudGaming.Code.Config IsAuthRealName = true }; appConfigDto.IsChecking = IsChecking; + appConfigDto.SignKey = AppConfig.TenantId.ToString("N"); return appConfigDto; } diff --git a/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgBLL.cs index dcd8d67..a950884 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgBLL.cs @@ -51,7 +51,8 @@ namespace CloudGaming.Code.Epg { return; } - var epgInfo = item.ToEpgInfo(Cache.GameEntityCache); + + var epgInfo = item.ToEpgInfo(Cache.GameEntityCache,it.DefaultImageStyle); if (epgInfo != null) { list.Add(epgInfo); diff --git a/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgExtend.cs index b0f5875..1c96da9 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgExtend.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Epg/EpgExtend.cs @@ -1,5 +1,6 @@ using CloudGaming.Code.Cache.Special; using CloudGaming.DtoModel.Epg; +using CloudGaming.DtoModel.Game; using System; using System.Collections.Generic; @@ -20,7 +21,7 @@ namespace CloudGaming.Code.Epg /// /// /// - public static EpgInfo ToEpgInfo(this T_Epg_Cfg epgCfg, GameEntityCache gameEntityCache) + public static EpgInfo ToEpgInfo(this T_Epg_Cfg epgCfg, GameEntityCache gameEntityCache, int defaultImageStyle) { if (epgCfg.ResType == (int)EpgEnum.EpgResType.游戏 && string.IsNullOrEmpty(epgCfg.ResId)) { @@ -55,18 +56,15 @@ namespace CloudGaming.Code.Epg { epgInfo.SubTitle = gameInfo.Title2; } - - if (epgInfo.ImageUrl == 0 && !string.IsNullOrEmpty(epgCfg.ImageResStyle)) + if (epgCfg.ImageResStyle == 0) { - epgInfo.ImageUrl = epgCfg.ImageResStyle switch - { - var style when style == ((int)ImageResStyle.Game尊享推荐_312_420).ToString() => gameInfo.ImageId_ZXTJ, - var style when style == ((int)ImageResStyle.Game推荐位大图_984_520).ToString() => gameInfo.ImageId_TJ, - var style when style == ((int)ImageResStyle.Game游戏库_180_180).ToString() => gameInfo.GameImageId, - var style when style == ((int)ImageResStyle.Game最近推出_360_548).ToString() => gameInfo.ImageId_ZJTC, - var style when style == ((int)ImageResStyle.Game精选推荐_474_300).ToString() => gameInfo.ImageId_JXTJ, - _ => epgInfo.ImageUrl - }; + epgCfg.ImageResStyle = defaultImageStyle; + } + epgInfo.ImageResStyle = epgCfg.ImageResStyle; + //defaultImageStyle + if (epgInfo.ImageUrl == 0 && epgCfg.ImageResStyle != 0) + { + epgInfo.ImageUrl = ((ImageResStyle)epgCfg.ImageResStyle).GetImageResStyle(gameInfo); } epgInfo.Score = gameInfo.Score ?? string.Empty; @@ -74,5 +72,7 @@ namespace CloudGaming.Code.Epg return epgInfo; } + + } } diff --git a/src/CloudGaming/Code/CloudGaming.Code/Extend/MessageAttributeExtend.cs b/src/CloudGaming/Code/CloudGaming.Code/Extend/MessageAttributeExtend.cs new file mode 100644 index 0000000..1d2285d --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Extend/MessageAttributeExtend.cs @@ -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; + + +/// +/// +/// +public static class MessageAttributeExtend +{ + private static readonly ConcurrentDictionary _attributeCache = new ConcurrentDictionary(); + + /// + /// + /// + /// + /// + public static MessageAttribute? GetMessageAttribute(ControllerActionDescriptor controllerActionDescriptor) + { + // 获取方法信息 + var methodInfo = controllerActionDescriptor.MethodInfo; + + // 尝试从缓存中获取MessageAttribute + if (!_attributeCache.TryGetValue(methodInfo, out var messageAttribute)) + { + // 如果缓存中没有,则使用反射获取并存储到缓存中 + messageAttribute = methodInfo.GetCustomAttribute(); + _attributeCache.TryAdd(methodInfo, messageAttribute); + } + + return messageAttribute; + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs new file mode 100644 index 0000000..773e4e8 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs @@ -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 MessageBox message) + { + var obj = new BaseResponse(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()); + } + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomResultFilter.cs b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomResultFilter.cs new file mode 100644 index 0000000..15e43da --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomResultFilter.cs @@ -0,0 +1,80 @@ + + +namespace CloudGaming.Code.Filter; + + +/// +/// +/// +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) + { + 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 baseResponse = new BaseResponse(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()); + } + } + + /// + /// 结果发送到客户端后 + /// + /// + public void OnResultExecuted(ResultExecutedContext context) + { + // 可在执行完结果后处理其他逻辑 + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Game/GameBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Game/GameBLL.cs index 4ccb75f..643a808 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Game/GameBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Game/GameBLL.cs @@ -7,6 +7,8 @@ using System.Linq; using System.Text; using System.Threading.Tasks; +using static CloudGaming.DtoModel.Epg.EpgEnum; + namespace CloudGaming.Code.Game { /// @@ -54,7 +56,7 @@ namespace CloudGaming.Code.Game { var gameList = Cache.GameEntityCache?.DataList ?? new List(); var x = gameList.Where(it => it.GameType?.Any(item => item.Id == typeId) ?? false).Select(it => new - GameListDto(it)).ToList(); + GameListDto(it, ImageResStyle.游戏库)).ToList(); return x; }); return gameListDto; @@ -102,8 +104,47 @@ namespace CloudGaming.Code.Game { gameInfos = Cache.GameInfos.OrderBy(it => Guid.NewGuid()).Take(3).ToList(); } - var gameList = gameInfos?.Select(it => new GameListDto(it)).ToList(); + var gameList = gameInfos?.Select(it => new GameListDto(it, ImageResStyle.竖形图)).ToList(); return gameList ?? new List(); } + + /// + /// 游戏搜索 + /// + /// + /// + public List GameSearch(string gameName) + { + + + var gameIdMatches = Cache.GameInfos + .Where(it => it.GameId.Contains(gameName)) + .Select(it => new GameListDto(it, ImageResStyle.中等LOGO)) + .ToList(); + + var gameNameMatches = Cache.GameInfos + .Where(it => it.GameName.Contains(gameName)) + .Select(it => new GameListDto(it, ImageResStyle.中等LOGO)) + .ToList(); + + var gameIntroduceMatches = Cache.GameInfos + .Where(it => it.GameIntroduce.Contains(gameName) ) + .Select(it => new GameListDto(it, ImageResStyle.中等LOGO)) + .ToList(); + + var otherMatches = Cache.GameInfos + .Where(it => !it.GameId.Contains(gameName) && !it.GameName.Contains(gameName) && !it.GameIntroduce.Contains(gameName) && + (it.GameTags.Any(tag => tag.Name.Contains(gameName)) || it.GameType.Any(types => types.Name.Contains(gameName)))) + .Select(it => new GameListDto(it, ImageResStyle.中等LOGO)) + .ToList(); + + var list = gameIdMatches + .Concat(gameNameMatches) + .Concat(gameIntroduceMatches) + .Concat(otherMatches) + .Distinct() + .ToList(); + return list; + } } } diff --git a/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs index 3876d89..2561ae3 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs @@ -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; \ No newline at end of file +global using Microsoft.Extensions.Hosting; +global using CloudGaming.Code.AppExtend.Config; +global using System.Diagnostics; \ No newline at end of file diff --git a/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/MiddlewareExtends.cs b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/MiddlewareExtends.cs index aa3a709..be7898f 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/MiddlewareExtends.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/MiddlewareExtends.cs @@ -1,45 +1,46 @@ -using HuanMeng.DotNetCore.MiddlewareExtend; +namespace CloudGaming.Code.MiddlewareExtend; -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CloudGaming.Code.MiddlewareExtend +/// +/// +/// +public static class MiddlewareExtends { /// - /// + /// 加载全部中间件 /// - public static class MiddlewareExtends + /// + /// + public static IApplicationBuilder UseCloudGamingMiddlewareAll(this IApplicationBuilder builder) { - /// - /// 加载全部中间件 - /// - /// - /// - public static IApplicationBuilder UseCloudGamingMiddlewareAll(this IApplicationBuilder builder) - { - return builder - .UseCacheMiddleware() - .UseRedisCacheMiddleware() - ; - - } - - /// - /// 缓存中间件 - /// - /// - /// - public static IApplicationBuilder UseCacheMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } - public static IApplicationBuilder UseRedisCacheMiddleware(this IApplicationBuilder builder) - { - return builder.UseMiddleware(); - } + return builder + .UseSignMiddleware() + .UseCacheMiddleware() + .UseRedisCacheMiddleware() + ; } + + /// + /// 缓存中间件 + /// + /// + /// + public static IApplicationBuilder UseCacheMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + public static IApplicationBuilder UseRedisCacheMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } + + /// + /// 加密验证 + /// + /// + /// + public static IApplicationBuilder UseSignMiddleware(this IApplicationBuilder builder) + { + return builder.UseMiddleware(); + } } diff --git a/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/RedisCacheMiddleware.cs b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/RedisCacheMiddleware.cs index fb1080d..39987d5 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/RedisCacheMiddleware.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/RedisCacheMiddleware.cs @@ -111,7 +111,7 @@ namespace CloudGaming.Code.MiddlewareExtend } /// - /// Redis 缓存特性类 + /// Redis 缓存特性类 /// [AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class RedisCacheAttribute : Attribute diff --git a/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/SignMiddleware.cs b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/SignMiddleware.cs new file mode 100644 index 0000000..4e85b85 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/MiddlewareExtend/SignMiddleware.cs @@ -0,0 +1,98 @@ +using Newtonsoft.Json.Linq; +using Newtonsoft.Json.Serialization; +using Newtonsoft.Json; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Security.Cryptography; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.Code.MiddlewareExtend; + +/// +/// 参数请求加密验证 +/// +public class SignMiddleware +{ + + private readonly RequestDelegate _next; + public SignMiddleware(RequestDelegate next) + { + _next = next; + } + + public async Task InvokeAsync(HttpContext context, AppConfig appConfig) + { // 读取请求体 + context.Request.EnableBuffering(); + if (context.Request.Method != "POST" || context.Request.Host.Host == "localhost") + { + // 调用下一个中间件 + await _next(context); + return; + } + + // 启用请求流的多次读取功能 + var requestBody = await new StreamReader(context.Request.Body).ReadToEndAsync(); + context.Request.Body.Position = 0; // 重置请求体的位置 + + if (string.IsNullOrEmpty(requestBody)) + { + await _next(context); + return; + } + // 解析请求体为 JSON 对象 + var requestJson = JObject.Parse(requestBody); + // 获取请求中的 sign 值 + var requestSign = requestJson["sign"]?.ToString(); + if (string.IsNullOrEmpty(requestSign)) + { + var response = GetSignError(context); + context.Response.ContentType = "application/json; charset=utf-8"; + await context.Response.WriteAsync(response); + return; + } + // 获取所有的键值对,并排序 + var sortedKeys = requestJson.Properties() + .Where(p => p.Name != "sign") + .OrderBy(p => p.Name) + .Select(p => p.Value.ToString()) + .ToList(); + + // 拼接所有的值,并加上固定字符串 + var concatenatedValues = string.Join("", sortedKeys) + appConfig.TenantId.ToString("N"); + + // 计算 MD5 哈希值 + var md5Hash = MD5Encryption.ComputeMD5Hash(concatenatedValues); + // 验证 MD5 哈希值与请求中的 sign 是否匹配 + if (md5Hash != requestSign) + { + var response = GetSignError(context); + context.Response.ContentType = "application/json; charset=utf-8"; + context.Response.Headers["X-Request-Sign-Value"] = concatenatedValues; + context.Response.Headers["X-Request-Sign"] = md5Hash; + // 将异常信息写入 HTTP 响应 + await context.Response.WriteAsync(response); + //await context.Response.WriteAsync(""); + return; + } + + // 调用下一个中间件 + await _next(context); + } + + private static string GetSignError(HttpContext context) + { + var settings = new JsonSerializerSettings + { + ContractResolver = new CamelCasePropertyNamesContractResolver() + }; + // 返回 500 错误 + context.Response.StatusCode = 200; + BaseResponse baseResponse = new BaseResponse(ResonseCode.SignError, "sign加密验证失败", null) + { }; + var json = JsonConvert.SerializeObject(baseResponse, settings); + return json; + } +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Sms/AlibabaPhoneNumberVerificationService.cs b/src/CloudGaming/Code/CloudGaming.Code/Sms/AlibabaPhoneNumberVerificationService.cs new file mode 100644 index 0000000..ca79868 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Sms/AlibabaPhoneNumberVerificationService.cs @@ -0,0 +1,116 @@ +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 +{ + /// + /// + /// + public class AlibabaPhoneNumberVerificationService(AliyunConfig aliyunOssConfig) : IPhoneNumberVerificationService + { + + /// Description: + /// + /// 使用AK&SK初始化账号Client + /// + /// + /// + /// Client + /// + /// + /// Exception: + /// 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 = "DgnYOJr0l9hTnl82vI4BxwVgtE1RdL"// aliyunOssConfig.AccessKeySecret, + }; + // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi + config.Endpoint = "dysmsapi.aliyuncs.com"; + return new AlibabaCloud.SDK.Dysmsapi20170525.Client(config); + } + + /// + /// + /// + /// + /// + /// + /// + public async Task 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 + "\"}"// "{\"code\":\"" + code + "\"}", + }; + AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions(); + try + { + // 复制代码运行请自行打印 API 的返回值 + // 复制代码运行请自行打印 API 的返回值 + //client.SendSmsWithOptions(sendSmsRequest, runtime); + var response = await client.SendSmsWithOptionsAsync(sendSmsRequest, runtime); + } + catch (TeaException error) + { + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + Console.WriteLine(error.Message); + // 诊断地址 + Console.WriteLine(error.Data["Recommend"]); + var x = AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); + throw error; + } + catch (Exception _error) + { + TeaException error = new TeaException(new Dictionary + { + { "message", _error.Message } + }); + // 此处仅做打印展示,请谨慎对待异常处理,在工程项目中切勿直接忽略异常。 + // 错误 message + Console.WriteLine(error.Message); + // 诊断地址 + Console.WriteLine(error.Data["Recommend"]); + var x = AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); + throw error; + } + + return true; + } + } + + /// + /// + /// + public static class SmsExtend + { + /// + /// 获取短信 + /// + /// + /// + public static IPhoneNumberVerificationService GetPhoneNumberVerificationService(this AliyunConfig aliyunOssConfig) + { + return new AlibabaPhoneNumberVerificationService(aliyunOssConfig); + } + } + +} diff --git a/src/CloudGaming/Code/CloudGaming.Code/Sms/Contract/IPhoneNumberVerificationService.cs b/src/CloudGaming/Code/CloudGaming.Code/Sms/Contract/IPhoneNumberVerificationService.cs new file mode 100644 index 0000000..4765a88 --- /dev/null +++ b/src/CloudGaming/Code/CloudGaming.Code/Sms/Contract/IPhoneNumberVerificationService.cs @@ -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 +{ + /// + /// 发送手机短信 + /// + public interface IPhoneNumberVerificationService + { + /// + /// 发送验证码到指定的手机号。 + /// + /// 目标手机号。 + /// 验证码 + /// 返回操作是否成功。 + Task SendVerificationCodeAsync(string phoneNumber, string code); + } +} diff --git a/src/CloudGaming/Console/CloudGaming.CreateDataBase/CloudGaming.CreateDataBase.csproj b/src/CloudGaming/Console/CloudGaming.CreateDataBase/CloudGaming.CreateDataBase.csproj index d5fe1c7..8603540 100644 --- a/src/CloudGaming/Console/CloudGaming.CreateDataBase/CloudGaming.CreateDataBase.csproj +++ b/src/CloudGaming/Console/CloudGaming.CreateDataBase/CloudGaming.CreateDataBase.csproj @@ -17,6 +17,7 @@ + diff --git a/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs b/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs index c016595..3967f79 100644 --- a/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs +++ b/src/CloudGaming/Console/CloudGaming.CreateDataBase/Program.cs @@ -1,8 +1,11 @@ // See https://aka.ms/new-console-template for more information using CloudGaming.Model.DbSqlServer.Db_Phone; using CloudGaming.Model.DbSqlServer.Db_User; - +using CloudGaming.DtoModel.Account.Login; using Microsoft.EntityFrameworkCore; + +using Newtonsoft.Json; +//var jsopn = JsonConvert.SerializeObject(new PhoneLoginParams()); //Server=192.168.1.17;Database=CloudGamingUser;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true; var optionsBuilder = new DbContextOptionsBuilder(); var option = optionsBuilder.UseSqlServer("Server=192.168.195.6;Database=CloudGamingPhone;User Id=sa;Password=Dbt@com@123;TrustServerCertificate=true;").Options; diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/AccountLogInResponse.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/AccountLogInResponse.cs new file mode 100644 index 0000000..d703b04 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/AccountLogInResponse.cs @@ -0,0 +1,29 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account +{ + /// + /// 登录返回的数据 + /// + public class AccountLogInResponse + { + /// + /// token + /// + public string Token { get; set; } + + /// + /// 昵称 + /// + public string NickName { get; set; } + + /// + /// 用户id + /// + public int UserId { get; set; } + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/AccountUserLoginInfo.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/AccountUserLoginInfo.cs new file mode 100644 index 0000000..28ff8b7 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/AccountUserLoginInfo.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account.Login +{ + /// + /// + /// + public class AccountUserLoginInfo + { + /// + /// 登录使用的tokens + /// + public string Token { get; set; } + /// + /// 过期时间 + /// + public DateTime ExpirationDate { get; set; } + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/BaseLoginParams.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/BaseLoginParams.cs new file mode 100644 index 0000000..384d22d --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/BaseLoginParams.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account.Login +{ + /// + /// + /// + public class BaseLoginParams + { + /// + /// 设备号 + /// + public string? DeviceNumber { get; set; } = ""; + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/PhoneLoginParams.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/PhoneLoginParams.cs new file mode 100644 index 0000000..29736c8 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/Login/PhoneLoginParams.cs @@ -0,0 +1,25 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account.Login; + +/// +/// 验证码登录 +/// +public class PhoneLoginParams : BaseLoginParams +{ + /// + /// 手机号码 + /// + public string PhoneNumber { get; set; } + + /// + /// 验证码 + /// + public string VerificationCode { get; set; } + +} + diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/PhoneNumberRequest.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/PhoneNumberRequest.cs new file mode 100644 index 0000000..3d04081 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/PhoneNumberRequest.cs @@ -0,0 +1,19 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account +{ + /// + /// + /// + public class PhoneNumberRequest + { + /// + /// 手机号码 + /// + public string? PhoneNumber { get; set; } + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Account/UserDataPropertyEnum.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/UserDataPropertyEnum.cs new file mode 100644 index 0000000..d2ae33f --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Account/UserDataPropertyEnum.cs @@ -0,0 +1,23 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace CloudGaming.DtoModel.Account +{ + /// + /// 用户数据属性 + /// + public enum UserDataPropertyEnum + { + /// + /// 手机号 + /// + PhoneNum = 1, + /// + /// 邮箱 + /// + Email = 2 + } +} diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/AppConfigDto.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/AppConfigDto.cs index 451ffff..51399b9 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/AppConfigDto.cs +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/AppConfigDto.cs @@ -31,7 +31,10 @@ public class AppConfigDto //[Images] //public int OpenImage { get; set; } - + /// + /// 加密key + /// + public string SignKey { get; set; } } diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgEnum.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgEnum.cs index 3bead89..1b09bfe 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgEnum.cs +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgEnum.cs @@ -1,9 +1,13 @@ +using CloudGaming.DtoModel.Game; + using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Threading.Tasks; +using static CloudGaming.DtoModel.Epg.EpgEnum; + namespace CloudGaming.DtoModel.Epg { /// @@ -29,6 +33,8 @@ namespace CloudGaming.DtoModel.Epg public const string 一起玩 = "GameLive"; public const string 一起看 = "VideoLive"; public const string 游玩历史 = "MyHistory"; + + } @@ -37,28 +43,32 @@ namespace CloudGaming.DtoModel.Epg /// public enum ImageResStyle { - /// - /// 游戏-尊享推荐, 竖版 Image_Shu - /// - Game尊享推荐_312_420 = 1, + /// - /// 游戏-推荐位大图 Image_Block2 + /// 小LOGO,适用于首页最近推出、游玩历史、游戏详情页logo。 /// - Game推荐位大图_984_520 = 2, - /// - /// 游戏-精选推荐, 豆腐块图 Image_Block - /// - Game精选推荐_474_300 = 3, - /// - /// 游戏-最近推出, 竖版2,高一点的(最近推出) Image_Shu2 - /// - Game最近推出_360_548 = 4, + 小LOGO = 1, /// - /// 游戏-游戏库, 正方形 (GameImageId) + /// 中等LOGO,适用于首页体育竞技、搜索。 /// - Game游戏库_180_180 = 5, + 中等LOGO = 2, + /// + /// 大LOGO,适用于首页爆款热门。 + /// + 大LOGO = 3, + /// + /// 竖形图,适用于首页蘑菇必玩、首页热血格斗、游戏详情页游戏推荐。 + /// + 竖形图 = 4, + + /// + /// 游戏库,适用于游戏库。 + /// + 游戏库 = 5, + + 系类 = 6, } @@ -91,4 +101,28 @@ namespace CloudGaming.DtoModel.Epg } + + public static class EpgEnumExtend + { + + /// + /// 获取对应样式的图片 + /// + /// + /// + /// + public static int GetImageResStyle(this ImageResStyle imageResStyle, GameInfo? gameInfo) + { + return imageResStyle switch + { + var style when style == ImageResStyle.小LOGO => gameInfo?.ImageIconId, + var style when style == ImageResStyle.中等LOGO => gameInfo?.ImageId_ShouSuo, + var style when style == ImageResStyle.大LOGO => gameInfo?.ImageId_RM, + var style when style == ImageResStyle.游戏库 => gameInfo?.ImageId_YXK, + var style when style == ImageResStyle.竖形图 => gameInfo?.ImageId_TJ, + var style when style == ImageResStyle.系类 => gameInfo?.ImageId_FK, + _ => 0 + } ?? 0; + } + } } diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgInfo.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgInfo.cs index c6cd066..ed3b91c 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgInfo.cs +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Epg/EpgInfo.cs @@ -39,6 +39,10 @@ namespace CloudGaming.DtoModel.Epg /// [Images] public int ImageUrl { get; set; } + /// + /// 图片样式 + /// + public int ImageResStyle { get; set; } /// /// 角标图片信息 diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameInfoDto.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameInfoDto.cs index 04076e8..6a71ed6 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameInfoDto.cs +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameInfoDto.cs @@ -76,7 +76,7 @@ namespace CloudGaming.DtoModel.Game public virtual string? GameIntroduce { get; set; } /// - /// 游戏分享 + /// 游戏分享者 /// public string GameShare { get; set; } diff --git a/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameListDto.cs b/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameListDto.cs index d58e359..e83a1d7 100644 --- a/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameListDto.cs +++ b/src/CloudGaming/Model/CloudGaming.DtoModel/Game/GameListDto.cs @@ -1,3 +1,5 @@ +using CloudGaming.DtoModel.Epg; + using HuanMeng.DotNetCore.AttributeExtend; using System; @@ -6,43 +8,59 @@ using System.Linq; using System.Text; using System.Threading.Tasks; -namespace CloudGaming.DtoModel.Game +using static CloudGaming.DtoModel.Epg.EpgEnum; + +namespace CloudGaming.DtoModel.Game; + +/// +/// 游戏列表数据 +/// +public class GameListDto { + public GameListDto() { } + /// - /// 游戏列表数据 + /// /// - public class GameListDto + /// + public GameListDto(GameInfo gameInfo, ImageResStyle imageResStyle) { - public GameListDto() { } - - /// - /// - /// - /// - public GameListDto(GameInfo gameInfo) + if (gameInfo != null) { - if (gameInfo != null) - { - this.GameName = gameInfo.GameName; - this.GameId = gameInfo.GameId; - this.GameIconImage = gameInfo.GameImageId; + this.GameName = gameInfo.GameName; + this.GameId = gameInfo.GameId; + this.GameIconImage = imageResStyle.GetImageResStyle(gameInfo); + - } } - /// - /// 游戏Id - /// - public string GameId { get; set; } - /// - /// 游戏名称 - /// - public string GameName { get; set; } + } + /// + /// 游戏Id + /// + public string GameId { get; set; } + /// + /// 游戏名称 + /// + public string GameName { get; set; } - /// - /// 游戏icon - /// - [Images] - public int GameIconImage { get; set; } + /// + /// 游戏icon + /// + [Images] + public int GameIconImage { get; set; } + + public override bool Equals(object obj) + { + if (obj is GameListDto other) + { + return string.Equals(this.GameId, other.GameId, StringComparison.Ordinal); + } + return false; + } + + public override int GetHashCode() + { + return this.GameId != null ? this.GameId.GetHashCode() : 0; } } diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs index c0a28a3..da20118 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs @@ -54,6 +54,16 @@ public partial class CloudGamingCBTContext : DbContext /// public virtual DbSet T_App_Image { get; set; } + /// + /// 发送短信日志表 + /// + public virtual DbSet T_Sms_Log { get; set; } + + /// + /// 用户登录日志表 + /// + public virtual DbSet T_User_Login_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 +149,62 @@ public partial class CloudGamingCBTContext : DbContext }); + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__T_Sms_Lo__3214EC07AD037B45"); + + entity.ToTable(tb => tb.HasComment("发送短信日志表")); + + entity.HasIndex(e => new { e.SendTimeDay, e.PhoneNumber }, "T_Sms_Log_SendTimeDay_index_desc").IsDescending(true, false); + + entity.Property(e => e.Id).HasComment("主键"); + entity.Property(e => e.ErrorMessage) + .HasMaxLength(255) + .HasComment("错误信息(如果发送失败)"); + entity.Property(e => e.PhoneNumber) + .HasMaxLength(20) + .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(20) + .HasComment("发送的验证码"); + + }); + + modelBuilder.Entity(entity => + { + entity.HasKey(e => e.Id).HasName("PK__T_User_L__3214EC07C2A990A7"); + + entity.ToTable(tb => tb.HasComment("用户登录日志表")); + + entity.Property(e => e.Id).HasComment("主键"); + entity.Property(e => e.AppVersion) + .HasMaxLength(10) + .HasComment("app版本"); + entity.Property(e => e.Channel) + .HasMaxLength(20) + .HasComment("渠道"); + entity.Property(e => e.CreateTime) + .HasComment("创建时间") + .HasColumnType("datetime"); + entity.Property(e => e.CreateTimeDay).HasComment("创建时间天"); + entity.Property(e => e.CreateTimeHour).HasComment("创建时间小时"); + entity.Property(e => e.Ip) + .HasMaxLength(50) + .HasComment("登录IP"); + entity.Property(e => e.IsNew).HasComment("是否新增用户"); + entity.Property(e => e.LoginType).HasComment("登录方式"); + entity.Property(e => e.PlatformType) + .HasMaxLength(10) + .HasComment("登录平台"); + entity.Property(e => e.UserId).HasComment("用户Id"); + + }); + OnModelCreatingPartial(modelBuilder); } diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Sms_Log.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Sms_Log.cs new file mode 100644 index 0000000..c63941c --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_Sms_Log.cs @@ -0,0 +1,47 @@ +using System; + +namespace CloudGaming.GameModel.Db.Db_Ext; + +/// +/// 发送短信日志表 +/// +public partial class T_Sms_Log +{ + public T_Sms_Log() { } + + /// + /// 主键 + /// + public virtual int Id { get; set; } + + /// + /// 手机号码 + /// + public virtual string PhoneNumber { get; set; } = null!; + + /// + /// 发送的验证码 + /// + public virtual string VerificationCode { get; set; } = null!; + + /// + /// 发送状态(0: 失败, 1: 成功) + /// + /// + public virtual int SendStatus { get; set; } + + /// + /// 发送时间 + /// + public virtual DateTime SendTime { get; set; } + + /// + /// 发送时间,天 + /// + public virtual int SendTimeDay { get; set; } + + /// + /// 错误信息(如果发送失败) + /// + public virtual string? ErrorMessage { get; set; } +} diff --git a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_Login_Log.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_Login_Log.cs new file mode 100644 index 0000000..9ef6f01 --- /dev/null +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/T_User_Login_Log.cs @@ -0,0 +1,66 @@ +using System; + +namespace CloudGaming.GameModel.Db.Db_Ext; + +/// +/// 用户登录日志表 +/// +public partial class T_User_Login_Log +{ + public T_User_Login_Log() { } + + /// + /// 主键 + /// + public virtual int Id { get; set; } + + /// + /// 用户Id + /// + public virtual int UserId { get; set; } + + /// + /// 登录IP + /// + public virtual string Ip { get; set; } = null!; + + /// + /// 是否新增用户 + /// + public virtual bool IsNew { get; set; } + + /// + /// 登录方式 + /// + public virtual int LoginType { get; set; } + + /// + /// 登录平台 + /// + public virtual string PlatformType { get; set; } = null!; + + /// + /// app版本 + /// + public virtual string AppVersion { get; set; } = null!; + + /// + /// 创建时间天 + /// + public virtual int CreateTimeDay { get; set; } + + /// + /// 创建时间小时 + /// + public virtual int CreateTimeHour { get; set; } + + /// + /// 创建时间 + /// + public virtual DateTime CreateTime { get; set; } + + /// + /// 渠道 + /// + public virtual string Channel { get; set; } = null!; +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/AttributeExtend/MessageAttribute.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/AttributeExtend/MessageAttribute.cs new file mode 100644 index 0000000..4800c70 --- /dev/null +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/AttributeExtend/MessageAttribute.cs @@ -0,0 +1,26 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.AttributeExtend +{ + /// + /// 执行成功后返回的消息 + /// + [AttributeUsage(AttributeTargets.Method)] + public class MessageAttribute : Attribute + { + /// + /// 执行成功后返回的消息 + /// + /// 消息内容 + public MessageAttribute(string message) + { + Message = message; + } + + public string Message { get; set; } + } +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs index 3706142..971ad8c 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/EfCoreDaoBase.cs @@ -2,162 +2,161 @@ using Microsoft.EntityFrameworkCore; using System.Linq.Expressions; -namespace HuanMeng.DotNetCore.Base +namespace HuanMeng.DotNetCore.Base; + +/// +/// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational +/// +/// +public class EfCoreDaoBase where TDbContext : DbContext { + private TDbContext _context; + + public TDbContext Context => _context; + /// - /// 基本数据库操作,需要安装 Microsoft.EntityFrameworkCore 和 Microsoft.EntityFrameworkCore.Relational + /// 构造函数 /// - /// - public class EfCoreDaoBase where TDbContext : DbContext + /// + public EfCoreDaoBase(TDbContext context) { - private TDbContext _context; + _context = context ?? throw new ArgumentNullException(nameof(context)); + } - public TDbContext Context => _context; + /// + /// 是否手动提交 + /// + public bool IsManualSubmit { get; set; } - /// - /// 构造函数 - /// - /// - public EfCoreDaoBase(TDbContext context) + /// + /// SqlQueryRaw + /// + public async Task SqlQueryAsync(string sql, params object[] parameters) where T : class + { + return await Context.Database.SqlQueryRaw(sql, parameters).FirstOrDefaultAsync(); + } + + /// + /// SqlQueryList + /// + public async Task> SqlQueryListAsync(string sql, params object[] parameters) + { + return await Context.Database.SqlQueryRaw(sql, parameters).ToListAsync(); + } + + /// + /// ExecuteSql + /// + public async Task ExecuteSqlAsync(string sql, params object[] parameters) + { + var result = await Context.Database.ExecuteSqlRawAsync(sql, parameters); + await AutoSaveChangesAsync(); + return result; + } + + /// + /// 添加实体 + /// + public async Task AddAsync(T entity) where T : class + { + Context.Set().Add(entity); + await AutoSaveChangesAsync(); + } + + /// + /// 批量添加实体 + /// + public async Task AddRangeAsync(IEnumerable entities) where T : class + { + Context.Set().AddRange(entities); + await AutoSaveChangesAsync(); + } + + /// + /// 删除某个实体 + /// + public async Task DeleteAsync(T entity) where T : class + { + Context.Entry(entity).State = EntityState.Deleted; + await AutoSaveChangesAsync(); + } + + /// + /// 更新实体 + /// + public async Task UpdateAsync(T entity) where T : class + { + if (Context.Entry(entity).State == EntityState.Detached) { - _context = context ?? throw new ArgumentNullException(nameof(context)); + Context.Set().Attach(entity); + Context.Entry(entity).State = EntityState.Modified; } + await AutoSaveChangesAsync(); + } - /// - /// 是否手动提交 - /// - public bool IsManualSubmit { get; set; } - - /// - /// SqlQueryRaw - /// - public async Task SqlQueryAsync(string sql, params object[] parameters) where T : class + /// + /// 清除上下文跟踪 + /// + public void RemoveTracking(T entity) where T : class + { + if (Context.Entry(entity).State != EntityState.Detached) { - return await Context.Database.SqlQueryRaw(sql, parameters).FirstOrDefaultAsync(); + Context.Entry(entity).State = EntityState.Detached; } + } - /// - /// SqlQueryList - /// - public async Task> SqlQueryListAsync(string sql, params object[] parameters) - { - return await Context.Database.SqlQueryRaw(sql, parameters).ToListAsync(); - } + /// + /// 获取实体,根据主键 + /// + public async Task GetModelAsync(params object[] keyValues) where T : class + { + return await Context.Set().FindAsync(keyValues); + } - /// - /// ExecuteSql - /// - public async Task ExecuteSqlAsync(string sql, params object[] parameters) - { - var result = await Context.Database.ExecuteSqlRawAsync(sql, parameters); - await AutoSaveChangesAsync(); - return result; - } + /// + /// 获取实体,根据条件 + /// + public async Task GetModelAsync(Expression> where, bool isNoTracking = false) where T : class + { + var query = Context.Set().AsQueryable(); + if (isNoTracking) + query = query.AsNoTracking(); + return await query.FirstOrDefaultAsync(where); + } - /// - /// 添加实体 - /// - public async Task AddAsync(T entity) where T : class - { - Context.Set().Add(entity); - await AutoSaveChangesAsync(); - } + /// + /// 获取列表数据 + /// + public IQueryable GetList(Expression> where, bool isNoTracking = false) where T : class + { + var query = Context.Set().Where(where); + return isNoTracking ? query.AsNoTracking() : query; + } - /// - /// 批量添加实体 - /// - public async Task AddRangeAsync(IEnumerable entities) where T : class - { - Context.Set().AddRange(entities); - await AutoSaveChangesAsync(); - } + /// + /// 获取记录数量 + /// + public async Task GetCountAsync(Expression> where) where T : class + { + return await Context.Set().AsNoTracking().CountAsync(where); + } - /// - /// 删除某个实体 - /// - public async Task DeleteAsync(T entity) where T : class - { - Context.Entry(entity).State = EntityState.Deleted; - await AutoSaveChangesAsync(); - } + /// + /// 判断是否存在记录 + /// + public async Task ExistsAsync(Expression> where) where T : class + { + return await Context.Set().AsNoTracking().AnyAsync(where); + } - /// - /// 更新实体 - /// - public async Task UpdateAsync(T entity) where T : class + /// + /// 根据提交状态决定是否自动保存更改 + /// + private async Task AutoSaveChangesAsync() + { + if (!IsManualSubmit) { - if (Context.Entry(entity).State == EntityState.Detached) - { - Context.Set().Attach(entity); - Context.Entry(entity).State = EntityState.Modified; - } - await AutoSaveChangesAsync(); - } - - /// - /// 清除上下文跟踪 - /// - public void RemoveTracking(T entity) where T : class - { - if (Context.Entry(entity).State != EntityState.Detached) - { - Context.Entry(entity).State = EntityState.Detached; - } - } - - /// - /// 获取实体,根据主键 - /// - public async Task GetModelAsync(params object[] keyValues) where T : class - { - return await Context.Set().FindAsync(keyValues); - } - - /// - /// 获取实体,根据条件 - /// - public async Task GetModelAsync(Expression> where, bool isNoTracking = false) where T : class - { - var query = Context.Set().AsQueryable(); - if (isNoTracking) - query = query.AsNoTracking(); - return await query.FirstOrDefaultAsync(where); - } - - /// - /// 获取列表数据 - /// - public IQueryable GetList(Expression> where, bool isNoTracking = false) where T : class - { - var query = Context.Set().Where(where); - return isNoTracking ? query.AsNoTracking() : query; - } - - /// - /// 获取记录数量 - /// - public async Task GetCountAsync(Expression> where) where T : class - { - return await Context.Set().AsNoTracking().CountAsync(where); - } - - /// - /// 判断是否存在记录 - /// - public async Task ExistsAsync(Expression> where) where T : class - { - return await Context.Set().AsNoTracking().AnyAsync(where); - } - - /// - /// 根据提交状态决定是否自动保存更改 - /// - private async Task AutoSaveChangesAsync() - { - if (!IsManualSubmit) - { - await Context.SaveChangesAsync(); - } + await Context.SaveChangesAsync(); } } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/IResponse.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/IResponse.cs index ecdf588..e4013fd 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/IResponse.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/IResponse.cs @@ -1,23 +1,22 @@ -namespace XLib.DotNetCore.Base +namespace XLib.DotNetCore.Base; + +/// +/// 接口和服务调用通用响应接口 +/// +public interface IResponse { + ///// + ///// Http状态码 + ///// + //HttpStatusCode StatusCode { get; set; } + /// - /// 接口和服务调用通用响应接口 + /// 功能执行返回代码 /// - public interface IResponse - { - ///// - ///// Http状态码 - ///// - //HttpStatusCode StatusCode { get; set; } + int Code { get; set; } - /// - /// 功能执行返回代码 - /// - int Code { get; set; } - - /// - /// 消息 - /// - string Message { get; set; } - } + /// + /// 消息 + /// + string Message { get; set; } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/MessageBox.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/MessageBox.cs new file mode 100644 index 0000000..a377470 --- /dev/null +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/MessageBox.cs @@ -0,0 +1,115 @@ +using Microsoft.AspNetCore.SignalR.Protocol; + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.Serialization; +using System.Text; +using System.Threading.Tasks; + +namespace HuanMeng.DotNetCore.Base +{ + /// + /// + /// + public class MessageBox : Exception + { + /// + /// + /// + /// + public MessageBox(ResonseCode code) + { + Code = (int)code; + Message = ""; + } + /// + /// + /// + /// + /// + /// + public MessageBox(ResonseCode code, string message, object? data = null) + { + Code = (int)code; + Message = message; + Data = data; + } + /// + /// + /// + /// + /// + /// + public MessageBox(int code, string message, object? data = null) + { + Code = code; + Message = message; + Data = data; + } + /// + /// + /// + /// + /// + public MessageBox(string message, object? data = null) + { + Code = 0; + Message = message; + Data = data; + } + /// + /// 功能执行返回代码 + /// + public int Code { get; set; } + + /// + /// 消息 + /// + public string Message { get; set; } + + /// + /// 数据 + /// + public object? Data { get; set; } + + /// + /// 创建错误消息 + /// + /// + /// + public static MessageBox ErrorShow(string message) => new(ResonseCode.Error, message); + + /// + /// 创建消息 + /// + /// + /// + public static MessageBox Show(string message) => new(message); + + /// + /// 创建消息 输出消息和数据 + /// + /// + /// + public static MessageBox Show(string message, object data) => new(message, data); + + /// + /// 创建消息 输出消息和数据 + /// + /// + /// + /// + /// + public static MessageBox Show(int code, string message, object? data = null) => new(code, message, data); + + /// + /// 创建消息 输出消息和数据 + /// + /// + /// + /// + /// + public static MessageBox Show(ResonseCode code, string message, object? data = null) => new(code, message, data); + } +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs index f5ffee2..2484118 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs @@ -1,53 +1,66 @@ -namespace HuanMeng.DotNetCore.Base +namespace HuanMeng.DotNetCore.Base; + +/// +/// 响应编码参考,实际的项目使用可以自行定义 +/// 基本规则: +/// 成功:大于等于0 +/// 失败:小于0 +/// +public enum ResonseCode { /// - /// 响应编码参考,实际的项目使用可以自行定义 - /// 基本规则: - /// 成功:大于等于0 - /// 失败:小于0 + /// Sign签名错误 /// - public enum ResonseCode - { - /// - /// Sign签名错误 - /// - SignError = -999, - /// - /// jwt用户签名错误 - /// - TwtError = -998, + SignError = -999, + /// + /// jwt用户签名错误 + /// + TwtError = -998, - /// - /// 用户验证失败 - /// - Unauthorized = 401, - /// - /// 重复请求 - /// - ManyRequests = 429, + /// + /// 用户验证失败 + /// + Unauthorized = 401, - /// - /// 正在处理中 - /// - Processing = 102, - /// - /// 通用错误 - /// - Error = -1, + /// + /// 重复请求 + /// + ManyRequests = 429, - /// - /// 参数错误 - /// - ParamError = -2, + /// + /// 手机号异常 + /// + PhoneNumberException = 530, + /// + /// 当日手机号发送已到达上限 + /// + PhoneNumberMaxException = 531, + /// + /// 正在处理中 + /// + Processing = 102, + /// + /// 通用错误 + /// + Error = -1, - /// - /// 没找到数据记录 - /// - NotFoundRecord = -3, + /// + /// 参数错误 + /// + ParamError = -2, - /// - /// 成功 - /// - Success = 0, - } + /// + /// 没找到数据记录 + /// + NotFoundRecord = -3, + + /// + /// 数据为null + /// + NullOrEmpty = -4, + + /// + /// 成功 + /// + Success = 0, } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs index 1a37e6d..7e5cf42 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/MiddlewareExtends.cs @@ -17,7 +17,7 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend return builder .UseExceptionMiddleware() .UseExecutionTimeMiddleware() - .UseSignMiddleware() + //.SignBaseMiddleware() ; } @@ -46,9 +46,9 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend /// /// /// - public static IApplicationBuilder UseSignMiddleware(this IApplicationBuilder builder) + public static IApplicationBuilder SignBaseMiddleware(this IApplicationBuilder builder) { - return builder.UseMiddleware(); + return builder.UseMiddleware(); } } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignBaseMiddleware.cs similarity index 97% rename from src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs rename to src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignBaseMiddleware.cs index a27517a..ca0c426 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignMiddleware.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/MiddlewareExtend/SignBaseMiddleware.cs @@ -15,11 +15,11 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend /// /// 参数请求加密验证 /// - public class SignMiddleware + public class SignBaseMiddleware { private readonly RequestDelegate _next; private const string FixedString = "cccc"; // 固定字符串 - public SignMiddleware(RequestDelegate next) + public SignBaseMiddleware(RequestDelegate next) { _next = next; } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs index 16e0968..ca72093 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Redis/RedisConnection.cs @@ -84,6 +84,33 @@ namespace HuanMeng.DotNetCore.Redis // 将 RedisValue 转换为 T 类型 return JsonConvert.DeserializeObject(value); } - + /// + /// 获取一个key的对象 + /// + /// + /// + /// + public static async Task StringGetAsync(this IDatabase database, string key) + { + var value = await database.StringGetAsync(key); + // 检查值是否为空 + if (!value.HasValue) + { + return default(T); + } + if (typeof(T).IsPrimitive || typeof(T) == typeof(string) || typeof(T) == typeof(decimal)) + { + try + { + return (T)Convert.ChangeType(value.ToString(), typeof(T)); + } + catch + { + return default; // 或抛出异常,取决于业务需求 + } + } + // 将 RedisValue 转换为 T 类型 + return JsonConvert.DeserializeObject(value); + } } } diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions.cs new file mode 100644 index 0000000..83b139f --- /dev/null +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions.cs @@ -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; + + +/// +/// object转数据字典 +/// +public static class ObjectExtensions +{ + /// + /// 用于存储每种类型的属性访问器数组的线程安全缓存。 + /// + private static readonly ConcurrentDictionary PropertyCache = new(); + + /// + /// 缓存每个属性是否具有 ImagesAttribute 特性。 + /// + public static readonly ConcurrentDictionary _PropertyCache = new(); + + /// + /// 判断对象是否为原始类型或字符串类型。 + /// + /// 要检查的对象。 + /// 如果对象是原始类型或字符串,返回 true;否则返回 false。 + public static bool IsPrimitiveType(object obj) => + obj is not null && (obj.GetType().IsPrimitive || obj is string or ValueType); + + /// + /// 判断对象是否为集合类型(不包括字符串)。 + /// + /// 要检查的对象。 + /// 如果对象是集合类型(但不是字符串),返回 true;否则返回 false。 + public static bool IsCollectionType(object obj) => obj is IEnumerable && obj is not string; + + /// + /// 根据对象类型,将对象转换为字典或列表格式,支持可选的路径前缀。 + /// + /// 要转换的对象。 + /// 属性路径的可选前缀。 + /// 对象的字典或列表表示形式。 + public static object ToDictionaryOrList(this object obj, string prefix = "", Func? imageFunc = null) + { + if (obj == null) return null; + + return obj switch + { + _ when IsPrimitiveType(obj) => obj, + IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc), + _ => TransformObject(obj, prefix, imageFunc) + }; + } + + /// + /// 将集合对象转换为包含转换项的列表,每个项保留其路径前缀。 + /// + /// 要转换的集合。 + /// 集合中每个属性路径的前缀。 + /// 转换后的项列表。 + private static List TransformCollection(IEnumerable enumerable, string prefix = "", Func? imageFunc = null) + { + var list = new List(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; + } + + /// + /// 将对象的属性转换为带有路径前缀的字典,并应用前缀规则。 + /// + /// 要转换的对象。 + /// 每个属性路径的前缀。 + /// 包含属性名和属性值的字典。 + private static Dictionary TransformObject(object obj, string prefix = "", Func? imageFunc = null) + { + if (obj == null) + { + return null; + } + var type = obj.GetType(); + var accessors = PropertyCache.GetOrAdd(type, CreatePropertyAccessors); + var keyValuePairs = new Dictionary(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; + } + + /// + /// 为给定类型创建属性访问器数组。 + /// + /// 要创建属性访问器的类型。 + /// 属性访问器数组。 + 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() != null); + return new PropertyAccessor(property.Name, getter, isImagesAttribute); + }).ToArray(); + } + + + + private static Func 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>(convertPropertyAccess, parameter).Compile(); + } + + private class PropertyAccessor + { + public string PropertyName { get; } + public Func Getter { get; } + public bool HasImagesAttribute { get; } + + public PropertyAccessor(string propertyName, Func getter, bool hasImagesAttribute) + { + PropertyName = propertyName; + Getter = getter; + HasImagesAttribute = hasImagesAttribute; + } + } +} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs index 32a5b95..1bc2acf 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/ObjectExtensions1.cs @@ -11,157 +11,6 @@ using Microsoft.IdentityModel.Tokens; namespace HuanMeng.DotNetCore.Utility; -/// -/// -/// -public static class ObjectExtensions -{ - /// - /// 用于存储每种类型的属性访问器数组的线程安全缓存。 - /// - private static readonly ConcurrentDictionary PropertyCache = new(); - - /// - /// 缓存每个属性是否具有 ImagesAttribute 特性。 - /// - public static readonly ConcurrentDictionary _PropertyCache = new(); - - /// - /// 判断对象是否为原始类型或字符串类型。 - /// - /// 要检查的对象。 - /// 如果对象是原始类型或字符串,返回 true;否则返回 false。 - public static bool IsPrimitiveType(object obj) => - obj is not null && (obj.GetType().IsPrimitive || obj is string or ValueType); - - /// - /// 判断对象是否为集合类型(不包括字符串)。 - /// - /// 要检查的对象。 - /// 如果对象是集合类型(但不是字符串),返回 true;否则返回 false。 - public static bool IsCollectionType(object obj) => obj is IEnumerable && obj is not string; - - /// - /// 根据对象类型,将对象转换为字典或列表格式,支持可选的路径前缀。 - /// - /// 要转换的对象。 - /// 属性路径的可选前缀。 - /// 对象的字典或列表表示形式。 - public static object ToDictionaryOrList(this object obj, string prefix = "", Func? imageFunc = null) - { - if (obj == null) return null; - - return obj switch - { - _ when IsPrimitiveType(obj) => obj, - IEnumerable enumerable => TransformCollection(enumerable, prefix, imageFunc), - _ => TransformObject(obj, prefix, imageFunc) - }; - } - - /// - /// 将集合对象转换为包含转换项的列表,每个项保留其路径前缀。 - /// - /// 要转换的集合。 - /// 集合中每个属性路径的前缀。 - /// 转换后的项列表。 - private static List TransformCollection(IEnumerable enumerable, string prefix = "", Func? imageFunc = null) - { - var list = new List(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; - } - - /// - /// 将对象的属性转换为带有路径前缀的字典,并应用前缀规则。 - /// - /// 要转换的对象。 - /// 每个属性路径的前缀。 - /// 包含属性名和属性值的字典。 - private static Dictionary TransformObject(object obj, string prefix = "", Func? imageFunc = null) - { - if (obj == null) - { - return null; - } - var type = obj.GetType(); - var accessors = PropertyCache.GetOrAdd(type, CreatePropertyAccessors); - var keyValuePairs = new Dictionary(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; - } - - /// - /// 为给定类型创建属性访问器数组。 - /// - /// 要创建属性访问器的类型。 - /// 属性访问器数组。 - 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() != null); - return new PropertyAccessor(property.Name, getter, isImagesAttribute); - }).ToArray(); - } - - - - private static Func 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>(convertPropertyAccess, parameter).Compile(); - } - - private class PropertyAccessor - { - public string PropertyName { get; } - public Func Getter { get; } - public bool HasImagesAttribute { get; } - - public PropertyAccessor(string propertyName, Func getter, bool hasImagesAttribute) - { - PropertyName = propertyName; - Getter = getter; - HasImagesAttribute = hasImagesAttribute; - } - } -} - [Obsolete] public static class ObjectExtensions11 { diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs index e5b3289..af12a12 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Utility/PhoneNumberValidator.cs @@ -18,6 +18,7 @@ public class PhoneNumberValidator { return false; } + return phoneNumberRegex.IsMatch(input); } }