From b5499ef7fe7668ac2d69c35f59c82a2d19f8431b Mon Sep 17 00:00:00 2001 From: niannian zheng Date: Fri, 6 Mar 2026 15:51:59 +0800 Subject: [PATCH] =?UTF-8?q?refactor:=20=E5=B0=86=E6=A8=A1=E5=9E=8B?= =?UTF-8?q?=E5=B1=9E=E6=80=A7=E6=94=B9=E4=B8=BA=E5=8F=AF=E4=B8=BA=E7=A9=BA?= =?UTF-8?q?=E7=B1=BB=E5=9E=8B=E4=BB=A5=E5=A2=9E=E5=BC=BA=E5=81=A5=E5=A3=AE?= =?UTF-8?q?=E6=80=A7?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 修改ApiResponse、RiskParityConfig等DTO类的属性为可空类型 - 在策略计算器中添加空值检查逻辑 - 更新服务层代码处理可能的空值情况 - 添加发布配置文件FolderProfile.pubxml --- .../Controllers/AuthController.cs | 11 +- .../Controllers/PortfolioController.cs | 11 +- .../PublishProfiles/FolderProfile.pubxml | 15 ++ AssetManager.Data/Portfolio.cs | 42 +++--- AssetManager.Data/Strategy.cs | 16 +-- AssetManager.Data/User.cs | 12 +- .../Calculators/ChandelierExitCalculator.cs | 5 + .../Calculators/MaTrendCalculator.cs | 5 + .../Calculators/RiskParityCalculator.cs | 15 +- .../StrategyEngine/StrategyEngine.cs | 2 +- AssetManager.Models/ApiResponse.cs | 4 +- AssetManager.Models/DTOs/AuthDTO.cs | 28 ++-- AssetManager.Models/DTOs/MarketDTO.cs | 8 +- AssetManager.Models/DTOs/PortfolioDTO.cs | 124 ++++++++-------- AssetManager.Models/DTOs/RiskParityConfig.cs | 4 +- AssetManager.Models/DTOs/StrategyDTO.cs | 136 +++++++++--------- AssetManager.Models/DTOs/StrategySignal.cs | 14 +- AssetManager.Models/DTOs/UserDTO.cs | 22 +-- .../AssetManager.Services.csproj | 1 + AssetManager.Services/PortfolioService.cs | 36 +++-- .../Services/WechatService.cs | 10 +- 21 files changed, 295 insertions(+), 226 deletions(-) create mode 100644 AssetManager.API/Properties/PublishProfiles/FolderProfile.pubxml diff --git a/AssetManager.API/Controllers/AuthController.cs b/AssetManager.API/Controllers/AuthController.cs index d82c8ec..b1c7a6b 100644 --- a/AssetManager.API/Controllers/AuthController.cs +++ b/AssetManager.API/Controllers/AuthController.cs @@ -44,6 +44,15 @@ public class AuthController : ControllerBase _logger.LogInformation("Wechat login attempt"); // 用code换取openid + if (request.Code == null) + { + return BadRequest(new ApiResponse + { + code = AssetManager.Models.StatusCodes.BadRequest, + data = null, + message = "Code is required" + }); + } var authResult = await _wechatService.GetOpenIdAsync(request.Code); if (authResult.Errcode != 0) @@ -93,7 +102,7 @@ public class AuthController : ControllerBase } // 生成真实的JWT令牌 - var token = _jwtService.GenerateToken(user.Id, user.UserName, user.Email); + var token = _jwtService.GenerateToken(user.Id ?? "", user.UserName ?? "", user.Email ?? ""); var response = new WechatLoginResponse { diff --git a/AssetManager.API/Controllers/PortfolioController.cs b/AssetManager.API/Controllers/PortfolioController.cs index 915a8e1..4a523a6 100644 --- a/AssetManager.API/Controllers/PortfolioController.cs +++ b/AssetManager.API/Controllers/PortfolioController.cs @@ -41,7 +41,7 @@ public class PortfolioController : ControllerBase /// private string GetCurrentUserId() { - return User.FindFirst(ClaimTypes.NameIdentifier)?.Value; + return User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? throw new Exception("User not authenticated"); } /// @@ -433,6 +433,15 @@ public class PortfolioController : ControllerBase } // 2. 获取策略 + if (portfolio.StrategyId == null) + { + return NotFound(new ApiResponse + { + code = AssetManager.Models.StatusCodes.NotFound, + data = null, + message = "策略ID不存在" + }); + } var strategy = _strategyService.GetStrategyById(portfolio.StrategyId, userId); if (strategy == null) { diff --git a/AssetManager.API/Properties/PublishProfiles/FolderProfile.pubxml b/AssetManager.API/Properties/PublishProfiles/FolderProfile.pubxml new file mode 100644 index 0000000..f524c66 --- /dev/null +++ b/AssetManager.API/Properties/PublishProfiles/FolderProfile.pubxml @@ -0,0 +1,15 @@ + + + + + false + false + true + Release + Any CPU + FileSystem + bin\Release\net8.0\publish\ + FileSystem + <_TargetId>Folder + + \ No newline at end of file diff --git a/AssetManager.Data/Portfolio.cs b/AssetManager.Data/Portfolio.cs index 1b6b68d..b9a316d 100644 --- a/AssetManager.Data/Portfolio.cs +++ b/AssetManager.Data/Portfolio.cs @@ -12,31 +12,31 @@ public class Portfolio /// 主键 /// [SugarColumn(IsPrimaryKey = true, IsIdentity = false)] - public string Id { get; set; } + public string? Id { get; set; } /// /// 所属用户ID /// [SugarColumn(ColumnName = "user_id", IndexGroupNameList = new string[] { "idx_user_id" })] - public string UserId { get; set; } + public string? UserId { get; set; } /// /// 所用策略ID /// [SugarColumn(ColumnName = "strategy_id", IndexGroupNameList = new string[] { "idx_strategy_id" })] - public string StrategyId { get; set; } + public string? StrategyId { get; set; } /// /// 组合名称 /// [SugarColumn(ColumnName = "name", Length = 200)] - public string Name { get; set; } + public string? Name { get; set; } /// /// 组合币种 (USD/CNY等) /// [SugarColumn(ColumnName = "currency", Length = 10)] - public string Currency { get; set; } + public string? Currency { get; set; } /// /// 当前总市值 (可冗余或实时计算) @@ -54,7 +54,7 @@ public class Portfolio /// 运行状态 (运行中/监控中) /// [SugarColumn(ColumnName = "status", Length = 50)] - public string Status { get; set; } + public string? Status { get; set; } /// /// 创建时间 @@ -79,31 +79,31 @@ public class Position /// 主键 /// [SugarColumn(IsPrimaryKey = true, IsIdentity = false)] - public string Id { get; set; } + public string? Id { get; set; } /// /// 所属组合ID /// [SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })] - public string PortfolioId { get; set; } + public string? PortfolioId { get; set; } /// /// 标的代码 (如: UPRO.US) /// [SugarColumn(ColumnName = "stock_code", Length = 50)] - public string StockCode { get; set; } + public string? StockCode { get; set; } /// /// 标的名称 /// [SugarColumn(ColumnName = "stock_name", Length = 200)] - public string StockName { get; set; } + public string? StockName { get; set; } /// /// 资产类型 (Stock/Crypto) /// [SugarColumn(ColumnName = "asset_type", Length = 20)] - public string AssetType { get; set; } + public string? AssetType { get; set; } /// /// 持有数量 @@ -121,7 +121,7 @@ public class Position /// 标的币种 /// [SugarColumn(ColumnName = "currency", Length = 10)] - public string Currency { get; set; } + public string? Currency { get; set; } /// /// 建仓时间 @@ -146,37 +146,37 @@ public class Transaction /// 主键 /// [SugarColumn(IsPrimaryKey = true, IsIdentity = false)] - public string Id { get; set; } + public string? Id { get; set; } /// /// 所属组合ID /// [SugarColumn(ColumnName = "portfolio_id", IndexGroupNameList = new string[] { "idx_portfolio_id" })] - public string PortfolioId { get; set; } + public string? PortfolioId { get; set; } /// /// 交易类型 (buy/sell) /// [SugarColumn(ColumnName = "type", Length = 20)] - public string Type { get; set; } + public string? Type { get; set; } /// /// 标的代码 /// [SugarColumn(ColumnName = "stock_code", Length = 50)] - public string StockCode { get; set; } + public string? StockCode { get; set; } /// /// 资产类型 (Stock/Crypto) /// [SugarColumn(ColumnName = "asset_type", Length = 20)] - public string AssetType { get; set; } + public string? AssetType { get; set; } /// /// 交易标题 (如: 定期定投) /// [SugarColumn(ColumnName = "title", Length = 200)] - public string Title { get; set; } + public string? Title { get; set; } /// /// 交易数量 @@ -200,19 +200,19 @@ public class Transaction /// 交易币种 /// [SugarColumn(ColumnName = "currency", Length = 10)] - public string Currency { get; set; } + public string? Currency { get; set; } /// /// 交易状态 (processing/completed) /// [SugarColumn(ColumnName = "status", Length = 50)] - public string Status { get; set; } + public string? Status { get; set; } /// /// 交易备注 /// [SugarColumn(ColumnName = "remark", Length = 500, IsNullable = true)] - public string Remark { get; set; } + public string? Remark { get; set; } /// /// 交易发生时间 diff --git a/AssetManager.Data/Strategy.cs b/AssetManager.Data/Strategy.cs index 2ef7bb1..60f5939 100644 --- a/AssetManager.Data/Strategy.cs +++ b/AssetManager.Data/Strategy.cs @@ -12,49 +12,49 @@ public class Strategy /// 主键 /// [SugarColumn(IsPrimaryKey = true, IsIdentity = false)] - public string Id { get; set; } + public string? Id { get; set; } /// /// 所属用户ID /// [SugarColumn(ColumnName = "user_id", Length = 100, IndexGroupNameList = new string[] { "idx_user_id" })] - public string UserId { get; set; } + public string? UserId { get; set; } /// /// 策略别名/名称 /// [SugarColumn(ColumnName = "alias", Length = 200)] - public string Alias { get; set; } + public string? Alias { get; set; } /// /// 策略类型 (如: ma_trend, chandelier_exit, risk_parity) /// [SugarColumn(ColumnName = "type", Length = 50)] - public string Type { get; set; } + public string? Type { get; set; } /// /// 策略描述 /// [SugarColumn(ColumnName = "description", Length = 500)] - public string Description { get; set; } + public string? Description { get; set; } /// /// 策略标签 (逗号分隔的字符串) /// [SugarColumn(ColumnName = "tags")] - public string Tags { get; set; } + public string? Tags { get; set; } /// /// 风险等级 /// [SugarColumn(ColumnName = "risk_level", Length = 20)] - public string RiskLevel { get; set; } + public string? RiskLevel { get; set; } /// /// 策略配置项(周期,阈值,资产配比) /// [SugarColumn(ColumnName = "config", IsJson = true)] - public string Config { get; set; } + public string? Config { get; set; } /// /// 创建时间 diff --git a/AssetManager.Data/User.cs b/AssetManager.Data/User.cs index dae2e17..1a723b3 100644 --- a/AssetManager.Data/User.cs +++ b/AssetManager.Data/User.cs @@ -12,37 +12,37 @@ public class User /// 主键 /// [SugarColumn(IsPrimaryKey = true, IsIdentity = false)] - public string Id { get; set; } + public string? Id { get; set; } /// /// 微信OpenID (UK) /// [SugarColumn(ColumnName = "open_id", Length = 100, IsNullable = true)] - public string OpenId { get; set; } + public string? OpenId { get; set; } /// /// 用户名 /// [SugarColumn(ColumnName = "user_name", Length = 100)] - public string UserName { get; set; } + public string? UserName { get; set; } /// /// 邮箱 /// [SugarColumn(ColumnName = "email", Length = 255, IsNullable = true)] - public string Email { get; set; } + public string? Email { get; set; } /// /// 头像URL /// [SugarColumn(ColumnName = "avatar", Length = 500, IsNullable = true)] - public string Avatar { get; set; } + public string? Avatar { get; set; } /// /// 会员等级 /// [SugarColumn(ColumnName = "member_level", Length = 50)] - public string MemberLevel { get; set; } + public string? MemberLevel { get; set; } /// /// 用户默认本位币 (CNY/USD/HKD) diff --git a/AssetManager.Infrastructure/StrategyEngine/Calculators/ChandelierExitCalculator.cs b/AssetManager.Infrastructure/StrategyEngine/Calculators/ChandelierExitCalculator.cs index 34cbdaa..1ccdeb8 100644 --- a/AssetManager.Infrastructure/StrategyEngine/Calculators/ChandelierExitCalculator.cs +++ b/AssetManager.Infrastructure/StrategyEngine/Calculators/ChandelierExitCalculator.cs @@ -55,6 +55,11 @@ public class ChandelierExitCalculator : IStrategyCalculator { cancellationToken.ThrowIfCancellationRequested(); + if (position.StockCode == null) + { + continue; + } + try { // 获取历史 K 线数据(需要 period + 1 根来计算 ATR) diff --git a/AssetManager.Infrastructure/StrategyEngine/Calculators/MaTrendCalculator.cs b/AssetManager.Infrastructure/StrategyEngine/Calculators/MaTrendCalculator.cs index 6a9752b..c3dac7f 100644 --- a/AssetManager.Infrastructure/StrategyEngine/Calculators/MaTrendCalculator.cs +++ b/AssetManager.Infrastructure/StrategyEngine/Calculators/MaTrendCalculator.cs @@ -55,6 +55,11 @@ public class MaTrendCalculator : IStrategyCalculator { cancellationToken.ThrowIfCancellationRequested(); + if (position.StockCode == null) + { + continue; + } + try { // 获取历史 K 线数据(需要 longPeriod + 1 根) diff --git a/AssetManager.Infrastructure/StrategyEngine/Calculators/RiskParityCalculator.cs b/AssetManager.Infrastructure/StrategyEngine/Calculators/RiskParityCalculator.cs index c11ef8f..e242399 100644 --- a/AssetManager.Infrastructure/StrategyEngine/Calculators/RiskParityCalculator.cs +++ b/AssetManager.Infrastructure/StrategyEngine/Calculators/RiskParityCalculator.cs @@ -36,14 +36,14 @@ public class RiskParityCalculator : IStrategyCalculator var targetAssets = new List(); var userDefinedWeights = new Dictionary(); - if (config.Assets?.Count > 0) + if (config.Assets?.Count >0) { - targetAssets = config.Assets.Select(a => a.Symbol).ToList(); - userDefinedWeights = config.Assets.ToDictionary(a => a.Symbol, a => a.TargetWeight); + targetAssets = config.Assets.Where(a => a.Symbol != null).Select(a => a.Symbol!).ToList(); + userDefinedWeights = config.Assets.Where(a => a.Symbol != null).ToDictionary(a => a.Symbol!, a => a.TargetWeight); } else { - targetAssets = positions.Select(p => p.StockCode).ToList(); + targetAssets = positions.Where(p => p.StockCode != null).Select(p => p.StockCode!).ToList(); } if (targetAssets.Count == 0) @@ -219,8 +219,13 @@ public class RiskParityCalculator : IStrategyCalculator { cancellationToken.ThrowIfCancellationRequested(); + if (position.StockCode == null) + { + continue; + } + MarketPriceResponse priceResponse; - if (position.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) + if (position.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true) { priceResponse = await _marketDataService.GetCryptoPriceAsync(position.StockCode); } diff --git a/AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs b/AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs index 8d2a589..e506ff2 100644 --- a/AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs +++ b/AssetManager.Infrastructure/StrategyEngine/StrategyEngine.cs @@ -40,7 +40,7 @@ public class StrategyEngine : IStrategyEngine }; } - if (!_calculators.TryGetValue(strategy.Type, out var calculator)) + if (strategy.Type == null || !_calculators.TryGetValue(strategy.Type, out var calculator)) { _logger.LogWarning("未找到策略类型 {StrategyType} 的计算器", strategy.Type); return new StrategySignal diff --git a/AssetManager.Models/ApiResponse.cs b/AssetManager.Models/ApiResponse.cs index 23736d6..c322912 100644 --- a/AssetManager.Models/ApiResponse.cs +++ b/AssetManager.Models/ApiResponse.cs @@ -3,8 +3,8 @@ namespace AssetManager.Models; public class ApiResponse { public int code { get; set; } - public T data { get; set; } - public string message { get; set; } + public T? data { get; set; } + public string? message { get; set; } } public static class StatusCodes diff --git a/AssetManager.Models/DTOs/AuthDTO.cs b/AssetManager.Models/DTOs/AuthDTO.cs index 65b1808..0770bc2 100644 --- a/AssetManager.Models/DTOs/AuthDTO.cs +++ b/AssetManager.Models/DTOs/AuthDTO.cs @@ -2,35 +2,35 @@ namespace AssetManager.Models.DTOs; public class LoginRequest { - public string Email { get; set; } - public string Password { get; set; } + public string? Email { get; set; } + public string? Password { get; set; } } public class LoginResponse { - public string Token { get; set; } - public string ExpireAt { get; set; } - public UserBasicInfo User { get; set; } - public string UserId { get; set; } + public string? Token { get; set; } + public string? ExpireAt { get; set; } + public UserBasicInfo? User { get; set; } + public string? UserId { get; set; } } public class UserBasicInfo { - public string UserName { get; set; } - public string Email { get; set; } + public string? UserName { get; set; } + public string? Email { 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 string UserId { get; set; } + public string? Token { get; set; } + public string? ExpireAt { get; set; } + public UserBasicInfo? User { get; set; } + public string? OpenId { get; set; } + public string? UserId { get; set; } } public class WechatLoginRequest { - public string Code { get; set; } + public string? Code { get; set; } public string? NickName { get; set; } } diff --git a/AssetManager.Models/DTOs/MarketDTO.cs b/AssetManager.Models/DTOs/MarketDTO.cs index 0aed729..b718608 100644 --- a/AssetManager.Models/DTOs/MarketDTO.cs +++ b/AssetManager.Models/DTOs/MarketDTO.cs @@ -8,7 +8,7 @@ public class MarketPriceResponse /// /// 标的代码 /// - public string Symbol { get; set; } + public string? Symbol { get; set; } /// /// 价格 @@ -28,7 +28,7 @@ public class MarketPriceResponse /// /// 资产类型 /// - public string AssetType { get; set; } + public string? AssetType { get; set; } } /// @@ -39,7 +39,7 @@ public class MarketDataResponse /// /// 标的代码 /// - public string Symbol { get; set; } + public string? Symbol { get; set; } /// /// 时间戳 @@ -74,5 +74,5 @@ public class MarketDataResponse /// /// 资产类型 /// - public string AssetType { get; set; } + public string? AssetType { get; set; } } \ No newline at end of file diff --git a/AssetManager.Models/DTOs/PortfolioDTO.cs b/AssetManager.Models/DTOs/PortfolioDTO.cs index 690ca6e..bcb7639 100644 --- a/AssetManager.Models/DTOs/PortfolioDTO.cs +++ b/AssetManager.Models/DTOs/PortfolioDTO.cs @@ -2,66 +2,66 @@ namespace AssetManager.Models.DTOs; public class CreatePortfolioRequest { - public string name { get; set; } - public string strategyId { get; set; } - public string currency { get; set; } - public List stocks { get; set; } + public string? name { get; set; } + public string? strategyId { get; set; } + public string? currency { get; set; } + public List? stocks { get; set; } } public class StockItem { - public string name { get; set; } - public string code { get; set; } + public string? name { get; set; } + public string? code { get; set; } public double price { get; set; } public int amount { get; set; } - public string date { get; set; } - public string currency { get; set; } + public string? date { get; set; } + public string? currency { get; set; } public string assetType { get; set; } = "Stock"; // Stock / Crypto } public class CreatePortfolioResponse { - public string id { get; set; } + public string? id { get; set; } public double totalValue { get; set; } public double returnRate { get; set; } - public string currency { get; set; } - public string createdAt { get; set; } + public string? currency { get; set; } + public string? createdAt { get; set; } } public class PortfolioDetailResponse { - public string id { get; set; } - public string name { get; set; } - public string currency { get; set; } - public string status { get; set; } - public StrategyInfo strategy { get; set; } + public string? id { get; set; } + public string? name { get; set; } + public string? currency { get; set; } + public string? status { get; set; } + public StrategyInfo? strategy { get; set; } public double portfolioValue { get; set; } public double totalReturn { get; set; } public double todayProfit { get; set; } public double historicalChange { get; set; } public double dailyVolatility { get; set; } - public string todayProfitCurrency { get; set; } - public string logicModel { get; set; } - public string logicModelStatus { get; set; } - public string logicModelDescription { get; set; } + public string? todayProfitCurrency { get; set; } + public string? logicModel { get; set; } + public string? logicModelStatus { get; set; } + public string? logicModelDescription { get; set; } public int totalItems { get; set; } public double totalRatio { get; set; } - public List positions { get; set; } + public List? positions { get; set; } } public class StrategyInfo { - public string id { get; set; } - public string name { get; set; } - public string description { get; set; } + public string? id { get; set; } + public string? name { get; set; } + public string? description { get; set; } } public class PositionItem { - public string id { get; set; } - public string stockCode { get; set; } - public string stockName { get; set; } - public string symbol { get; set; } + public string? id { get; set; } + public string? stockCode { get; set; } + public string? stockName { get; set; } + public string? symbol { get; set; } public int amount { get; set; } public double averagePrice { get; set; } public double currentPrice { get; set; } @@ -71,33 +71,33 @@ public class PositionItem public double changeAmount { get; set; } public double ratio { get; set; } public double deviationRatio { get; set; } - public string currency { get; set; } + public string? currency { get; set; } } public class TransactionItem { - public string id { get; set; } - public string portfolioId { get; set; } - public string date { get; set; } - public string time { get; set; } - public string type { get; set; } - public string title { get; set; } + public string? id { get; set; } + public string? portfolioId { get; set; } + public string? date { get; set; } + public string? time { get; set; } + public string? type { get; set; } + public string? title { get; set; } public double amount { get; set; } - public string currency { get; set; } - public string status { get; set; } - public string remark { get; set; } + public string? currency { get; set; } + public string? status { get; set; } + public string? remark { get; set; } } public class GetTransactionsRequest { - public string portfolioId { get; set; } + public string? portfolioId { get; set; } public int limit { get; set; } public int offset { get; set; } } public class GetTransactionsResponse { - public List items { get; set; } + public List? items { get; set; } public int total { get; set; } public int page { get; set; } public int pageSize { get; set; } @@ -105,51 +105,51 @@ public class GetTransactionsResponse public class CreateTransactionRequest { - public string portfolioId { get; set; } - public string type { get; set; } - public string stockCode { get; set; } + public string? portfolioId { get; set; } + public string? type { get; set; } + public string? stockCode { get; set; } public int amount { get; set; } public double price { get; set; } - public string currency { get; set; } - public string remark { get; set; } + public string? currency { get; set; } + public string? remark { get; set; } public string assetType { get; set; } = "Stock"; // Stock / Crypto - public string transactionTime { get; set; } // 实际交易时间,可选 + public string? transactionTime { get; set; } // 实际交易时间,可选 } public class CreateTransactionResponse { - public string id { get; set; } + public string? id { get; set; } public double totalAmount { get; set; } - public string status { get; set; } - public string createdAt { get; set; } + 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 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 string? currency { get; set; } public double returnRate { get; set; } - public string returnType { get; set; } + public string? returnType { get; set; } } public class GetPortfoliosResponse { - public List items { get; set; } + public List? items { get; set; } } public class TotalAssetsResponse { public double totalValue { get; set; } - public string currency { get; set; } + public string? currency { get; set; } public double todayProfit { get; set; } - public string todayProfitCurrency { get; set; } + public string? todayProfitCurrency { get; set; } public double totalReturnRate { get; set; } } diff --git a/AssetManager.Models/DTOs/RiskParityConfig.cs b/AssetManager.Models/DTOs/RiskParityConfig.cs index 3bc5277..574aec1 100644 --- a/AssetManager.Models/DTOs/RiskParityConfig.cs +++ b/AssetManager.Models/DTOs/RiskParityConfig.cs @@ -18,7 +18,7 @@ public class RiskParityConfig /// /// 目标资产列表(可选,不指定则使用当前持仓) /// - public List Assets { get; set; } + public List? Assets { get; set; } } /// @@ -29,7 +29,7 @@ public class AssetAllocation /// /// 标的代码 /// - public string Symbol { get; set; } + public string? Symbol { get; set; } /// /// 目标权重 diff --git a/AssetManager.Models/DTOs/StrategyDTO.cs b/AssetManager.Models/DTOs/StrategyDTO.cs index dc7948d..eec8a93 100644 --- a/AssetManager.Models/DTOs/StrategyDTO.cs +++ b/AssetManager.Models/DTOs/StrategyDTO.cs @@ -2,110 +2,110 @@ namespace AssetManager.Models.DTOs; public class StrategyListResponse { - public List items { get; set; } + public List? items { get; set; } } public class StrategyItem { - public string id { get; set; } - public string iconChar { get; set; } - public string title { get; set; } - public string tag { get; set; } - public string desc { get; set; } - public string bgClass { get; set; } - public string tagClass { get; set; } - public string btnText { get; set; } - public string btnClass { get; set; } - public string[] Tags { get; set; } - public string Id { get; set; } - public string IconChar { get; set; } - public string Title { get; set; } - public string Tag { get; set; } - public string Desc { get; set; } - public string BgClass { get; set; } - public string TagClass { get; set; } - public string BtnText { get; set; } - public string BtnClass { get; set; } + public string? id { get; set; } + public string? iconChar { get; set; } + public string? title { get; set; } + public string? tag { get; set; } + public string? desc { get; set; } + public string? bgClass { get; set; } + public string? tagClass { get; set; } + public string? btnText { get; set; } + public string? btnClass { get; set; } + public string[]? Tags { get; set; } + public string? Id { get; set; } + public string? IconChar { get; set; } + public string? Title { get; set; } + public string? Tag { get; set; } + public string? Desc { get; set; } + public string? BgClass { get; set; } + public string? TagClass { get; set; } + public string? BtnText { get; set; } + public string? BtnClass { get; set; } } public class StrategyDetail { - public string Id { get; set; } - public string IconChar { get; set; } - public string Title { get; set; } - public string Tag { get; set; } - public string Desc { get; set; } - public string BgClass { get; set; } - public string TagClass { get; set; } - public string[] Tags { get; set; } - public string BtnText { get; set; } - public string BtnClass { get; set; } - public object Parameters { get; set; } - public object Backtest { get; set; } + public string? Id { get; set; } + public string? IconChar { get; set; } + public string? Title { get; set; } + public string? Tag { get; set; } + public string? Desc { get; set; } + public string? BgClass { get; set; } + public string? TagClass { get; set; } + public string[]? Tags { get; set; } + public string? BtnText { get; set; } + public string? BtnClass { get; set; } + public object? Parameters { get; set; } + public object? Backtest { get; set; } } public class StrategyDetailResponse { - public string id { get; set; } - public string iconChar { get; set; } - public string title { get; set; } - public string riskLevel { get; set; } - public string description { get; set; } - public List tags { get; set; } - public List parameters { get; set; } + public string? id { get; set; } + public string? iconChar { get; set; } + public string? title { get; set; } + public string? riskLevel { get; set; } + public string? description { get; set; } + public List? tags { get; set; } + public List? parameters { get; set; } } public class ParameterItem { - public string name { get; set; } - public string displayName { get; set; } - public string type { get; set; } - public string value { get; set; } + public string? name { get; set; } + public string? displayName { get; set; } + public string? type { get; set; } + public string? value { get; set; } } public class CreateStrategyRequest { - public string name { get; set; } - public string type { get; set; } - public string description { get; set; } - public string riskLevel { get; set; } - public List tags { get; set; } - public object parameters { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public string? description { get; set; } + public string? riskLevel { get; set; } + public List? tags { get; set; } + public object? parameters { get; set; } } public class StrategyResponse { - public string Id { get; set; } - public string Title { get; set; } - public string Status { get; set; } + public string? Id { get; set; } + public string? Title { get; set; } + public string? Status { get; set; } } public class UpdateStrategyRequest { - public string name { get; set; } - public string type { get; set; } - public string description { get; set; } - public string riskLevel { get; set; } - public List tags { get; set; } - public object parameters { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public string? description { get; set; } + public string? riskLevel { get; set; } + public List? tags { get; set; } + public object? parameters { get; set; } } public class DeleteStrategyResponse { - public string Id { get; set; } - public string Status { get; set; } + public string? Id { get; set; } + public string? Status { get; set; } } public class StrategyListItemDTO { - public string id { get; set; } - public string userId { get; set; } - public string name { get; set; } - public string type { get; set; } - public string description { get; set; } - public List tags { get; set; } - public string riskLevel { get; set; } - public string config { get; set; } + public string? id { get; set; } + public string? userId { get; set; } + public string? name { get; set; } + public string? type { get; set; } + public string? description { get; set; } + public List? tags { get; set; } + public string? riskLevel { get; set; } + public string? config { get; set; } public DateTime createdAt { get; set; } public DateTime updatedAt { get; set; } } diff --git a/AssetManager.Models/DTOs/StrategySignal.cs b/AssetManager.Models/DTOs/StrategySignal.cs index 034697a..2fa8686 100644 --- a/AssetManager.Models/DTOs/StrategySignal.cs +++ b/AssetManager.Models/DTOs/StrategySignal.cs @@ -8,17 +8,17 @@ public class StrategySignal /// /// 策略类型: ma_trend / chandelier_exit / risk_parity /// - public string StrategyType { get; set; } + public string? StrategyType { get; set; } /// /// 信号: BUY / SELL / HOLD / REBALANCE /// - public string Signal { get; set; } + public string? Signal { get; set; } /// /// 标的代码 /// - public string Symbol { get; set; } + public string? Symbol { get; set; } /// /// 信号强度 (0-1) @@ -28,7 +28,7 @@ public class StrategySignal /// /// 信号原因 /// - public string Reason { get; set; } + public string? Reason { get; set; } /// /// 建议价格 @@ -59,12 +59,12 @@ public class PositionSignal /// /// 标的代码 /// - public string Symbol { get; set; } + public string? Symbol { get; set; } /// /// 该标的的信号 /// - public string Signal { get; set; } + public string? Signal { get; set; } /// /// 建议数量 @@ -74,7 +74,7 @@ public class PositionSignal /// /// 信号原因 /// - public string Reason { get; set; } + public string? Reason { get; set; } /// /// 目标权重(风险平价策略用) diff --git a/AssetManager.Models/DTOs/UserDTO.cs b/AssetManager.Models/DTOs/UserDTO.cs index dbbbf7c..dc17124 100644 --- a/AssetManager.Models/DTOs/UserDTO.cs +++ b/AssetManager.Models/DTOs/UserDTO.cs @@ -2,20 +2,20 @@ namespace AssetManager.Models.DTOs; public class UserInfoResponse { - public string UserName { get; set; } - public string MemberLevel { get; set; } + public string? UserName { get; set; } + public string? MemberLevel { get; set; } public int RunningDays { get; set; } - public string Avatar { get; set; } - public string Email { get; set; } - public string DefaultCurrency { get; set; } + public string? Avatar { get; set; } + public string? Email { get; set; } + public string? DefaultCurrency { get; set; } } public class UpdateUserRequest { - public string UserName { get; set; } - public string Avatar { get; set; } - public string Email { get; set; } - public string DefaultCurrency { get; set; } + public string? UserName { get; set; } + public string? Avatar { get; set; } + public string? Email { get; set; } + public string? DefaultCurrency { get; set; } } public class UserStatsResponse @@ -28,6 +28,6 @@ public class UserStatsResponse public class UpdateUserResponse { - public string Status { get; set; } - public string UserName { get; set; } + public string? Status { get; set; } + public string? UserName { get; set; } } diff --git a/AssetManager.Services/AssetManager.Services.csproj b/AssetManager.Services/AssetManager.Services.csproj index 67252d0..29f0c9d 100644 --- a/AssetManager.Services/AssetManager.Services.csproj +++ b/AssetManager.Services/AssetManager.Services.csproj @@ -2,6 +2,7 @@ + diff --git a/AssetManager.Services/PortfolioService.cs b/AssetManager.Services/PortfolioService.cs index 0a18601..a6a3836 100644 --- a/AssetManager.Services/PortfolioService.cs +++ b/AssetManager.Services/PortfolioService.cs @@ -27,7 +27,7 @@ public class PortfolioService : IPortfolioService StrategyId = request.strategyId, Name = request.name, Currency = request.currency, - TotalValue = (decimal)request.stocks.Sum(s => s.price * s.amount), + TotalValue = (decimal)(request.stocks?.Sum(s => s.price * s.amount) ?? 0), ReturnRate = 0, Status = "运行中", CreatedAt = DateTime.Now, @@ -37,8 +37,13 @@ public class PortfolioService : IPortfolioService _db.Insertable(portfolio).ExecuteCommand(); // 创建初始持仓 - foreach (var stock in request.stocks) + foreach (var stock in request.stocks ?? new List()) { + if (stock.code == null || stock.name == null) + { + continue; + } + // 解析实际买入时间,如果解析失败则用当前时间 DateTime buyTime = DateTime.Now; if (!string.IsNullOrEmpty(stock.date)) @@ -110,7 +115,7 @@ public class PortfolioService : IPortfolioService tags = $"{p.Status} · {p.Currency}", status = p.Status, statusType = p.Status == "运行中" ? "green" : "gray", - iconChar = p.Name.Substring(0, 1).ToUpper(), + iconChar = p.Name?.Substring(0, 1).ToUpper() ?? "P", iconBgClass = "bg-blue-100", iconTextClass = "text-blue-700", value = (double)p.TotalValue, @@ -132,7 +137,7 @@ public class PortfolioService : IPortfolioService throw new Exception("User not found"); } - string targetCurrency = user.DefaultCurrency; + string targetCurrency = user.DefaultCurrency ?? "CNY"; decimal totalValueInTargetCurrency = 0; decimal totalCostInTargetCurrency = 0; decimal totalTodayProfitInTargetCurrency = 0; @@ -151,9 +156,14 @@ public class PortfolioService : IPortfolioService foreach (var pos in positions) { + if (pos.StockCode == null || pos.Currency == null) + { + continue; + } + // 获取实时价格 MarketPriceResponse priceResponse; - if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) + if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true) { priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode); } @@ -220,9 +230,14 @@ public class PortfolioService : IPortfolioService foreach (var pos in positions) { + if (pos.StockCode == null || pos.Currency == null) + { + continue; + } + // 获取实时价格 MarketPriceResponse priceResponse; - if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) + if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true) { priceResponse = await _marketDataService.GetCryptoPriceAsync(pos.StockCode); } @@ -383,7 +398,7 @@ public class PortfolioService : IPortfolioService TotalAmount = (decimal)(request.price * request.amount), Currency = request.currency, Status = "completed", - Remark = request.remark, + Remark = request.remark ?? string.Empty, TransactionTime = transactionTime, CreatedAt = DateTime.Now }; @@ -445,9 +460,14 @@ public class PortfolioService : IPortfolioService decimal totalPortfolioValue = 0; foreach (var pos in positions) { + if (pos.StockCode == null) + { + continue; + } + // 获取实时价格 MarketPriceResponse priceResponse; - if (pos.AssetType.Equals("Crypto", StringComparison.OrdinalIgnoreCase)) + if (pos.AssetType?.Equals("Crypto", StringComparison.OrdinalIgnoreCase) == true) { priceResponse = _marketDataService.GetCryptoPriceAsync(pos.StockCode).GetAwaiter().GetResult(); } diff --git a/AssetManager.Services/Services/WechatService.cs b/AssetManager.Services/Services/WechatService.cs index fd9e723..adb4698 100644 --- a/AssetManager.Services/Services/WechatService.cs +++ b/AssetManager.Services/Services/WechatService.cs @@ -23,15 +23,15 @@ public class WechatService var response = await _httpClient.GetAsync(url); response.EnsureSuccessStatusCode(); var content = await response.Content.ReadAsStringAsync(); - return JsonSerializer.Deserialize(content); + return JsonSerializer.Deserialize(content) ?? new WechatAuthResult(); } } public class WechatAuthResult { - public string OpenId { get; set; } - public string SessionKey { get; set; } - public string UnionId { get; set; } + public string? OpenId { get; set; } + public string? SessionKey { get; set; } + public string? UnionId { get; set; } public int Errcode { get; set; } - public string Errmsg { get; set; } + public string? Errmsg { get; set; } } \ No newline at end of file