提交代码

This commit is contained in:
zpc 2024-11-15 00:13:25 +08:00
parent c89d729bf2
commit 6f41de9079
15 changed files with 273 additions and 75 deletions

View File

@ -2,6 +2,7 @@ using AutoMapper;
using CloudGaming.Code.DataAccess;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace CloudGaming.Api.Base
@ -12,6 +13,7 @@ namespace CloudGaming.Api.Base
/// <param name="_serviceProvider"></param>
[ApiController]
[Route("api/[controller]/[action]")]
public class CloudGamingControllerBase(IServiceProvider _serviceProvider) : ControllerBase
{
/// <summary>

View File

@ -40,16 +40,17 @@ public class AccountController : CloudGamingControllerBase
AccountBLL account = new AccountBLL(ServiceProvider);
return await account.SendPhoneNumber(phoneNumber.PhoneNumber);
}
/// <summary>
/// 登录接口
/// 登录数据格式(设备号的目的是用于以后的多设备登录)
/// 短信登录:{"phoneNumber":"手机号","verificationCode":"验证码","deviceNumber":"设备号"}
/// token登录请求标头需要带上Authorized的token {"deviceNumber":"设备号"}
///登录接口
/// </summary>
/// <param name="baseLogin">
/// 登录数据格式(设备号的目的是用于以后的多设备登录)(设备号可不给)<br />
/// 短信登录:{"phoneNumber":"手机号","verificationCode":"验证码","deviceNumber":"设备号"}<br />
/// token登录请求标头带上Authorized的token即可 {"deviceNumber":"设备号"}<br />
/// </param>
/// <returns></returns>
[HttpPost]
public async Task<AccountLogInResponse> LoginAsync([FromBody] BaseLoginParams baseLogin)
public async Task<AccountLogInResponse> LoginAsync([FromBody] BaseLoginParams? baseLogin)
{
AccountBLL account = new AccountBLL(ServiceProvider);
return await account.LoginAsync();

View File

@ -96,10 +96,11 @@ namespace CloudGaming.Code.Account
{
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, "登录方式不合格");
}
//this.HttpContextAccessor.HttpContext.Request.Headers.GetAuthorization();
//if (string.IsNullOrEmpty(json))
//{
// throw MessageBox.Show(ResonseCode.NullOrEmpty, "登录方式不合格");
//}
var account = AccountExtend.GetUserAccount(json, this);
if (account == null)
@ -213,12 +214,15 @@ namespace CloudGaming.Code.Account
Ip = ip
};
await Dao.DaoUser.Context.T_User.AddAsync(user);
await Dao.DaoUser.Context.SaveChangesAsync();
await account.CreateLoginAsync(user);
}
user.LastLoginAt = DateTime.Now;
user.UpdatedAt = DateTime.Now;
user.Ip = ip;
await Dao.DaoUser.Context.SaveChangesAsync();
return user;
}

View File

