341 lines
10 KiB
C#
Executable File
341 lines
10 KiB
C#
Executable File
using AssetManager.Models.DTOs;
|
||
using AssetManager.Models;
|
||
using AssetManager.Services;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
using Microsoft.Extensions.Logging;
|
||
using System.Security.Claims;
|
||
|
||
namespace AssetManager.API.Controllers;
|
||
|
||
[ApiController]
|
||
[Route("api/v1/portfolio")]
|
||
[Authorize]
|
||
public class PortfolioController : ControllerBase
|
||
{
|
||
private readonly ILogger<PortfolioController> _logger;
|
||
private readonly IPortfolioFacade _portfolioFacade;
|
||
|
||
public PortfolioController(
|
||
ILogger<PortfolioController> logger,
|
||
IPortfolioFacade portfolioFacade)
|
||
{
|
||
_logger = logger;
|
||
_portfolioFacade = portfolioFacade;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取当前登录用户ID
|
||
/// </summary>
|
||
private string GetCurrentUserId()
|
||
{
|
||
return User.FindFirst(ClaimTypes.NameIdentifier)?.Value ?? throw new Exception("User not authenticated");
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建投资组合(账本)
|
||
/// </summary>
|
||
[HttpPost]
|
||
public async Task<ActionResult<ApiResponse<CreatePortfolioResponse>>> CreatePortfolio([FromBody] CreatePortfolioRequest request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
try
|
||
{
|
||
var response = await _portfolioFacade.CreatePortfolioWithValidationAsync(request, userId);
|
||
return Ok(new ApiResponse<CreatePortfolioResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = response,
|
||
message = "创建成功"
|
||
});
|
||
}
|
||
catch (ArgumentException ex)
|
||
{
|
||
return BadRequest(new ApiResponse<CreatePortfolioResponse>
|
||
{
|
||
code = Models.StatusCodes.BadRequest,
|
||
data = null,
|
||
message = ex.Message
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取投资组合列表
|
||
/// </summary>
|
||
[HttpGet]
|
||
public async Task<ActionResult<ApiResponse<GetPortfoliosResponse>>> GetPortfolios()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var portfolios = await _portfolioFacade.GetPortfolioListAsync(userId);
|
||
|
||
return Ok(new ApiResponse<GetPortfoliosResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = new GetPortfoliosResponse
|
||
{
|
||
Items = portfolios
|
||
},
|
||
message = "success"
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户总资产
|
||
/// </summary>
|
||
[HttpGet("assets")]
|
||
public async Task<ActionResult<ApiResponse<TotalAssetsResponse>>> GetTotalAssets()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var totalAssets = await _portfolioFacade.GetTotalAssetsAsync(userId);
|
||
|
||
return Ok(new ApiResponse<TotalAssetsResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = totalAssets,
|
||
message = "success"
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取交易记录(通过 query parameter)
|
||
/// </summary>
|
||
[HttpGet("transactions")]
|
||
public async Task<ActionResult<ApiResponse<GetTransactionsResponse>>> GetTransactionsByQuery(
|
||
[FromQuery] string portfolioId,
|
||
[FromQuery] int limit = 20,
|
||
[FromQuery] int offset = 0)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
try
|
||
{
|
||
var request = new GetTransactionsRequest
|
||
{
|
||
PortfolioId = portfolioId,
|
||
Limit = limit,
|
||
Offset = offset
|
||
};
|
||
|
||
var transactions = await _portfolioFacade.GetTransactionsAsync(portfolioId, request, userId);
|
||
|
||
return Ok(new ApiResponse<GetTransactionsResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = new GetTransactionsResponse
|
||
{
|
||
Items = transactions,
|
||
Total = transactions.Count
|
||
},
|
||
message = "success"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<GetTransactionsResponse>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取投资组合详情
|
||
/// </summary>
|
||
[HttpGet("{id}")]
|
||
public async Task<ActionResult<ApiResponse<PortfolioDetailResponse>>> GetPortfolio(string id)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
try
|
||
{
|
||
var portfolio = await _portfolioFacade.GetPortfolioDetailAsync(id, userId);
|
||
return Ok(new ApiResponse<PortfolioDetailResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = portfolio,
|
||
message = "success"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<PortfolioDetailResponse>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 删除投资组合
|
||
/// </summary>
|
||
[HttpDelete("{id}")]
|
||
public async Task<ActionResult<ApiResponse<object>>> DeletePortfolio(string id)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
var success = await _portfolioFacade.DeletePortfolioAsync(id, userId);
|
||
|
||
if (!success)
|
||
{
|
||
return NotFound(new ApiResponse<object>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
|
||
return Ok(new ApiResponse<object>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = null,
|
||
message = "删除成功"
|
||
});
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取净值历史(收益曲线)
|
||
/// </summary>
|
||
[HttpGet("{id}/nav-history")]
|
||
public async Task<ActionResult<ApiResponse<NavHistoryResponse>>> GetNavHistory(
|
||
string id,
|
||
[FromQuery] DateTime? startDate,
|
||
[FromQuery] DateTime? endDate,
|
||
[FromQuery] string? interval)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
var request = new NavHistoryRequest
|
||
{
|
||
StartDate = startDate,
|
||
EndDate = endDate,
|
||
Interval = interval ?? "daily"
|
||
};
|
||
|
||
try
|
||
{
|
||
var response = await _portfolioFacade.GetNavHistoryAsync(id, request, userId);
|
||
return Ok(new ApiResponse<NavHistoryResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = response,
|
||
message = "success"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<NavHistoryResponse>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 手动回填净值历史
|
||
/// </summary>
|
||
[HttpPost("{id}/nav-history/backfill")]
|
||
public async Task<ActionResult<ApiResponse<BackfillNavResponse>>> BackfillNavHistory(
|
||
string id,
|
||
[FromQuery] bool force = false)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
try
|
||
{
|
||
var response = await _portfolioFacade.BackfillNavHistoryAsync(id, userId, force);
|
||
return Ok(new ApiResponse<BackfillNavResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = response,
|
||
message = "success"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<BackfillNavResponse>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 创建交易(买入/卖出)
|
||
/// </summary>
|
||
[HttpPost("transactions")]
|
||
public async Task<ActionResult<ApiResponse<TransactionItem>>> CreateTransaction(
|
||
[FromBody] CreateTransactionRequest request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
if (string.IsNullOrEmpty(request.PortfolioId))
|
||
{
|
||
return BadRequest(new ApiResponse<TransactionItem>
|
||
{
|
||
code = Models.StatusCodes.BadRequest,
|
||
data = null,
|
||
message = "portfolioId 不能为空"
|
||
});
|
||
}
|
||
|
||
try
|
||
{
|
||
var transaction = await _portfolioFacade.CreateTransactionAsync(request.PortfolioId, request, userId);
|
||
return Ok(new ApiResponse<TransactionItem>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = transaction,
|
||
message = "交易创建成功"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<TransactionItem>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取策略信号
|
||
/// </summary>
|
||
[HttpGet("{id}/signal")]
|
||
public async Task<ActionResult<ApiResponse<StrategySignalResponse>>> GetStrategySignal(string id)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
|
||
try
|
||
{
|
||
var signal = await _portfolioFacade.GetStrategySignalAsync(id, userId);
|
||
return Ok(new ApiResponse<StrategySignalResponse>
|
||
{
|
||
code = Models.StatusCodes.Success,
|
||
data = signal,
|
||
message = "success"
|
||
});
|
||
}
|
||
catch (UnauthorizedAccessException)
|
||
{
|
||
return NotFound(new ApiResponse<StrategySignalResponse>
|
||
{
|
||
code = Models.StatusCodes.NotFound,
|
||
data = null,
|
||
message = "组合不存在"
|
||
});
|
||
}
|
||
}
|
||
}
|