575 lines
18 KiB
C#
575 lines
18 KiB
C#
using System.Security.Claims;
|
||
using HoneyBox.Core.Interfaces;
|
||
using HoneyBox.Model.Base;
|
||
using HoneyBox.Model.Models.Asset;
|
||
using HoneyBox.Model.Models.Auth;
|
||
using HoneyBox.Model.Models.Vip;
|
||
using Microsoft.AspNetCore.Authorization;
|
||
using Microsoft.AspNetCore.Mvc;
|
||
|
||
namespace HoneyBox.Api.Controllers;
|
||
|
||
/// <summary>
|
||
/// 用户控制器 - 处理用户信息、资产和VIP相关功能
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// 提供用户信息查询和更新、资产明细查询、VIP信息查询等功能
|
||
/// </remarks>
|
||
[ApiController]
|
||
[Route("api")]
|
||
public class UserController : ControllerBase
|
||
{
|
||
private readonly IUserService _userService;
|
||
private readonly IAuthService _authService;
|
||
private readonly IAssetService _assetService;
|
||
private readonly IVipService _vipService;
|
||
private readonly IQuanYiService _quanYiService;
|
||
private readonly ILogger<UserController> _logger;
|
||
|
||
public UserController(
|
||
IUserService userService,
|
||
IAuthService authService,
|
||
IAssetService assetService,
|
||
IVipService vipService,
|
||
IQuanYiService quanYiService,
|
||
ILogger<UserController> logger)
|
||
{
|
||
_userService = userService;
|
||
_authService = authService;
|
||
_assetService = assetService;
|
||
_vipService = vipService;
|
||
_quanYiService = quanYiService;
|
||
_logger = logger;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户简要信息(GET方式)
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// GET /api/userInfo
|
||
///
|
||
/// 获取当前登录用户的简要信息,直接返回用户数据(不嵌套在userinfo对象中)
|
||
/// 用于前端 getUserInfo() 调用
|
||
/// </remarks>
|
||
/// <returns>用户信息数据</returns>
|
||
[HttpGet("userInfo")]
|
||
[Authorize]
|
||
[ProducesResponseType(typeof(ApiResponse<UserInfoDto>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<UserInfoDto>), StatusCodes.Status401Unauthorized)]
|
||
public async Task<ApiResponse<UserInfoDto>> GetUserInfoSimple()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<UserInfoDto>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var userInfo = await _userService.GetUserInfoAsync(userId.Value);
|
||
if (userInfo == null)
|
||
{
|
||
_logger.LogWarning("User not found: UserId={UserId}", userId);
|
||
return ApiResponse<UserInfoDto>.Fail("用户不存在");
|
||
}
|
||
|
||
return ApiResponse<UserInfoDto>.Success(userInfo);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get user info: UserId={UserId}", userId);
|
||
return ApiResponse<UserInfoDto>.Fail("获取用户信息失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取用户完整信息(POST方式)
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// POST /api/user
|
||
///
|
||
/// 获取当前登录用户的详细信息,包含余额、积分、VIP等级等
|
||
/// 返回数据嵌套在 userinfo 对象中,用于前端 getUser() 调用
|
||
/// Requirements: 4.1-4.5
|
||
/// </remarks>
|
||
/// <returns>用户信息数据</returns>
|
||
[HttpPost("user")]
|
||
[Authorize]
|
||
[ProducesResponseType(typeof(ApiResponse<UserInfoResponse>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<UserInfoResponse>), StatusCodes.Status401Unauthorized)]
|
||
public async Task<ApiResponse<UserInfoResponse>> GetUserInfo()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<UserInfoResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var userInfo = await _userService.GetUserInfoAsync(userId.Value);
|
||
if (userInfo == null)
|
||
{
|
||
_logger.LogWarning("User not found: UserId={UserId}", userId);
|
||
return ApiResponse<UserInfoResponse>.Fail("用户不存在");
|
||
}
|
||
|
||
var response = new UserInfoResponse
|
||
{
|
||
Userinfo = userInfo,
|
||
Other = new OtherConfigDto(),
|
||
TaskList = new List<TaskDto>()
|
||
};
|
||
|
||
return ApiResponse<UserInfoResponse>.Success(response);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get user info: UserId={UserId}", userId);
|
||
return ApiResponse<UserInfoResponse>.Fail("获取用户信息失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 更新用户信息
|
||
/// POST /api/update_userinfo
|
||
/// Requirements: 4.2, 4.3
|
||
/// </summary>
|
||
[HttpPost("update_userinfo")]
|
||
[Authorize]
|
||
public async Task<ApiResponse> UpdateUserInfo([FromBody] UpdateUserInfoRequest request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse.Unauthorized();
|
||
}
|
||
|
||
if (request == null)
|
||
{
|
||
return ApiResponse.Fail("请求参数不能为空");
|
||
}
|
||
|
||
try
|
||
{
|
||
var updateDto = new UpdateUserDto();
|
||
var hasUpdate = false;
|
||
|
||
// 处理昵称更新
|
||
if (!string.IsNullOrWhiteSpace(request.Nickname))
|
||
{
|
||
updateDto.Nickname = request.Nickname;
|
||
hasUpdate = true;
|
||
}
|
||
|
||
// 处理头像更新
|
||
if (!string.IsNullOrWhiteSpace(request.Imagebase))
|
||
{
|
||
// Base64图片上传到腾讯云COS
|
||
var headimgUrl = await UploadBase64ImageAsync(request.Imagebase, userId.Value);
|
||
if (!string.IsNullOrWhiteSpace(headimgUrl))
|
||
{
|
||
updateDto.Headimg = headimgUrl;
|
||
hasUpdate = true;
|
||
}
|
||
}
|
||
else if (!string.IsNullOrWhiteSpace(request.Headimg))
|
||
{
|
||
// 直接使用传入的头像URL
|
||
updateDto.Headimg = request.Headimg;
|
||
hasUpdate = true;
|
||
}
|
||
|
||
if (!hasUpdate)
|
||
{
|
||
return ApiResponse.Fail("没有需要更新的内容");
|
||
}
|
||
|
||
await _userService.UpdateUserAsync(userId.Value, updateDto);
|
||
_logger.LogInformation("User info updated: UserId={UserId}", userId);
|
||
return ApiResponse.Success("更新成功");
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogWarning("Update user info failed: UserId={UserId}, Error={Error}", userId, ex.Message);
|
||
return ApiResponse.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to update user info: UserId={UserId}", userId);
|
||
return ApiResponse.Fail("更新用户信息失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 账号注销
|
||
/// POST /api/user_log_off
|
||
/// Requirements: 7.1-7.3
|
||
/// </summary>
|
||
[HttpPost("user_log_off")]
|
||
[Authorize]
|
||
public async Task<ApiResponse> LogOff([FromBody] LogOffRequest? request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var type = request?.Type ?? 0;
|
||
await _authService.LogOffAsync(userId.Value, type);
|
||
|
||
var message = type == 0 ? "注销成功" : "取消注销成功";
|
||
_logger.LogInformation("User log off: UserId={UserId}, Type={Type}", userId, type);
|
||
return ApiResponse.Success(message);
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogWarning("Log off failed: UserId={UserId}, Error={Error}", userId, ex.Message);
|
||
return ApiResponse.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to log off: UserId={UserId}", userId);
|
||
return ApiResponse.Fail("操作失败");
|
||
}
|
||
}
|
||
|
||
#region Asset Endpoints
|
||
|
||
/// <summary>
|
||
/// 获取余额明细
|
||
/// POST /api/profitMoney
|
||
/// Requirements: 1.1
|
||
/// </summary>
|
||
[HttpPost("profitMoney")]
|
||
[Authorize]
|
||
public async Task<ApiResponse<AssetRecordPageResponse>> GetMoneyRecords([FromBody] AssetRecordRequest? request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<AssetRecordPageResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var type = request?.Type ?? 0;
|
||
var page = request?.Page ?? 1;
|
||
var limit = request?.Limit ?? 15;
|
||
|
||
if (page < 1) page = 1;
|
||
if (limit < 1) limit = 15;
|
||
|
||
var result = await _assetService.GetMoneyRecordsAsync(userId.Value, type, page, limit);
|
||
return ApiResponse<AssetRecordPageResponse>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get money records: UserId={UserId}", userId);
|
||
return ApiResponse<AssetRecordPageResponse>.Fail("获取余额明细失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取吧唧币明细
|
||
/// POST /api/profitIntegral
|
||
/// Requirements: 1.2
|
||
/// </summary>
|
||
[HttpPost("profitIntegral")]
|
||
[Authorize]
|
||
public async Task<ApiResponse<AssetRecordPageResponse>> GetIntegralRecords([FromBody] AssetRecordRequest? request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<AssetRecordPageResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var type = request?.Type ?? 0;
|
||
var page = request?.Page ?? 1;
|
||
var limit = request?.Limit ?? 15;
|
||
|
||
if (page < 1) page = 1;
|
||
if (limit < 1) limit = 15;
|
||
|
||
var result = await _assetService.GetIntegralRecordsAsync(userId.Value, type, page, limit);
|
||
return ApiResponse<AssetRecordPageResponse>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get integral records: UserId={UserId}", userId);
|
||
return ApiResponse<AssetRecordPageResponse>.Fail("获取吧唧币明细失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取积分明细
|
||
/// POST /api/profitScore
|
||
/// Requirements: 1.3
|
||
/// </summary>
|
||
[HttpPost("profitScore")]
|
||
[Authorize]
|
||
public async Task<ApiResponse<AssetRecordPageResponse>> GetScoreRecords([FromBody] AssetRecordRequest? request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<AssetRecordPageResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var type = request?.Type ?? 0;
|
||
var page = request?.Page ?? 1;
|
||
var limit = request?.Limit ?? 15;
|
||
|
||
if (page < 1) page = 1;
|
||
if (limit < 1) limit = 15;
|
||
|
||
var result = await _assetService.GetScoreRecordsAsync(userId.Value, type, page, limit);
|
||
return ApiResponse<AssetRecordPageResponse>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get score records: UserId={UserId}", userId);
|
||
return ApiResponse<AssetRecordPageResponse>.Fail("获取积分明细失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 获取支付记录
|
||
/// POST /api/profitPay
|
||
/// Requirements: 1.4
|
||
/// </summary>
|
||
[HttpPost("profitPay")]
|
||
[Authorize]
|
||
public async Task<ApiResponse<AssetRecordPageResponse>> GetPayRecords([FromBody] AssetRecordRequest? request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<AssetRecordPageResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var page = request?.Page ?? 1;
|
||
var limit = request?.Limit ?? 15;
|
||
|
||
if (page < 1) page = 1;
|
||
if (limit < 1) limit = 15;
|
||
|
||
var result = await _assetService.GetPayRecordsAsync(userId.Value, page, limit);
|
||
return ApiResponse<AssetRecordPageResponse>.Success(result);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get pay records: UserId={UserId}", userId);
|
||
return ApiResponse<AssetRecordPageResponse>.Fail("获取支付记录失败");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region VIP Endpoints
|
||
|
||
/// <summary>
|
||
/// 获取VIP信息
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// POST /api/vip_list
|
||
///
|
||
/// 获取当前用户的VIP等级信息和权益列表
|
||
/// Requirements: 2.1-2.5
|
||
/// </remarks>
|
||
/// <returns>VIP信息数据</returns>
|
||
[HttpPost("vip_list")]
|
||
[Authorize]
|
||
[ProducesResponseType(typeof(ApiResponse<VipInfoResponse>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<VipInfoResponse>), StatusCodes.Status401Unauthorized)]
|
||
public async Task<ApiResponse<VipInfoResponse>> GetVipInfo()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<VipInfoResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var result = await _vipService.GetVipInfoAsync(userId.Value);
|
||
return ApiResponse<VipInfoResponse>.Success(result);
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogWarning("Get VIP info failed: UserId={UserId}, Error={Error}", userId, ex.Message);
|
||
return ApiResponse<VipInfoResponse>.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get VIP info: UserId={UserId}", userId);
|
||
return ApiResponse<VipInfoResponse>.Fail("获取VIP信息失败");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region QuanYi (VIP Rights) Endpoints
|
||
|
||
/// <summary>
|
||
/// 获取权益中心信息
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// Get /api/quan_yi
|
||
///
|
||
/// 获取当前用户的权益中心信息,包含等级列表和可领取奖品
|
||
/// Requirements: 8.1
|
||
/// </remarks>
|
||
/// <returns>权益中心数据</returns>
|
||
[HttpGet("quan_yi")]
|
||
[Authorize]
|
||
[ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<QuanYiResponse>), StatusCodes.Status401Unauthorized)]
|
||
public async Task<ApiResponse<QuanYiResponse>> GetQuanYi()
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<QuanYiResponse>.Unauthorized();
|
||
}
|
||
|
||
try
|
||
{
|
||
var result = await _quanYiService.GetQuanYiAsync(userId.Value);
|
||
return ApiResponse<QuanYiResponse>.Success(result);
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogWarning("Get QuanYi failed: UserId={UserId}, Error={Error}", userId, ex.Message);
|
||
return ApiResponse<QuanYiResponse>.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to get QuanYi: UserId={UserId}", userId);
|
||
return ApiResponse<QuanYiResponse>.Fail("获取权益信息失败");
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 领取权益奖品
|
||
/// </summary>
|
||
/// <remarks>
|
||
/// POST /api/quan_yi_ling
|
||
///
|
||
/// 领取指定等级的权益奖品
|
||
/// Requirements: 8.2
|
||
/// </remarks>
|
||
/// <param name="request">领取请求</param>
|
||
/// <returns>领取结果</returns>
|
||
[HttpPost("quan_yi_ling")]
|
||
[Authorize]
|
||
[ProducesResponseType(typeof(ApiResponse<QuanYiLingResponse>), StatusCodes.Status200OK)]
|
||
[ProducesResponseType(typeof(ApiResponse<QuanYiLingResponse>), StatusCodes.Status401Unauthorized)]
|
||
public async Task<ApiResponse<QuanYiLingResponse>> ClaimQuanYi([FromBody] QuanYiLingRequest request)
|
||
{
|
||
var userId = GetCurrentUserId();
|
||
if (userId == null)
|
||
{
|
||
return ApiResponse<QuanYiLingResponse>.Unauthorized();
|
||
}
|
||
|
||
if (request == null || request.Id <= 0)
|
||
{
|
||
return ApiResponse<QuanYiLingResponse>.Fail("请选择要领取的等级");
|
||
}
|
||
|
||
try
|
||
{
|
||
var result = await _quanYiService.ClaimQuanYiAsync(userId.Value, request.Id);
|
||
return ApiResponse<QuanYiLingResponse>.Success(result, "领取成功");
|
||
}
|
||
catch (InvalidOperationException ex)
|
||
{
|
||
_logger.LogWarning("Claim QuanYi failed: UserId={UserId}, LevelId={LevelId}, Error={Error}",
|
||
userId, request.Id, ex.Message);
|
||
return ApiResponse<QuanYiLingResponse>.Fail(ex.Message);
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to claim QuanYi: UserId={UserId}, LevelId={LevelId}",
|
||
userId, request.Id);
|
||
return ApiResponse<QuanYiLingResponse>.Fail("领取失败");
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
|
||
#region Private Helper Methods
|
||
|
||
/// <summary>
|
||
/// 获取当前登录用户ID
|
||
/// </summary>
|
||
private int? GetCurrentUserId()
|
||
{
|
||
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
|
||
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
|
||
{
|
||
return null;
|
||
}
|
||
return userId;
|
||
}
|
||
|
||
/// <summary>
|
||
/// 上传Base64图片到腾讯云COS
|
||
/// </summary>
|
||
/// <param name="base64Image">Base64编码的图片数据</param>
|
||
/// <param name="userId">用户ID</param>
|
||
/// <returns>上传后的图片URL</returns>
|
||
private async Task<string?> UploadBase64ImageAsync(string base64Image, int userId)
|
||
{
|
||
try
|
||
{
|
||
// 移除Base64前缀(如果有)
|
||
var base64Data = base64Image;
|
||
if (base64Image.Contains(","))
|
||
{
|
||
base64Data = base64Image.Split(',')[1];
|
||
}
|
||
|
||
// 解码Base64数据
|
||
var imageBytes = Convert.FromBase64String(base64Data);
|
||
|
||
// TODO: 实现腾讯云COS上传
|
||
// 目前返回一个占位URL,实际应该上传到COS并返回真实URL
|
||
// 可以在后续添加ICosUploadService接口和实现
|
||
|
||
// 临时方案:将图片保存到本地并返回相对路径
|
||
var fileName = $"avatar_{userId}_{DateTime.UtcNow:yyyyMMddHHmmss}.png";
|
||
var uploadPath = Path.Combine("wwwroot", "uploads", "avatars");
|
||
|
||
if (!Directory.Exists(uploadPath))
|
||
{
|
||
Directory.CreateDirectory(uploadPath);
|
||
}
|
||
|
||
var filePath = Path.Combine(uploadPath, fileName);
|
||
await System.IO.File.WriteAllBytesAsync(filePath, imageBytes);
|
||
|
||
// 返回相对URL
|
||
return $"/uploads/avatars/{fileName}";
|
||
}
|
||
catch (Exception ex)
|
||
{
|
||
_logger.LogError(ex, "Failed to upload base64 image for user: {UserId}", userId);
|
||
return null;
|
||
}
|
||
}
|
||
|
||
#endregion
|
||
}
|