@ -23,12 +23,21 @@ namespace CloudGaming.Code.Account
/// <returns></returns>
public static IUserAccount? GetUserAccount(string jsonString, CloudGamingBase cloudGamingBase)
{
JObject jsonObject = JObject.Parse(jsonString);
if (AllKeysExistIgnoreCase(jsonObject, "PhoneNumber", "VerificationCode"))
if (!string.IsNullOrEmpty(jsonString))
{
var loginParams = JsonConvert.DeserializeObject<PhoneLoginParams>(jsonString);
PhoneUserLogin phoneUserLogin = new PhoneUserLogin(loginParams, cloudGamingBase);
return phoneUserLogin;
JObject jsonObject = JObject.Parse(jsonString);
if (AllKeysExistIgnoreCase(jsonObject, "PhoneNumber", "VerificationCode"))
{
var loginParams = JsonConvert.DeserializeObject<PhoneLoginParams>(jsonString);
PhoneUserLogin phoneUserLogin = new PhoneUserLogin(loginParams, cloudGamingBase);
return phoneUserLogin;
} //检测是否还有jwt登录
}
var jwt = cloudGamingBase.HttpContextAccessor.HttpContext.Request.Headers.GetAuthorization();
if (!string.IsNullOrEmpty(jwt))
{
IUserAccount authorizationUserLogin = new AuthorizationUserLogin(jwt, cloudGamingBase);
return authorizationUserLogin;
}
return null;

View File

@ -0,0 +1,87 @@
using Alipay.EasySDK.Kernel;
using CloudGaming.Code.Account.Contract;
using CloudGaming.Code.AppExtend;
using CloudGaming.DtoModel.Account;
using HuanMeng.DotNetCore.JwtInfrastructure;
using HuanMeng.DotNetCore.JwtInfrastructure.Interface;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens.Jwt;
using System.Linq;
using System.Security.Claims;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.Account.Login
{
/// <summary>
/// 自动登录,只能登录,不能注册
/// </summary>
public class AuthorizationUserLogin(string token, CloudGamingBase cloudGamingBase) : IUserAccount
{
/// <summary>
///
/// </summary>
public int LastLoginType { get; set; } = 0;
/// <summary>
///
/// </summary>
public string DeviceNumber { get; set; }
public async Task<int> LoginAsync()
{
if (string.IsNullOrEmpty(token))
{
throw MessageBox.Show(ResonseCode.ParamError, "登录失败");
}
var (principal, jwtToken) = cloudGamingBase.JwtAuthManager.DecodeJwtToken(token);
if (jwtToken == null || !jwtToken.Header.Alg.Equals(SecurityAlgorithms.HmacSha256Signature))
{
throw MessageBox.Show(ResonseCode.ParamError, "无效的token");
//throw new SecurityTokenException("无效的token");
}
var exp = principal.FindFirst("exp")?.Value;
if (string.IsNullOrEmpty(exp))
{
throw MessageBox.Show(ResonseCode.TwtError, "无效的token");
}
var exptime = long.Parse(exp);
long timestamp = DateTimeOffset.UtcNow.ToUnixTimeSeconds();
if (exptime < timestamp)
{
throw MessageBox.Show(ResonseCode.TwtError, "token已经过期");
}
var _userId = principal.FindFirst("userId")?.Value;
var tokenMd5 = MD5Encryption.ComputeMD5Hash(token);
int userId = 0;
if (_userId == null || !int.TryParse(_userId, out userId))
{
throw MessageBox.Show(ResonseCode.TwtError, "token错误");
}
var tokenUser = await cloudGamingBase.Dao.DaoUser.Context.T_User_Token.Where(it => it.UserId == userId && it.TokenMd5 == tokenMd5).FirstOrDefaultAsync();
if (tokenUser == null)
{
throw MessageBox.Show(ResonseCode.UserNotLogin, "token登录失败");
}
return tokenUser?.UserId ?? 0;
}
public Task CreateLoginAsync(T_User user)
{
return Task.CompletedTask;
}
public string GetUserDataProperty(UserDataPropertyEnum userDataPropertyEnum)
{
return "";
}
}
}

View File

@ -109,8 +109,6 @@ namespace CloudGaming.Code.Account.Login
}
return loginParams.DeviceNumber;
}
}
}
}

View File

@ -154,9 +154,9 @@ namespace CloudGaming.Code.AppExtend
/// </summary>
/// <param name="appConfig"></param>
/// <returns></returns>
public static DAO GetDAO(this IServiceProvider serviceProvider)
public static DAO GetDAO(this IServiceProvider serviceProvider, AppConfig appConfig)
{
return new DAO(serviceProvider);
return new DAO(serviceProvider, appConfig);
}

View File

