diff --git a/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs index 723b9c8..9e60b6c 100644 --- a/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs +++ b/src/CloudGaming/Api/CloudGaming.Api/Controllers/AccountController.cs @@ -1,9 +1,12 @@ +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; @@ -11,6 +14,7 @@ using HuanMeng.DotNetCore.Base; using HuanMeng.DotNetCore.Utility; +using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; namespace CloudGaming.Api.Controllers; @@ -36,4 +40,29 @@ public class AccountController : CloudGamingControllerBase 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/Code/CloudGaming.Code/Account/AccountBLL.cs b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs index d7ec67e..0b46f8c 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Account/AccountBLL.cs @@ -1,10 +1,20 @@ -using CloudGaming.Code.Sms; +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; @@ -29,13 +39,13 @@ namespace CloudGaming.Code.Account { if (!PhoneNumberValidator.IsPhoneNumber(PhoneNumber)) { - throw new MessageException(ResonseCode.PhoneNumberException, "手机号格式错误"); + 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 new MessageException(ResonseCode.PhoneNumberMaxException, "当日发送以达到上限"); + throw MessageBox.Show(ResonseCode.PhoneNumberMaxException, "当日发送以达到上限"); } var phoneNumberCache = RedisCache.StringGetAsync($"App:sms:{PhoneNumber}"); var verificationCode = new Random().Next(1000, 9999).ToString(); @@ -59,6 +69,7 @@ namespace CloudGaming.Code.Account { await RedisCache.StringSetAsync($"App:sms:{PhoneNumber}", verificationCode, TimeSpan.FromMinutes(5)); } + T_Sms_Log t_Sms_Log = new T_Sms_Log() { VerificationCode = verificationCode, @@ -70,7 +81,214 @@ namespace CloudGaming.Code.Account }; 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/AliyunConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunConfig.cs deleted file mode 100644 index 25f87fa..0000000 --- a/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AliyunConfig.cs +++ /dev/null @@ -1,67 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace CloudGaming.Code.AppExtend -{ - /// - /// 阿里云配置 - /// - 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/AppConfig.cs b/src/CloudGaming/Code/CloudGaming.Code/AppExtend/AppConfig.cs index 70d9c8f..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 AliyunConfig 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/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/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/Filter/CustomExceptionFilter.cs b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs index acf9489..773e4e8 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Filter/CustomExceptionFilter.cs @@ -14,7 +14,7 @@ namespace CloudGaming.Code.Filter { var sw = Stopwatch.StartNew(); // 检查异常是否是特定的异常类型 - if (context.Exception is MessageException message) + if (context.Exception is MessageBox message) { var obj = new BaseResponse(message.Code, message.Message, message.Data); //// 处理特定异常:记录日志、设置响应结果等 diff --git a/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs index a195ca3..2561ae3 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/GlobalUsings.cs @@ -19,5 +19,5 @@ global using Microsoft.EntityFrameworkCore; global using Microsoft.Extensions.Configuration; global using Microsoft.Extensions.DependencyInjection; 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/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 index c80eb1d..ca79868 100644 --- a/src/CloudGaming/Code/CloudGaming.Code/Sms/AlibabaPhoneNumberVerificationService.cs +++ b/src/CloudGaming/Code/CloudGaming.Code/Sms/AlibabaPhoneNumberVerificationService.cs @@ -36,7 +36,7 @@ namespace CloudGaming.Code.Sms // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_ID。 AccessKeyId = "LTAI5tEMoHbcDC5d9CQWovJk",//aliyunOssConfig.AccessKeyId, //Environment.GetEnvironmentVariable("ALIBABA_CLOUD_ACCESS_KEY_ID"), // 必填,请确保代码运行环境设置了环境变量 ALIBABA_CLOUD_ACCESS_KEY_SECRET。 - AccessKeySecret = "gnYOJr0l9hTnl82vI4BxwVgtE1RdL"// aliyunOssConfig.AccessKeySecret, + AccessKeySecret = "DgnYOJr0l9hTnl82vI4BxwVgtE1RdL"// aliyunOssConfig.AccessKeySecret, }; // Endpoint 请参考 https://api.aliyun.com/product/Dysmsapi config.Endpoint = "dysmsapi.aliyuncs.com"; @@ -58,12 +58,15 @@ namespace CloudGaming.Code.Sms SignName = aliyunOssConfig.SmsSignName,// "氢荷健康", TemplateCode = aliyunOssConfig.SmsTemplateCode,// "SMS_154950909", PhoneNumbers = phoneNumber, - TemplateParam = "{\"code\":\"" + code + "\"}", + TemplateParam = "{\"code\":\"" + code + "\"}"// "{\"code\":\"" + code + "\"}", }; + AlibabaCloud.TeaUtil.Models.RuntimeOptions runtime = new AlibabaCloud.TeaUtil.Models.RuntimeOptions(); try { // 复制代码运行请自行打印 API 的返回值 - var response = await client.SendSmsWithOptionsAsync(sendSmsRequest, new AlibabaCloud.TeaUtil.Models.RuntimeOptions()); + // 复制代码运行请自行打印 API 的返回值 + //client.SendSmsWithOptions(sendSmsRequest, runtime); + var response = await client.SendSmsWithOptionsAsync(sendSmsRequest, runtime); } catch (TeaException error) { @@ -72,7 +75,7 @@ namespace CloudGaming.Code.Sms Console.WriteLine(error.Message); // 诊断地址 Console.WriteLine(error.Data["Recommend"]); - AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); + var x = AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); throw error; } catch (Exception _error) @@ -86,7 +89,7 @@ namespace CloudGaming.Code.Sms Console.WriteLine(error.Message); // 诊断地址 Console.WriteLine(error.Data["Recommend"]); - AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); + var x = AlibabaCloud.TeaUtil.Common.AssertAsString(error.Message); throw error; } 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 6c46857..5cff9ee 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.1.17;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/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.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs index ca5ba11..da20118 100644 --- a/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs +++ b/src/CloudGaming/Model/CloudGaming.GameModel/Db/Db_Ext/CloudGamingCBTContext.cs @@ -59,6 +59,11 @@ public partial class CloudGamingCBTContext : DbContext /// 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;"); @@ -150,14 +155,14 @@ public partial class CloudGamingCBTContext : DbContext entity.ToTable(tb => tb.HasComment("发送短信日志表")); - entity.HasIndex(e => e.SendTimeDay, "T_Sms_Log_SendTimeDay_index_desc").IsDescending(); + 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(1) + .HasMaxLength(20) .HasComment("手机号码"); entity.Property(e => e.SendStatus).HasComment("发送状态(0: 失败, 1: 成功)\r\n"); entity.Property(e => e.SendTime) @@ -165,11 +170,41 @@ public partial class CloudGamingCBTContext : DbContext .HasColumnType("datetime"); entity.Property(e => e.SendTimeDay).HasComment("发送时间,天"); entity.Property(e => e.VerificationCode) - .HasMaxLength(1) + .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_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/Model/CloudGaming.Model/DbSqlServer/Db_User/CloudGamingUserContext.cs b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/CloudGamingUserContext.cs index cd73324..255362b 100644 --- a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/CloudGamingUserContext.cs +++ b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/CloudGamingUserContext.cs @@ -413,9 +413,6 @@ public partial class CloudGamingUserContext : MultiTenantDbContext//DbContext .HasComment("修改时间") .HasColumnType("datetime"); entity.Property(e => e.IsLogout).HasComment("是否注销"); - entity.Property(e => e.LastLoginAt) - .HasComment("最后一次登录时间") - .HasColumnType("datetime"); entity.Property(e => e.NikeName) .HasMaxLength(100) .HasComment("用户昵称") @@ -430,11 +427,6 @@ public partial class CloudGamingUserContext : MultiTenantDbContext//DbContext .HasComment("创建时间") .HasColumnType("datetime"); entity.Property(e => e.UserId).HasComment("用户Id"); - entity.Property(e => e.VerificationCode) - .HasMaxLength(10) - .IsUnicode(false) - .HasComment("验证码") - .UseCollation("Chinese_PRC_CI_AS"); //添加全局筛选器 if (this.TenantInfo != null) { @@ -451,11 +443,20 @@ public partial class CloudGamingUserContext : MultiTenantDbContext//DbContext entity.Property(e => e.CreateAt) .HasComment("创建时间") .HasColumnType("datetime"); + entity.Property(e => e.DeviceNumber) + .HasMaxLength(100) + .HasComment("设备号"); entity.Property(e => e.ExpiresAt) .HasComment("过期时间") .HasColumnType("datetime"); + entity.Property(e => e.LastLoginAt) + .HasComment("最后一次登录时间") + .HasColumnType("datetime"); entity.Property(e => e.TenantId).HasComment("租户"); entity.Property(e => e.Token).HasComment("登录token"); + entity.Property(e => e.TokenMd5) + .HasMaxLength(64) + .HasComment("tokenMd5值"); entity.Property(e => e.UpdateAt) .HasComment("修改时间") .HasColumnType("datetime"); diff --git a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Phone_Account.cs b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Phone_Account.cs index 84c3411..df837ac 100644 --- a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Phone_Account.cs +++ b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Phone_Account.cs @@ -29,16 +29,6 @@ public partial class T_User_Phone_Account: MultiTenantEntity /// public virtual string PhoneNum { get; set; } = null!; - /// - /// 验证码 - /// - public virtual string VerificationCode { get; set; } = null!; - - /// - /// 最后一次登录时间 - /// - public virtual DateTime LastLoginAt { get; set; } - /// /// 修改时间 /// diff --git a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Token.cs b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Token.cs index 35160af..9055562 100644 --- a/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Token.cs +++ b/src/CloudGaming/Model/CloudGaming.Model/DbSqlServer/Db_User/T_User_Token.cs @@ -40,4 +40,19 @@ public partial class T_User_Token: MultiTenantEntity /// 修改时间 /// public virtual DateTime? UpdateAt { get; set; } + + /// + /// 设备号 + /// + public virtual string? DeviceNumber { get; set; } + + /// + /// tokenMd5值 + /// + public virtual string? TokenMd5 { get; set; } + + /// + /// 最后一次登录时间 + /// + public virtual DateTime LastLoginAt { 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/MessageException.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/MessageException.cs deleted file mode 100644 index 86f6879..0000000 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/MessageException.cs +++ /dev/null @@ -1,74 +0,0 @@ -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 MessageException : Exception - { - /// - /// - /// - /// - public MessageException(ResonseCode code) - { - Code = (int)code; - Message = ""; - } - /// - /// - /// - /// - /// - /// - public MessageException(ResonseCode code, string message, object? data = null) - { - Code = (int)code; - Message = message; - Data = data; - } - /// - /// - /// - /// - /// - /// - public MessageException(int code, string message, object? data = null) - { - Code = code; - Message = message; - Data = data; - } - /// - /// - /// - /// - /// - public MessageException(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; } - } -} diff --git a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs index c62a436..2484118 100644 --- a/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs +++ b/src/CloudGaming/Utile/HuanMeng.DotNetCore/Base/ResonseCode.cs @@ -54,6 +54,11 @@ public enum ResonseCode /// NotFoundRecord = -3, + /// + /// 数据为null + /// + NullOrEmpty = -4, + /// /// 成功 /// 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/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); } }