From d39a6347cdae9962cffff14cae9651eacedabbbe Mon Sep 17 00:00:00 2001 From: niannian zheng Date: Thu, 26 Feb 2026 11:56:14 +0800 Subject: [PATCH] =?UTF-8?q?feat:=20=E5=AE=9E=E7=8E=B0=E5=BE=AE=E4=BF=A1?= =?UTF-8?q?=E7=99=BB=E5=BD=95=E5=92=8C=E7=94=A8=E6=88=B7=E4=BF=A1=E6=81=AF?= =?UTF-8?q?=E7=AE=A1=E7=90=86=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 添加微信登录功能,支持通过微信小程序登录 - 实现用户信息管理接口,包括获取用户信息和统计数据 - 新增投资组合列表和总资产统计接口 - 完善JWT令牌生成逻辑,支持可选用户名 - 添加数据库初始化配置和连接字符串 - 移除传统登录和注册功能,专注微信登录方案 --- .../Controllers/AuthController.cs | 143 ++++++------------ .../Controllers/PortfolioController.cs | 62 ++++++++ .../Controllers/UserController.cs | 114 ++++++++++++-- AssetManager.API/Program.cs | 3 + AssetManager.API/appsettings.json | 5 +- AssetManager.Data/SqlSugarConfig.cs | 2 +- AssetManager.Models/DTOs/AuthDTO.cs | 41 +---- AssetManager.Models/DTOs/PortfolioDTO.cs | 30 ++++ AssetManager.Services/IPortfolioService.cs | 2 + AssetManager.Services/PortfolioService.cs | 69 +++++++++ AssetManager.Services/Services/JwtService.cs | 13 +- .../Services/WechatService.cs | 4 +- 12 files changed, 331 insertions(+), 157 deletions(-) diff --git a/AssetManager.API/Controllers/AuthController.cs b/AssetManager.API/Controllers/AuthController.cs index f8a1e24..6eb3acf 100644 --- a/AssetManager.API/Controllers/AuthController.cs +++ b/AssetManager.API/Controllers/AuthController.cs @@ -4,6 +4,8 @@ using AssetManager.Services.Services; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; using System.Threading.Tasks; +using AssetManager.Data; +using System.Linq; namespace AssetManager.API.Controllers; @@ -14,69 +16,16 @@ public class AuthController : ControllerBase private readonly ILogger _logger; private readonly WechatService _wechatService; private readonly JwtService _jwtService; + private readonly DatabaseService _databaseService; - public AuthController(ILogger logger, WechatService wechatService, JwtService jwtService) + public AuthController(ILogger logger, WechatService wechatService, JwtService jwtService, DatabaseService databaseService) { _logger = logger; _wechatService = wechatService; _jwtService = jwtService; + _databaseService = databaseService; } - [HttpPost("login")] - public ActionResult> Login([FromBody] LoginRequest request) - { - try - { - _logger.LogInformation($"Login attempt for user: {request.Email}"); - - // 模拟登录验证 - if (request.Email == "test@example.com" && request.Password == "password123") - { - // 生成真实的JWT令牌 - var token = _jwtService.GenerateToken("1", "Test User", request.Email); - - var response = new LoginResponse - { - Token = token, - ExpireAt = DateTime.Now.AddHours(24).ToString("yyyy-MM-dd HH:mm:ss"), - User = new UserBasicInfo - { - UserName = "Test User", - Email = request.Email - } - }; - - _logger.LogInformation($"Login successful for user: {request.Email}"); - - return Ok(new ApiResponse - { - code = AssetManager.Models.StatusCodes.Success, - data = response, - message = "Login successful" - }); - } - - _logger.LogWarning($"Login failed for user: {request.Email}"); - - return Unauthorized(new ApiResponse - { - code = AssetManager.Models.StatusCodes.Unauthorized, - data = null, - message = "Invalid email or password" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error during login"); - - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse - { - code = AssetManager.Models.StatusCodes.InternalServerError, - data = null, - message = ex.Message - }); - } - } [HttpPost("wechat-login")] public async Task>> WechatLogin([FromBody] WechatLoginRequest request) @@ -99,8 +48,43 @@ public class AuthController : ControllerBase }); } + // 从数据库中查找用户 + var db = _databaseService.GetDb(); + var user = db.Queryable().Where(u => u.OpenId == authResult.OpenId).First(); + + // 如果用户不存在,创建一个新用户 + if (user == null) + { + user = new User + { + Id = Guid.NewGuid().ToString(), + OpenId = authResult.OpenId, + UserName = request.NickName ?? "Wechat User", + Email = $"{authResult.OpenId}@wechat.com", + MemberLevel = "普通会员", + RunningDays = 0, + SignalsCaptured = 0, + WinRate = 0, + TotalTrades = 0, + TotalReturn = 0, + CreatedAt = DateTime.Now, + UpdatedAt = DateTime.Now + }; + db.Insertable(user).ExecuteCommand(); + } + else + { + // 更新用户信息 + if (!string.IsNullOrEmpty(request.NickName) && user.UserName != request.NickName) + { + user.UserName = request.NickName; + user.UpdatedAt = DateTime.Now; + db.Updateable(user).ExecuteCommand(); + } + } + // 生成真实的JWT令牌 - var token = _jwtService.GenerateToken(authResult.OpenId, request.NickName ?? "Wechat User", $"{authResult.OpenId}@wechat.com"); + var token = _jwtService.GenerateToken(user.Id, user.UserName, user.Email); var response = new WechatLoginResponse { @@ -108,13 +92,14 @@ public class AuthController : ControllerBase ExpireAt = DateTime.Now.AddHours(24).ToString("yyyy-MM-dd HH:mm:ss"), User = new UserBasicInfo { - UserName = request.NickName ?? "Wechat User", - Email = $"{authResult.OpenId}@wechat.com" + UserName = user.UserName, + Email = user.Email }, - OpenId = authResult.OpenId + OpenId = authResult.OpenId, + UserId = user.Id }; - _logger.LogInformation($"Wechat login successful for user: {authResult.OpenId}"); + _logger.LogInformation($"Wechat login successful for user: {user.Id}"); return Ok(new ApiResponse { @@ -135,40 +120,4 @@ public class AuthController : ControllerBase }); } } - - [HttpPost("register")] - public ActionResult> Register([FromBody] RegisterRequest request) - { - try - { - _logger.LogInformation($"Registration attempt for user: {request.Email}"); - - // 模拟注册 - var response = new RegisterResponse - { - Id = Guid.NewGuid().ToString(), - Status = "success" - }; - - _logger.LogInformation($"Registration successful for user: {request.Email}"); - - return Ok(new ApiResponse - { - code = AssetManager.Models.StatusCodes.Success, - data = response, - message = "Registration successful" - }); - } - catch (Exception ex) - { - _logger.LogError(ex, "Error during registration"); - - return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse - { - code = AssetManager.Models.StatusCodes.InternalServerError, - data = null, - message = ex.Message - }); - } - } } \ No newline at end of file diff --git a/AssetManager.API/Controllers/PortfolioController.cs b/AssetManager.API/Controllers/PortfolioController.cs index 90ba0b6..572e439 100644 --- a/AssetManager.API/Controllers/PortfolioController.cs +++ b/AssetManager.API/Controllers/PortfolioController.cs @@ -52,6 +52,68 @@ public class PortfolioController : ControllerBase } } + [HttpGet] + public ActionResult> GetPortfolios() + { + try + { + _logger.LogInformation("Request to get portfolios"); + + var response = _portfolioService.GetPortfolios(); + + _logger.LogInformation("Portfolios retrieved successfully"); + + return Ok(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Success, + data = response, + message = "success" + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving portfolios"); + + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse + { + code = AssetManager.Models.StatusCodes.InternalServerError, + data = null, + message = ex.Message + }); + } + } + + [HttpGet("assets")] + public ActionResult> GetTotalAssets() + { + try + { + _logger.LogInformation("Request to get total assets"); + + var response = _portfolioService.GetTotalAssets(); + + _logger.LogInformation("Total assets retrieved successfully"); + + return Ok(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Success, + data = response, + message = "success" + }); + } + catch (Exception ex) + { + _logger.LogError(ex, "Error retrieving total assets"); + + return StatusCode(AssetManager.Models.StatusCodes.InternalServerError, new ApiResponse + { + code = AssetManager.Models.StatusCodes.InternalServerError, + data = null, + message = ex.Message + }); + } + } + [HttpGet("{id}")] public ActionResult> GetPortfolioById(string id) { diff --git a/AssetManager.API/Controllers/UserController.cs b/AssetManager.API/Controllers/UserController.cs index 5d5f51e..f06af36 100644 --- a/AssetManager.API/Controllers/UserController.cs +++ b/AssetManager.API/Controllers/UserController.cs @@ -3,6 +3,7 @@ using AssetManager.Models; using Microsoft.AspNetCore.Authorization; using Microsoft.AspNetCore.Mvc; using Microsoft.Extensions.Logging; +using AssetManager.Data; namespace AssetManager.API.Controllers; @@ -12,10 +13,12 @@ namespace AssetManager.API.Controllers; public class UserController : ControllerBase { private readonly ILogger _logger; + private readonly DatabaseService _databaseService; - public UserController(ILogger logger) + public UserController(ILogger logger, DatabaseService databaseService) { _logger = logger; + _databaseService = databaseService; } [HttpGet("info")] @@ -25,14 +28,40 @@ public class UserController : ControllerBase { _logger.LogInformation("Request to get user info"); - // 模拟返回用户信息 + // 从JWT中获取用户ID + var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "User not authenticated" + }); + } + + // 从数据库中获取用户信息 + var db = _databaseService.GetDb(); + var user = db.Queryable().Where(u => u.Id == userId).First(); + + if (user == null) + { + return NotFound(new ApiResponse + { + code = AssetManager.Models.StatusCodes.NotFound, + data = null, + message = "User not found" + }); + } + var response = new UserInfoResponse { - UserName = "Test User", - MemberLevel = "高级会员", - RunningDays = 180, - Avatar = "https://example.com/avatar.jpg", - Email = "test@example.com" + UserName = user.UserName, + MemberLevel = user.MemberLevel, + RunningDays = user.RunningDays, + Avatar = user.Avatar, + Email = user.Email }; _logger.LogInformation("User info retrieved successfully"); @@ -64,13 +93,39 @@ public class UserController : ControllerBase { _logger.LogInformation("Request to get user stats"); - // 模拟返回用户统计数据 + // 从JWT中获取用户ID + var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "User not authenticated" + }); + } + + // 从数据库中获取用户信息 + var db = _databaseService.GetDb(); + var user = db.Queryable().Where(u => u.Id == userId).First(); + + if (user == null) + { + return NotFound(new ApiResponse + { + code = AssetManager.Models.StatusCodes.NotFound, + data = null, + message = "User not found" + }); + } + var response = new UserStatsResponse { - SignalsCaptured = 125, - WinRate = 68.5, - TotalTrades = 320, - AverageProfit = 5.2 + SignalsCaptured = user.SignalsCaptured, + WinRate = (double)user.WinRate, + TotalTrades = user.TotalTrades, + AverageProfit = user.TotalTrades > 0 ? (double)user.TotalReturn / user.TotalTrades : 0 }; _logger.LogInformation("User stats retrieved successfully"); @@ -102,11 +157,42 @@ public class UserController : ControllerBase { _logger.LogInformation($"Request to update user info: {request.UserName}"); - // 模拟更新用户信息 + // 从JWT中获取用户ID + var userId = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; + + if (string.IsNullOrEmpty(userId)) + { + return Unauthorized(new ApiResponse + { + code = AssetManager.Models.StatusCodes.Unauthorized, + data = null, + message = "User not authenticated" + }); + } + + // 从数据库中获取用户信息 + var db = _databaseService.GetDb(); + var user = db.Queryable().Where(u => u.Id == userId).First(); + + if (user == null) + { + return NotFound(new ApiResponse + { + code = AssetManager.Models.StatusCodes.NotFound, + data = null, + message = "User not found" + }); + } + + // 更新用户信息 + user.UserName = request.UserName; + user.UpdatedAt = DateTime.Now; + db.Updateable(user).ExecuteCommand(); + var response = new UpdateUserResponse { Status = "updated", - UserName = request.UserName + UserName = user.UserName }; _logger.LogInformation("User info updated successfully"); diff --git a/AssetManager.API/Program.cs b/AssetManager.API/Program.cs index c378bb2..d6c98da 100644 --- a/AssetManager.API/Program.cs +++ b/AssetManager.API/Program.cs @@ -75,6 +75,9 @@ builder.Logging.AddDebug(); var app = builder.Build(); +// 初始化数据库 +app.Services.InitializeDatabase(); + if (app.Environment.IsDevelopment()) { app.UseSwagger(); diff --git a/AssetManager.API/appsettings.json b/AssetManager.API/appsettings.json index 10f68b8..3c153a6 100644 --- a/AssetManager.API/appsettings.json +++ b/AssetManager.API/appsettings.json @@ -5,5 +5,8 @@ "Microsoft.AspNetCore": "Warning" } }, - "AllowedHosts": "*" + "AllowedHosts": "*", + "ConnectionStrings": { + "DefaultConnection": "server=localhost;Database=assetmanager;Uid=root;Pwd=your_password;CharSet=utf8mb4;" + } } diff --git a/AssetManager.Data/SqlSugarConfig.cs b/AssetManager.Data/SqlSugarConfig.cs index 0a3fd83..fd67de2 100644 --- a/AssetManager.Data/SqlSugarConfig.cs +++ b/AssetManager.Data/SqlSugarConfig.cs @@ -8,7 +8,7 @@ public class SqlSugarConfig { return new SqlSugarScope(new ConnectionConfig() { - ConnectionString = "server=localhost;Database=assetmanager;Uid=root;Pwd=your_password;CharSet=utf8mb4;", + ConnectionString = "server=43.167.226.216;Database=assetmanager;Uid=AssetManager;Pwd=2XpcnYGTpB5BhJyG;CharSet=utf8mb4;", DbType = DbType.MySql, IsAutoCloseConnection = true, InitKeyType = InitKeyType.Attribute, diff --git a/AssetManager.Models/DTOs/AuthDTO.cs b/AssetManager.Models/DTOs/AuthDTO.cs index de97dff..65b1808 100644 --- a/AssetManager.Models/DTOs/AuthDTO.cs +++ b/AssetManager.Models/DTOs/AuthDTO.cs @@ -4,14 +4,6 @@ public class LoginRequest { public string Email { get; set; } public string Password { get; set; } - public string OpenId { get; set; } - public string Nickname { get; set; } - public string AvatarUrl { get; set; } - public int Gender { get; set; } - public string Country { get; set; } - public string Province { get; set; } - public string City { get; set; } - public string Language { get; set; } } public class LoginResponse @@ -19,7 +11,7 @@ public class LoginResponse public string Token { get; set; } public string ExpireAt { get; set; } public UserBasicInfo User { get; set; } - public UserInfo UserInfo { get; set; } + public string UserId { get; set; } } public class UserBasicInfo @@ -28,44 +20,17 @@ public class UserBasicInfo public string Email { get; set; } } -public class UserInfo -{ - public string Id { get; set; } - public string UserName { get; set; } - public string Email { get; set; } - public string Avatar { get; set; } - public string MemberLevel { get; set; } - public int RunningDays { get; set; } - public int SignalsCaptured { get; set; } - public double WinRate { get; set; } - public int TotalTrades { get; set; } - public double TotalReturn { get; set; } - public string CreatedAt { get; set; } -} - public class WechatLoginResponse { public string Token { get; set; } public string ExpireAt { get; set; } public UserBasicInfo User { get; set; } public string OpenId { get; set; } -} - -public class RegisterRequest -{ - public string Email { get; set; } - public string Password { get; set; } - public string UserName { get; set; } -} - -public class RegisterResponse -{ - public string Id { get; set; } - public string Status { get; set; } + public string UserId { get; set; } } public class WechatLoginRequest { public string Code { get; set; } - public string NickName { get; set; } + public string? NickName { get; set; } } diff --git a/AssetManager.Models/DTOs/PortfolioDTO.cs b/AssetManager.Models/DTOs/PortfolioDTO.cs index 0817578..181ba44 100644 --- a/AssetManager.Models/DTOs/PortfolioDTO.cs +++ b/AssetManager.Models/DTOs/PortfolioDTO.cs @@ -109,3 +109,33 @@ public class CreateTransactionResponse public string status { get; set; } public string createdAt { get; set; } } + +public class PortfolioListItem +{ + public string id { get; set; } + public string name { get; set; } + public string tags { get; set; } + public string status { get; set; } + public string statusType { get; set; } + public string iconChar { get; set; } + public string iconBgClass { get; set; } + public string iconTextClass { get; set; } + public double value { get; set; } + public string currency { get; set; } + public double returnRate { get; set; } + public string returnType { get; set; } +} + +public class GetPortfoliosResponse +{ + public List items { get; set; } +} + +public class TotalAssetsResponse +{ + public double totalValue { get; set; } + public string currency { get; set; } + public double todayProfit { get; set; } + public string todayProfitCurrency { get; set; } + public double totalReturnRate { get; set; } +} diff --git a/AssetManager.Services/IPortfolioService.cs b/AssetManager.Services/IPortfolioService.cs index 92f6e1a..8f8618b 100644 --- a/AssetManager.Services/IPortfolioService.cs +++ b/AssetManager.Services/IPortfolioService.cs @@ -8,4 +8,6 @@ public interface IPortfolioService PortfolioDetailResponse GetPortfolioById(string id); GetTransactionsResponse GetTransactions(string portfolioId, int limit, int offset); CreateTransactionResponse CreateTransaction(CreateTransactionRequest request); + GetPortfoliosResponse GetPortfolios(); + TotalAssetsResponse GetTotalAssets(); } diff --git a/AssetManager.Services/PortfolioService.cs b/AssetManager.Services/PortfolioService.cs index 054488c..a0e5a48 100644 --- a/AssetManager.Services/PortfolioService.cs +++ b/AssetManager.Services/PortfolioService.cs @@ -108,4 +108,73 @@ public class PortfolioService : IPortfolioService createdAt = DateTime.Now.ToString("yyyy-MM-dd HH:mm:ss") }; } + + public GetPortfoliosResponse GetPortfolios() + { + // 模拟获取投资组合列表 + return new GetPortfoliosResponse + { + items = new List + { + new PortfolioListItem + { + id = "hfea-001", + name = "美股全天候杠杆", + tags = "HFEA · 季度调仓", + status = "监控中", + statusType = "green", + iconChar = "H", + iconBgClass = "bg-green-100", + iconTextClass = "text-green-700", + value = 156240, + currency = "USD", + returnRate = 42.82, + returnType = "positive" + }, + new PortfolioListItem + { + id = "ma-002", + name = "纳指双均线趋势", + tags = "趋势跟踪 · 日线", + status = "等待信号", + statusType = "gray", + iconChar = "T", + iconBgClass = "bg-blue-100", + iconTextClass = "text-blue-700", + value = 412500, + currency = "USD", + returnRate = -1.79, + returnType = "negative" + }, + new PortfolioListItem + { + id = "hk-003", + name = "港股价值投资", + tags = "价值投资 · 蓝筹", + status = "持有中", + statusType = "green", + iconChar = "H", + iconBgClass = "bg-green-100", + iconTextClass = "text-green-700", + value = 896000, + currency = "HKD", + returnRate = 12.56, + returnType = "positive" + } + } + }; + } + + public TotalAssetsResponse GetTotalAssets() + { + // 模拟获取总资产情况 + return new TotalAssetsResponse + { + totalValue = 1284592.4, + currency = "CNY", + todayProfit = 12482, + todayProfitCurrency = "CNY", + totalReturnRate = 24.82 + }; + } } diff --git a/AssetManager.Services/Services/JwtService.cs b/AssetManager.Services/Services/JwtService.cs index 1e97655..2fcad7e 100644 --- a/AssetManager.Services/Services/JwtService.cs +++ b/AssetManager.Services/Services/JwtService.cs @@ -21,14 +21,19 @@ public class JwtService public string GenerateToken(string userId, string userName, string email) { - var claims = new[] + var claims = new List { - new Claim(JwtRegisteredClaimNames.Sub, userId), - new Claim(JwtRegisteredClaimNames.Name, userName), - new Claim(JwtRegisteredClaimNames.Email, email), + new Claim(JwtRegisteredClaimNames.Sub, userId ?? ""), + new Claim(JwtRegisteredClaimNames.Email, email ?? ""), new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()) }; + // 如果userName不为null,添加到claims中 + if (!string.IsNullOrEmpty(userName)) + { + claims.Add(new Claim(JwtRegisteredClaimNames.Name, userName)); + } + var key = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_secretKey)); var credentials = new SigningCredentials(key, SecurityAlgorithms.HmacSha256); diff --git a/AssetManager.Services/Services/WechatService.cs b/AssetManager.Services/Services/WechatService.cs index ee79da6..fd9e723 100644 --- a/AssetManager.Services/Services/WechatService.cs +++ b/AssetManager.Services/Services/WechatService.cs @@ -13,8 +13,8 @@ public class WechatService public WechatService(HttpClient httpClient) { _httpClient = httpClient; - _appId = "your-wechat-app-id"; // 替换为实际的微信小程序AppId - _appSecret = "your-wechat-app-secret"; // 替换为实际的微信小程序AppSecret + _appId = "wx245f0f3ebcfcf5a7"; // 替换为实际的微信小程序AppId + _appSecret = "809c740129bc8b434177ce12ef292dd0"; // 替换为实际的微信小程序AppSecret } public async Task GetOpenIdAsync(string code)