@ -9,6 +9,8 @@ using System.Threading.Tasks;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using System.Text.Json;
using CloudGaming.Code.ExceptionExtend;
using Microsoft.AspNetCore.Http;
namespace CloudGaming.Code.AppExtend
{
@ -80,7 +82,7 @@ namespace CloudGaming.Code.AppExtend
}
var _userId = context.Principal.FindFirst("userId")?.Value;
int userId = 0;
if (_userId == null && !int.TryParse(_userId, out userId))
if (_userId == null || !int.TryParse(_userId, out userId))
{
context.Fail("请求标头错误");
return;
@ -89,6 +91,7 @@ namespace CloudGaming.Code.AppExtend
var appConfig = context.HttpContext.RequestServices.GetRequiredService<AppConfig>();
var host = context.Request.Host.Host;
var app = AppConfigurationExtend.GetAppConfig(host);
app.ToAppConfig(appConfig);
if (app == null)
{
context.Fail("未配置租户");
@ -102,15 +105,15 @@ namespace CloudGaming.Code.AppExtend
{
//再次去数据库中验证
//IServiceProvider
var _serviceProvider = context.HttpContext.RequestServices.GetRequiredService<IServiceProvider>();
var dao = _serviceProvider.GetDAO();
var c = await dao.DaoUser.Context.T_User_Token.Where(it => it.UserId == userId && it.TokenMd5 == tokenMd5).CountAsync();
//var _serviceProvider = context.HttpContext.RequestServices.GetRequiredService<IServiceProvider>();
var dao = context.HttpContext.RequestServices.GetDAO(app);
var c = await dao.DaoUser.Context.T_User_Token.CountAsync(it => it.UserId == userId && it.TokenMd5 == tokenMd5);
if (c <= 0)
{
//添加过期信息
await redis.StringSetAsync($"user:login:{_userId}:{tokenMd5}", "0", TimeSpan.FromMinutes(15));
//app.get
context.Fail("用户状态错误");
context.Fail(new LoginExpiredException(ResonseCode.LoginExpired, "用户状态错误"));
return;
}
else
@ -123,10 +126,10 @@ namespace CloudGaming.Code.AppExtend
if (isUserExpireStatus == "0")
{
//设备被顶掉
context.Fail("用户在其它设备登录");
context.Fail(new LoginExpiredException(ResonseCode.LoginExpired, "用户在其它设备登录"));
return;
}
},
// 处理认证失败的事件
OnAuthenticationFailed = context =>
@ -134,29 +137,52 @@ namespace CloudGaming.Code.AppExtend
// 在这里可以根据需要设置自定义的HTTP状态码
context.Response.StatusCode = StatusCodes.Status403Forbidden; // 设置为 403 Forbidden
context.Response.ContentType = "application/json";
if (context.Exception is LoginExpiredException loginExpired)
{
//context.HttpContext.HandleResponse(); // 确保不再执行默认的挑战响应
context.Response.Clear(); // 清空现有响应内容
context.Response.StatusCode = StatusCodes.Status200OK; // 设置状态码为403 Forbidden
context.Response.ContentType = "application/json";
// 构建自定义的 JSON 响应
var str = loginExpired.ToString();
//var result = JsonSerializer.Serialize(new { error = context.AuthenticateFailure.Message });
return context.Response.WriteAsync(str); // 写入响应
}
context.Response.Clear(); // 清空现有响应内容
context.Response.StatusCode = StatusCodes.Status200OK; // 设置状态码为403 Forbidden
// 返回自定义的错误消息
var result = JsonSerializer.Serialize(new { error = context.Exception?.Message });
return context.Response.WriteAsync(result);
var result = new BaseResponse<object>(ResonseCode.TwtError, "", null);
return context.Response.WriteAsync(result.ToString());
},
// 在认证失败并被 Challenge 时触发该事件
OnChallenge = context =>
{
// 如果已经有错误消息,则根据错误原因返回自定义状态码
if (context.AuthenticateFailure != null)
if (context.AuthenticateFailure is LoginExpiredException loginExpired)
{
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); // 写入响应
var str = loginExpired.ToString();
//var result = JsonSerializer.Serialize(new { error = context.AuthenticateFailure.Message });
return context.Response.WriteAsync(str); // 写入响应
}
// 构建自定义的 JSON 响应
var result = new BaseResponse<object>(ResonseCode.UserNotLogin, "用户未登录", null);
// 如果已经有错误消息,则根据错误原因返回自定义状态码
if (context.AuthenticateFailure != null)
{
result.Message = context.AuthenticateFailure.Message;
}
context.HandleResponse(); // 确保不再执行默认的挑战响应
context.Response.Clear(); // 清空现有响应内容
context.Response.StatusCode = StatusCodes.Status200OK; // 设置状态码为403 Forbidden
context.Response.ContentType = "application/json";
return context.Response.WriteAsync(result.ToString()); // 写入响应
// 默认情况下返回 401 Unauthorized
return Task.CompletedTask;
//return Task.CompletedTask;
}
};

View File

@ -1,4 +1,5 @@
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Tokens;
using System;
using System.Collections.Generic;
@ -29,12 +30,15 @@ namespace CloudGaming.Code.DataAccess.MultiTenantUtil
/// <returns></returns>
public virtual async Task Invoke(HttpContext context,
IServiceProvider _serviceProvider,
AppConfig appConfig )
AppConfig appConfig)
{
var host = context.Request.Host.Host;
var app = AppConfigurationExtend.GetAppConfig(host);
app.ToAppConfig(appConfig);
if (string.IsNullOrEmpty(appConfig.Identifier))
{
app.ToAppConfig(appConfig);
}
await _next.Invoke(context);
}
}

View File

@ -0,0 +1,39 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace CloudGaming.Code.ExceptionExtend
{
/// <summary>
/// 用户登录过期
/// </summary>
public class LoginExpiredException : MessageBox
{
/// <summary>
///
/// </summary>
/// <param name="code"></param>
public LoginExpiredException(ResonseCode code) : base(code)
{
}
/// <summary>
///
/// </summary>
/// <param name="message"></param>
/// <param name="data"></param>
public LoginExpiredException(string message, object? data = null) : base(message, data)
{
}
public LoginExpiredException(ResonseCode code, string message, object? data = null) : base(code, message, data)
{
}
public LoginExpiredException(int code, string message, object? data = null) : base(code, message, data)
{
}
}
}

View File

@ -1,3 +1,5 @@
using HuanMeng.DotNetCore.Base;
using Microsoft.AspNetCore.Mvc;
using System;
@ -8,6 +10,9 @@ using System.Threading.Tasks;
namespace CloudGaming.Code.Filter
{
/// <summary>
/// 异常过滤器
/// </summary>
public class CustomExceptionFilter : IExceptionFilter
{
public void OnException(ExceptionContext context)
@ -16,7 +21,7 @@ namespace CloudGaming.Code.Filter
// 检查异常是否是特定的异常类型
if (context.Exception is MessageBox message)
{
var obj = new BaseResponse<object>(message.Code, message.Message, message.Data);
var obj = message.ToBaseResponse();// new BaseResponse<object>(message.Code, message.Message, message.Data);
//// 处理特定异常:记录日志、设置响应结果等
//Console.WriteLine($"Custom exception caught: {message.Message}");

View File

@ -1,4 +1,9 @@
using Alipay.EasySDK.Kernel;
using Microsoft.AspNetCore.Http;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using System.Runtime.Serialization;
using XLib.DotNetCore.Base;
@ -13,7 +18,7 @@ namespace HuanMeng.DotNetCore.Base
[Serializable]
public class BaseResponse<T> : IResponse
{
///// <summary>
///// Http状态码
///// </summary>
@ -55,14 +60,14 @@ namespace HuanMeng.DotNetCore.Base
Code = code;
Message = message;
Data = default(T);
}
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(int code, string message, T? data)
public BaseResponse(int code, string message = "", T? data = default(T))
{
Code = code;
Message = message;
@ -72,20 +77,25 @@ namespace HuanMeng.DotNetCore.Base
/// <summary>
/// 构造函数
/// </summary>
public BaseResponse(ResonseCode code, string message, T? data)
public BaseResponse(ResonseCode code, string message = "", T? data = default(T))
{
Code = (int)code;
Message = message;
Data = data;
}
/// <summary>
/// ToString
///
/// </summary>
/// <returns></returns>
public override string ToString()
{
return $"Code:{Code};Message:{Message}; Data:{Data}";
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
return JsonConvert.SerializeObject(this, settings);
}
}

View File

@ -1,5 +1,8 @@
using Microsoft.AspNetCore.SignalR.Protocol;
using Newtonsoft.Json.Serialization;
using Newtonsoft.Json;
using System;
using System.Collections.Generic;
using System.Linq;
@ -14,14 +17,23 @@ namespace HuanMeng.DotNetCore.Base
/// </summary>
public class MessageBox : Exception
{
/// <summary>
/// 基础数据
/// </summary>
public BaseResponse<object> BaseResponse { get; set; }
/// <summary>
///
/// </summary>
/// <param name="code"></param>
public MessageBox(ResonseCode code)
{
Code = (int)code;
Message = "";
BaseResponse = new Base.BaseResponse<object>(code);
}
public MessageBox(BaseResponse<object> baseResponse)
{
this.BaseResponse = baseResponse;
}
/// <summary>
///
@ -31,9 +43,8 @@ namespace HuanMeng.DotNetCore.Base
/// <param name="data"></param>
public MessageBox(ResonseCode code, string message, object? data = null)
{
Code = (int)code;
Message = message;
Data = data;
BaseResponse = new Base.BaseResponse<object>(code, message, data);
}
/// <summary>
///
@ -43,9 +54,7 @@ namespace HuanMeng.DotNetCore.Base
/// <param name="data"></param>
public MessageBox(int code, string message, object? data = null)
{
Code = code;
Message = message;
Data = data;
BaseResponse = new Base.BaseResponse<object>(code, message, data);
}
/// <summary>
///
@ -54,25 +63,27 @@ namespace HuanMeng.DotNetCore.Base
/// <param name="data"></param>
public MessageBox(string message, object? data = null)
{
Code = 0;
Message = message;
Data = data;
BaseResponse = new Base.BaseResponse<object>(0, message, data);
}
/// <summary>
/// 功能执行返回代码
/// </summary>
public int Code { get; set; }
/// <summary>
/// 消息
///
/// </summary>
public string Message { get; set; }
/// <returns></returns>
public BaseResponse<object> ToBaseResponse()
{
return BaseResponse;
}
/// <summary>
/// 数据
///
/// </summary>
public object? Data { get; set; }
/// <returns></returns>
public override string ToString()
{
return BaseResponse.ToString();
}
/// <summary>
/// 创建错误消息
/// </summary>

View File

@ -17,11 +17,20 @@ public enum ResonseCode
/// </summary>
TwtError = -998,
/// <summary>
/// 用户未登录
/// </summary>
UserNotLogin = 400,
/// <summary>
/// 用户验证失败
/// </summary>
Unauthorized = 401,
/// <summary>
/// 用户登录过期
/// </summary>
LoginExpired = 402,
/// <summary>
/// 重复请求
/// </summary>

View File

@ -33,11 +33,7 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend
}
catch (ArgumentNullException ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.ParamError, ex.ParamName ?? "参数错误", null)
@ -46,21 +42,18 @@ namespace HuanMeng.DotNetCore.MiddlewareExtend
};
context.Response.ContentType = "application/json; charset=utf-8";
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
await context.Response.WriteAsync(baseResponse.ToString());
}
catch (Exception ex)
{
var settings = new JsonSerializerSettings
{
ContractResolver = new CamelCasePropertyNamesContractResolver()
};
_logger.LogError(context.Request.Path.ToString(), ex, "异常记录");
// 设置 HTTP 响应状态码为 500
context.Response.ContentType = "application/json; charset=utf-8";
context.Response.StatusCode = StatusCodes.Status200OK;
BaseResponse<object> baseResponse = new BaseResponse<object>(ResonseCode.Error, ex.Message, null);
// 将异常信息写入 HTTP 响应
await context.Response.WriteAsync(JsonConvert.SerializeObject(baseResponse, settings));
await context.Response.WriteAsync(baseResponse.ToString());
}
finally
{