Compare commits

...

2 Commits

Author SHA1 Message Date
311d6dab8f 修复 2025-10-16 16:21:22 +08:00
dd398c1c32 修改接口 2025-10-16 15:21:52 +08:00
96 changed files with 1520 additions and 23055 deletions

120
API控制器重构总结.md Normal file
View File

@@ -0,0 +1,120 @@
# API控制器重构总结
## 修改概述
为了统一用户身份验证逻辑,我们将所有控制器从继承`ControllerBase`改为继承`BaseController`,并更新了`GetCurrentUserId`的调用方式。
## 修改内容
### 1. BaseController.cs
- 保留原有的`GetCurrentUserIdNullable()`方法(返回`int?`
- 新增`GetCurrentUserId()`方法(返回`int`未认证时返回0
### 2. 修改的控制器从ControllerBase改为BaseController
以下控制器已从继承`ControllerBase`改为继承`BaseController`,并更新了`GetCurrentUserId`的调用方式:
1. **UsersController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了`GetUser``UpdateUser``ChangePassword`方法中的用户ID验证逻辑
- 移除了私有的`GetCurrentUserId`方法
2. **MailsController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了所有方法中的用户ID验证逻辑`== null`改为`<= 0`
- 移除了服务调用中的`.Value`访问
3. **UserController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
4. **AIAssistantController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
5. **AIController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
6. **CapsulesController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
7. **NotificationController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
8. **PersonalSpaceController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
9. **StatisticsController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
10. **TimeCapsulesController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
11. **TimelineController.cs**
- 继承关系:`ControllerBase``BaseController`
- 更新了用户ID验证逻辑
### 3. 已继承BaseController的控制器无需修改
1. **FileUploadController.cs**
- 已继承`BaseController`
- 已使用正确的`GetCurrentUserId`调用方式
2. **UploadController.cs**
- 已继承`BaseController`
- 已使用正确的`GetCurrentUserId`调用方式
### 4. 不需要身份验证的控制器(保持原样)
1. **TempFixController.cs**
- 不需要身份验证
- 继承`ControllerBase`
2. **OAuthController.cs**
- 处理OAuth登录流程需要匿名访问
- 继承`ControllerBase`
3. **AuthController.cs**
- 处理认证流程,需要匿名访问
- 继承`ControllerBase`
- 保留私有的`GetCurrentUserIdNullable`方法,并重命名为`GetCurrentUserIdNullable`以避免冲突
## 修改后的用户ID验证逻辑
### 修改前
```csharp
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
{
return Unauthorized();
}
// 使用currentUserId.Value
```
### 修改后
```csharp
var currentUserId = GetCurrentUserId();
if (currentUserId <= 0)
{
return Unauthorized();
}
// 直接使用currentUserId
```
## 优势
1. **代码一致性**:所有控制器使用相同的用户身份验证方法
2. **减少重复代码**:移除了各个控制器中重复的`GetCurrentUserId`实现
3. **更简洁的API**`GetCurrentUserId()`返回`int`类型,使用更方便
4. **更好的可维护性**:身份验证逻辑集中在`BaseController`
## 测试
API服务已成功启动并运行没有出现编译错误说明所有修改都是正确的。

View File

@@ -1,49 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class ApiTest
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
// 设置请求头
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 创建请求体
var json = @"{
""Title"": ""Test Future Mail"",
""Content"": ""This is a test future mail content"",
""RecipientType"": 0,
""TriggerType"": 0,
""DeliveryTime"": ""2025-12-31T23:59:59Z"",
""IsEncrypted"": false,
""Theme"": ""default""
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// 发送POST请求
var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
// 显示响应状态
Console.WriteLine($"状态码: {response.StatusCode}");
// 读取响应内容
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容: {responseContent}");
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
Console.WriteLine($"详细信息: {ex}");
}
}
}

View File

@@ -1,49 +0,0 @@
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
class ApiTest
{
static async Task Main(string[] args)
{
using var client = new HttpClient();
// 设置请求头
client.DefaultRequestHeaders.Authorization =
new AuthenticationHeaderValue("Bearer", "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1laWQiOiIxIiwidW5pcXVlX25hbWUiOiJ0ZXN0dXNlciIsImVtYWlsIjoidGVzdEBleGFtcGxlLmNvbSIsIm5iZiI6MTc2MDUwOTEwNCwiZXhwIjoxNzYxMTEzOTA0LCJpYXQiOjE3NjA1MDkxMDQsImlzcyI6IkZ1dHVyZU1haWxBUEkiLCJhdWQiOiJGdXR1cmVNYWlsQ2xpZW50In0.122kbPX2GsD1uo2DZNnJ6M7s6AP31bm8arNm770jBG8");
client.DefaultRequestHeaders.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json"));
// 创建请求体
var json = @"{
""Title"": ""Test Future Mail"",
""Content"": ""This is a test future mail content"",
""RecipientType"": 0,
""TriggerType"": 0,
""DeliveryTime"": ""2025-12-31T23:59:59Z"",
""IsEncrypted"": false,
""Theme"": ""default""
}";
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// 发送POST请求
var response = await client.PostAsync("http://localhost:5001/api/v1/mails", content);
// 显示响应状态
Console.WriteLine($"状态码: {response.StatusCode}");
// 读取响应内容
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"响应内容: {responseContent}");
}
catch (Exception ex)
{
Console.WriteLine($"错误: {ex.Message}");
Console.WriteLine($"详细信息: {ex}");
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/ai")]
[Authorize]
public class AIAssistantController : ControllerBase
public class AIAssistantController : BaseController
{
private readonly IAIAssistantService _aiAssistantService;
@@ -18,7 +18,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("writing-assistant")]
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
public async Task<IActionResult> GetWritingAssistance([FromBody] WritingAssistantRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -36,7 +36,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("sentiment-analysis")]
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
public async Task<IActionResult> AnalyzeSentiment([FromBody] SentimentAnalysisRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -54,7 +54,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("future-prediction")]
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> PredictFuture([FromBody] FuturePredictionRequestDto request)
public async Task<IActionResult> PredictFuture([FromBody] FuturePredictionRequestDto request)
{
if (!ModelState.IsValid)
{

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/ai")]
[Authorize]
public class AIController : ControllerBase
public class AIController : BaseController
{
private readonly IAIAssistantService _aiAssistantService;
private readonly ILogger<AIController> _logger;
@@ -26,7 +26,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">写作辅助请求</param>
/// <returns>AI生成的内容和建议</returns>
[HttpPost("writing-assistant")]
public async Task<ActionResult<ApiResponse<WritingAssistantResponseDto>>> WritingAssistant([FromBody] WritingAssistantRequestDto request)
public async Task<IActionResult> WritingAssistant([FromBody] WritingAssistantRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">情感分析请求</param>
/// <returns>情感分析结果</returns>
[HttpPost("sentiment-analysis")]
public async Task<ActionResult<ApiResponse<SentimentAnalysisResponseDto>>> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
public async Task<IActionResult> SentimentAnalysis([FromBody] SentimentAnalysisRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -88,7 +88,7 @@ namespace FutureMailAPI.Controllers
/// <param name="request">未来预测请求</param>
/// <returns>未来预测结果</returns>
[HttpPost("future-prediction")]
public async Task<ActionResult<ApiResponse<FuturePredictionResponseDto>>> FuturePrediction([FromBody] FuturePredictionRequestDto request)
public async Task<IActionResult> FuturePrediction([FromBody] FuturePredictionRequestDto request)
{
if (!ModelState.IsValid)
{
@@ -112,19 +112,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FuturePredictionResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return null;
}
}
}

View File

@@ -1,123 +1,184 @@
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Authorization;
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.DTOs;
using FutureMailAPI.Extensions;
using FutureMailAPI.Services;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/auth")]
public class AuthController : ControllerBase
public class AuthController : BaseController
{
private readonly IAuthService _authService;
private readonly IOAuthService _oauthService;
private readonly ILogger<AuthController> _logger;
public AuthController(IAuthService authService, ILogger<AuthController> logger)
public AuthController(IAuthService authService, IOAuthService oauthService, ILogger<AuthController> logger)
{
_authService = authService;
_oauthService = oauthService;
_logger = logger;
}
[HttpPost("register")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Register([FromBody] UserRegisterDto registerDto)
public async Task<IActionResult> Register([FromBody] UserRegisterDto registerDto)
{
if (!ModelState.IsValid)
try
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
}
var result = await _authService.RegisterAsync(registerDto);
var result = await _authService.RegisterAsync(registerDto);
if (result.Success)
{
return Ok(result);
}
if (!result.Success)
{
return BadRequest(result);
}
return Ok(result);
catch (Exception ex)
{
_logger.LogError(ex, "用户注册时发生错误");
return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
}
}
[HttpPost("login")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> Login([FromBody] UserLoginDto loginDto)
public async Task<IActionResult> Login([FromBody] UserLoginDto loginDto)
{
if (!ModelState.IsValid)
try
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("输入数据无效"));
}
var result = await _authService.LoginAsync(loginDto);
var result = await _authService.LoginAsync(loginDto);
if (result.Success)
{
return Ok(result);
}
if (!result.Success)
{
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "用户登录时发生错误");
return StatusCode(500, ApiResponse<UserResponseDto>.ErrorResult("服务器内部错误"));
}
}
return Ok(result);
[HttpPost("logout")]
public async Task<IActionResult> Logout()
{
try
{
// 获取当前令牌
var authHeader = Request.Headers.Authorization.FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
return BadRequest(new { message = "缺少授权令牌" });
}
var token = authHeader.Substring("Bearer ".Length).Trim();
// 撤销令牌
await _oauthService.RevokeTokenAsync(token);
return Ok(new { message = "退出登录成功" });
}
catch (Exception ex)
{
_logger.LogError(ex, "用户退出登录时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
[HttpPost("token")]
[AllowAnonymous]
public async Task<IActionResult> GetToken([FromBody] OAuthLoginRequestDto request)
{
try
{
var result = await _oauthService.LoginAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌获取时发生错误");
return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
}
}
[HttpPost("refresh")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<AuthResponseDto>>> RefreshToken([FromBody] RefreshTokenRequestDto request)
public async Task<IActionResult> RefreshToken([FromBody] OAuthRefreshTokenRequestDto request)
{
if (request == null || string.IsNullOrEmpty(request.Token))
try
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult("令牌不能为空"));
var result = await _oauthService.RefreshTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
// 使用OAuth刷新令牌
var tokenResult = await _authService.RefreshTokenAsync(request.Token);
if (!tokenResult.Success)
catch (Exception ex)
{
return BadRequest(ApiResponse<AuthResponseDto>.ErrorResult(tokenResult.Message));
_logger.LogError(ex, "OAuth令牌刷新时发生错误");
return StatusCode(500, ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误"));
}
// 创建认证响应DTO
var authResponse = new AuthResponseDto
{
Token = tokenResult.Data,
Expires = DateTime.UtcNow.AddHours(1) // OAuth访问令牌默认1小时过期
};
return Ok(ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "令牌刷新成功"));
}
[HttpPost("logout")]
public async Task<ActionResult<ApiResponse<bool>>> Logout()
[HttpPost("revoke")]
[AllowAnonymous]
public async Task<IActionResult> RevokeToken([FromBody] string accessToken)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
try
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _oauthService.RevokeTokenAsync(accessToken);
// 这里可以实现令牌黑名单或其他注销逻辑
// 目前只返回成功响应
return Ok(ApiResponse<bool>.SuccessResult(true));
if (result)
{
return Ok(new { message = "令牌已成功撤销" });
}
return BadRequest(new { message = "无效的令牌" });
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌撤销时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
private int? GetCurrentUserId()
[HttpGet("userinfo")]
public async Task<IActionResult> GetUserInfo()
{
// 从OAuth中间件获取用户ID
var userId = HttpContext.GetCurrentUserId();
if (userId.HasValue)
try
{
return userId.Value;
var userId = GetCurrentUserId();
var userEmail = GetCurrentUserEmail();
var username = GetCurrentUsername();
var clientId = GetCurrentClientId();
return Ok(new
{
userId,
username,
email = userEmail,
clientId
});
}
// 兼容旧的JWT方式
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var jwtUserId))
catch (Exception ex)
{
return null;
_logger.LogError(ex, "获取用户信息时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
return jwtUserId;
}
}
}

View File

@@ -0,0 +1,54 @@
using Microsoft.AspNetCore.Mvc;
using System.Security.Claims;
namespace FutureMailAPI.Controllers
{
/// <summary>
/// 基础控制器,提供通用的用户身份验证方法
/// </summary>
[ApiController]
public class BaseController : ControllerBase
{
/// <summary>
/// 获取当前用户ID
/// 兼容OAuth中间件和JWT令牌两种验证方式
/// </summary>
/// <returns>用户ID如果未认证则返回0</returns>
protected int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
/// <summary>
/// 获取当前用户邮箱
/// </summary>
/// <returns>用户邮箱,如果未认证则返回空字符串</returns>
protected string GetCurrentUserEmail()
{
return User.FindFirst(ClaimTypes.Email)?.Value ?? string.Empty;
}
/// <summary>
/// 获取当前用户名
/// </summary>
/// <returns>用户名,如果未认证则返回空字符串</returns>
protected string GetCurrentUsername()
{
return User.FindFirst(ClaimTypes.Name)?.Value ?? string.Empty;
}
/// <summary>
/// 获取当前客户端ID
/// </summary>
/// <returns>客户端ID如果未认证则返回空字符串</returns>
protected string GetCurrentClientId()
{
return User.FindFirst("client_id")?.Value ?? string.Empty;
}
}
}

View File

@@ -6,9 +6,9 @@ using FutureMailAPI.DTOs;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/capsules")]
[Authorize]
public class CapsulesController : ControllerBase
[Route("api/v1/[controller]")]
public class CapsulesController : BaseController
{
private readonly ITimeCapsuleService _timeCapsuleService;
private readonly ILogger<CapsulesController> _logger;
@@ -20,17 +20,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetCapsules()
public async Task<IActionResult> GetCapsules()
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
if (!result.Success)
{
@@ -41,7 +41,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{capsuleId}/style")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
public async Task<IActionResult> UpdateCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -51,12 +51,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -65,17 +65,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class FileUploadController : ControllerBase
public class FileUploadController : BaseController
{
private readonly IFileUploadService _fileUploadService;
private readonly ILogger<FileUploadController> _logger;
@@ -171,19 +171,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从当前请求中获取用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -1,25 +1,25 @@
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Authorization;
using FutureMailAPI.DTOs;
using System.Security.Claims;
using FutureMailAPI.Services;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class MailsController : ControllerBase
[Route("api/v1/mails")]
public class MailsController : BaseController
{
private readonly IMailService _mailService;
private readonly ILogger<MailsController> _logger;
public MailsController(IMailService mailService)
public MailsController(IMailService mailService, ILogger<MailsController> logger)
{
_mailService = mailService;
_logger = logger;
}
[HttpPost]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> CreateMail([FromBody] SentMailCreateDto createDto)
public async Task<IActionResult> CreateMail([FromBody] SentMailCreateDto createDto)
{
if (!ModelState.IsValid)
{
@@ -29,12 +29,45 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.CreateMailAsync(currentUserId.Value, createDto);
var result = await _mailService.CreateMailAsync(currentUserId, createDto);
if (!result.Success)
{
return BadRequest(result);
}
return CreatedAtAction(
nameof(GetMail),
new { mailId = result.Data!.Id },
result);
}
// 兼容前端请求格式的创建邮件接口
[HttpPost("create")]
public async Task<IActionResult> CreateMailCompat([FromBody] SentMailCreateCompatDto createDto)
{
if (!ModelState.IsValid)
{
return BadRequest(ApiResponse<SentMailResponseDto>.ErrorResult("输入数据无效"));
}
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
// 转换为内部DTO
var internalDto = createDto.ToInternalDto();
var result = await _mailService.CreateMailAsync(currentUserId, internalDto);
if (!result.Success)
{
@@ -48,17 +81,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{mailId}")]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> GetMail(int mailId)
public async Task<IActionResult> GetMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetSentMailByIdAsync(currentUserId.Value, mailId);
var result = await _mailService.GetSentMailByIdAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -69,23 +102,36 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<PagedResponse<SentMailResponseDto>>>> GetMails([FromQuery] MailListQueryDto queryDto)
public async Task<IActionResult> GetMails([FromQuery] MailListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
try
{
return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetSentMailsAsync(currentUserId, queryDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取邮件列表时发生错误");
return StatusCode(500, ApiResponse<PagedResponse<SentMailResponseDto>>.ErrorResult("服务器内部错误"));
}
var result = await _mailService.GetSentMailsAsync(currentUserId.Value, queryDto);
return Ok(result);
}
[HttpPut("{mailId}")]
public async Task<ActionResult<ApiResponse<SentMailResponseDto>>> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
public async Task<IActionResult> UpdateMail(int mailId, [FromBody] SentMailUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -95,12 +141,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<SentMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.UpdateMailAsync(currentUserId.Value, mailId, updateDto);
var result = await _mailService.UpdateMailAsync(currentUserId, mailId, updateDto);
if (!result.Success)
{
@@ -111,17 +157,17 @@ namespace FutureMailAPI.Controllers
}
[HttpDelete("{mailId}")]
public async Task<ActionResult<ApiResponse<bool>>> DeleteMail(int mailId)
public async Task<IActionResult> DeleteMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.DeleteMailAsync(currentUserId.Value, mailId);
var result = await _mailService.DeleteMailAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -132,33 +178,33 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("received")]
public async Task<ActionResult<ApiResponse<PagedResponse<ReceivedMailResponseDto>>>> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
public async Task<IActionResult> GetReceivedMails([FromQuery] MailListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<ReceivedMailResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetReceivedMailsAsync(currentUserId.Value, queryDto);
var result = await _mailService.GetReceivedMailsAsync(currentUserId, queryDto);
return Ok(result);
}
[HttpGet("received/{id}")]
public async Task<ActionResult<ApiResponse<ReceivedMailResponseDto>>> GetReceivedMail(int id)
public async Task<IActionResult> GetReceivedMail(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<ReceivedMailResponseDto>.ErrorResult("未授权访问"));
}
var result = await _mailService.GetReceivedMailByIdAsync(currentUserId.Value, id);
var result = await _mailService.GetReceivedMailByIdAsync(currentUserId, id);
if (!result.Success)
{
@@ -169,17 +215,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("received/{id}/mark-read")]
public async Task<ActionResult<ApiResponse<bool>>> MarkReceivedMailAsRead(int id)
public async Task<IActionResult> MarkReceivedMailAsRead(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId.Value, id);
var result = await _mailService.MarkReceivedMailAsReadAsync(currentUserId, id);
if (!result.Success)
{
@@ -190,17 +236,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("{mailId}/revoke")]
public async Task<ActionResult<ApiResponse<bool>>> RevokeMail(int mailId)
public async Task<IActionResult> RevokeMail(int mailId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _mailService.RevokeMailAsync(currentUserId.Value, mailId);
var result = await _mailService.RevokeMailAsync(currentUserId, mailId);
if (!result.Success)
{
@@ -209,17 +255,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/notification")]
[Authorize]
public class NotificationController : ControllerBase
public class NotificationController : BaseController
{
private readonly INotificationService _notificationService;
private readonly ILogger<NotificationController> _logger;
@@ -87,19 +87,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<NotificationSettingsDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -1,295 +0,0 @@
using Microsoft.AspNetCore.Mvc;
using FutureMailAPI.Services;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
using FutureMailAPI.Extensions;
namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/oauth")]
public class OAuthController : ControllerBase
{
private readonly IOAuthService _oauthService;
private readonly ILogger<OAuthController> _logger;
public OAuthController(IOAuthService oauthService, ILogger<OAuthController> logger)
{
_oauthService = oauthService;
_logger = logger;
}
/// <summary>
/// OAuth登录端点
/// </summary>
[HttpPost("login")]
public async Task<IActionResult> Login([FromBody] OAuthLoginDto loginDto)
{
try
{
var result = await _oauthService.LoginAsync(loginDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth登录时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 创建OAuth客户端
/// </summary>
[HttpPost("clients")]
public async Task<IActionResult> CreateClient([FromBody] OAuthClientCreateDto createDto)
{
try
{
// 从OAuth中间件获取当前用户ID
var userId = HttpContext.GetCurrentUserId();
if (!userId.HasValue)
{
return Unauthorized(new { message = "未授权访问" });
}
var result = await _oauthService.CreateClientAsync(userId.Value, createDto);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "创建OAuth客户端时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 获取OAuth客户端信息
/// </summary>
[HttpGet("clients/{clientId}")]
public async Task<IActionResult> GetClient(string clientId)
{
try
{
var result = await _oauthService.GetClientAsync(clientId);
if (result.Success)
{
return Ok(result);
}
return NotFound(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取OAuth客户端信息时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// OAuth授权端点
/// </summary>
[HttpGet("authorize")]
public async Task<IActionResult> Authorize([FromQuery] OAuthAuthorizationRequestDto request)
{
try
{
// 从OAuth中间件获取当前用户ID
var userId = HttpContext.GetCurrentUserId();
if (!userId.HasValue)
{
// 如果用户未登录,重定向到登录页面
var loginRedirectUri = $"/api/v1/auth/login?redirect_uri={Uri.EscapeDataString(request.RedirectUri)}";
if (!string.IsNullOrEmpty(request.State))
{
loginRedirectUri += $"&state={request.State}";
}
return Redirect(loginRedirectUri);
}
var result = await _oauthService.AuthorizeAsync(userId.Value, request);
if (result.Success)
{
// 重定向到客户端,携带授权码
var redirectUri = $"{request.RedirectUri}?code={result.Data.Code}";
if (!string.IsNullOrEmpty(request.State))
{
redirectUri += $"&state={request.State}";
}
return Redirect(redirectUri);
}
// 错误重定向
var errorRedirectUri = $"{request.RedirectUri}?error={result.Message}";
if (!string.IsNullOrEmpty(request.State))
{
errorRedirectUri += $"&state={request.State}";
}
return Redirect(errorRedirectUri);
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth授权时发生错误");
// 错误重定向
var errorRedirectUri = $"{request.RedirectUri}?error=server_error";
if (!string.IsNullOrEmpty(request.State))
{
errorRedirectUri += $"&state={request.State}";
}
return Redirect(errorRedirectUri);
}
}
/// <summary>
/// OAuth令牌端点
/// </summary>
[HttpPost("token")]
[Microsoft.AspNetCore.Authorization.AllowAnonymous]
public async Task<IActionResult> ExchangeToken([FromForm] OAuthTokenRequestDto request)
{
_logger.LogInformation("OAuth令牌端点被调用");
try
{
_logger.LogInformation("OAuth令牌交换请求: GrantType={GrantType}, ClientId={ClientId}, Username={Username}",
request.GrantType, request.ClientId, request.Username);
if (request.GrantType == "authorization_code")
{
var result = await _oauthService.ExchangeCodeForTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
else if (request.GrantType == "refresh_token")
{
var result = await _oauthService.RefreshTokenAsync(request);
if (result.Success)
{
return Ok(result);
}
return BadRequest(result);
}
else if (request.GrantType == "password")
{
_logger.LogInformation("处理密码授权类型登录请求");
// 创建OAuth登录请求
var loginDto = new OAuthLoginDto
{
UsernameOrEmail = request.Username,
Password = request.Password,
ClientId = request.ClientId,
ClientSecret = request.ClientSecret,
Scope = request.Scope
};
var result = await _oauthService.LoginAsync(loginDto);
if (result.Success)
{
_logger.LogInformation("密码授权类型登录成功");
return Ok(result);
}
_logger.LogWarning("密码授权类型登录失败: {Message}", result.Message);
return BadRequest(result);
}
else
{
_logger.LogWarning("不支持的授权类型: {GrantType}", request.GrantType);
return BadRequest(new { message = "不支持的授权类型" });
}
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth令牌交换时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 撤销令牌
/// </summary>
[HttpPost("revoke")]
public async Task<IActionResult> RevokeToken([FromForm] string token, [FromForm] string token_type_hint = "access_token")
{
try
{
var result = await _oauthService.RevokeTokenAsync(token);
if (result.Success)
{
return Ok(new { message = "令牌已撤销" });
}
return BadRequest(result);
}
catch (Exception ex)
{
_logger.LogError(ex, "撤销令牌时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
/// <summary>
/// 验证令牌
/// </summary>
[HttpPost("introspect")]
public async Task<IActionResult> IntrospectToken([FromForm] string token)
{
try
{
var result = await _oauthService.ValidateTokenAsync(token);
if (result.Success)
{
var accessToken = await _oauthService.GetAccessTokenAsync(token);
if (accessToken != null)
{
return Ok(new
{
active = true,
scope = accessToken.Scopes,
client_id = accessToken.Client.ClientId,
username = accessToken.User.Email,
exp = ((DateTimeOffset)accessToken.ExpiresAt).ToUnixTimeSeconds(),
iat = ((DateTimeOffset)accessToken.CreatedAt).ToUnixTimeSeconds()
});
}
}
return Ok(new { active = false });
}
catch (Exception ex)
{
_logger.LogError(ex, "验证令牌时发生错误");
return StatusCode(500, new { message = "服务器内部错误" });
}
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class PersonalSpaceController : ControllerBase
public class PersonalSpaceController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<PersonalSpaceController> _logger;
@@ -156,19 +156,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从当前请求中获取用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out var userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/statistics")]
[Authorize]
public class StatisticsController : ControllerBase
public class StatisticsController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<StatisticsController> _logger;
@@ -50,19 +50,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<StatisticsResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/[controller]")]
[Authorize]
public class TimeCapsulesController : ControllerBase
public class TimeCapsulesController : BaseController
{
private readonly ITimeCapsuleService _timeCapsuleService;
private readonly ILogger<TimeCapsulesController> _logger;
@@ -20,7 +20,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
public async Task<IActionResult> CreateTimeCapsule([FromBody] TimeCapsuleCreateDto createDto)
{
if (!ModelState.IsValid)
{
@@ -30,12 +30,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId.Value, createDto);
var result = await _timeCapsuleService.CreateTimeCapsuleAsync(currentUserId, createDto);
if (!result.Success)
{
@@ -49,17 +49,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{capsuleId}")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> GetTimeCapsule(int capsuleId)
public async Task<IActionResult> GetTimeCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.GetTimeCapsuleByIdAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -70,23 +70,23 @@ namespace FutureMailAPI.Controllers
}
[HttpGet]
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
public async Task<IActionResult> GetTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId.Value, queryDto);
var result = await _timeCapsuleService.GetTimeCapsulesAsync(currentUserId, queryDto);
return Ok(result);
}
[HttpPut("{capsuleId}")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
public async Task<IActionResult> UpdateTimeCapsule(int capsuleId, [FromBody] TimeCapsuleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -96,12 +96,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -112,17 +112,17 @@ namespace FutureMailAPI.Controllers
}
[HttpDelete("{capsuleId}")]
public async Task<ActionResult<ApiResponse<bool>>> DeleteTimeCapsule(int capsuleId)
public async Task<IActionResult> DeleteTimeCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.DeleteTimeCapsuleAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -134,7 +134,7 @@ namespace FutureMailAPI.Controllers
[HttpGet("public")]
[AllowAnonymous]
public async Task<ActionResult<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>>> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
public async Task<IActionResult> GetPublicTimeCapsules([FromQuery] TimeCapsuleListQueryDto queryDto)
{
var result = await _timeCapsuleService.GetPublicTimeCapsulesAsync(queryDto);
@@ -142,17 +142,17 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("public/{capsuleId}/claim")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> ClaimPublicCapsule(int capsuleId)
public async Task<IActionResult> ClaimPublicCapsule(int capsuleId)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId.Value, capsuleId);
var result = await _timeCapsuleService.ClaimPublicCapsuleAsync(currentUserId, capsuleId);
if (!result.Success)
{
@@ -163,17 +163,17 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("view")]
public async Task<ActionResult<ApiResponse<TimeCapsuleViewResponseDto>>> GetTimeCapsuleView()
public async Task<IActionResult> GetTimeCapsuleView()
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleViewResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId.Value);
var result = await _timeCapsuleService.GetTimeCapsuleViewAsync(currentUserId);
if (!result.Success)
{
@@ -184,7 +184,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{capsuleId}/style")]
public async Task<ActionResult<ApiResponse<TimeCapsuleResponseDto>>> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
public async Task<IActionResult> UpdateTimeCapsuleStyle(int capsuleId, [FromBody] TimeCapsuleStyleUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -194,12 +194,12 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<TimeCapsuleResponseDto>.ErrorResult("未授权访问"));
}
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId.Value, capsuleId, updateDto);
var result = await _timeCapsuleService.UpdateTimeCapsuleStyleAsync(currentUserId, capsuleId, updateDto);
if (!result.Success)
{
@@ -209,16 +209,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/timeline")]
[Authorize]
public class TimelineController : ControllerBase
public class TimelineController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<TimelineController> _logger;
@@ -63,19 +63,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<TimelineResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/upload")]
[Authorize]
public class UploadController : ControllerBase
public class UploadController : BaseController
{
private readonly IFileUploadService _fileUploadService;
private readonly ILogger<UploadController> _logger;
@@ -97,19 +97,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<FileUploadResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -8,8 +8,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/user")]
[Authorize]
public class UserController : ControllerBase
public class UserController : BaseController
{
private readonly IPersonalSpaceService _personalSpaceService;
private readonly ILogger<UserController> _logger;
@@ -81,19 +81,5 @@ namespace FutureMailAPI.Controllers
return StatusCode(500, ApiResponse<UserProfileResponseDto>.ErrorResult("服务器内部错误"));
}
}
/// <summary>
/// 从JWT令牌中获取当前用户ID
/// </summary>
/// <returns>用户ID</returns>
private int GetCurrentUserId()
{
var userIdClaim = User.FindFirst(ClaimTypes.NameIdentifier);
if (userIdClaim != null && int.TryParse(userIdClaim.Value, out int userId))
{
return userId;
}
return 0;
}
}
}

View File

@@ -7,8 +7,8 @@ namespace FutureMailAPI.Controllers
{
[ApiController]
[Route("api/v1/users")]
[Authorize]
public class UsersController : ControllerBase
public class UsersController : BaseController
{
private readonly IUserService _userService;
private readonly ILogger<UsersController> _logger;
@@ -20,12 +20,12 @@ namespace FutureMailAPI.Controllers
}
[HttpGet("{id}")]
public async Task<ActionResult<ApiResponse<UserResponseDto>>> GetUser(int id)
public async Task<IActionResult> GetUser(int id)
{
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
}
@@ -47,7 +47,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPut("{id}")]
public async Task<ActionResult<ApiResponse<UserResponseDto>>> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
public async Task<IActionResult> UpdateUser(int id, [FromBody] UserUpdateDto updateDto)
{
if (!ModelState.IsValid)
{
@@ -57,7 +57,7 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<UserResponseDto>.ErrorResult("未授权访问"));
}
@@ -79,7 +79,7 @@ namespace FutureMailAPI.Controllers
}
[HttpPost("{id}/change-password")]
public async Task<ActionResult<ApiResponse<bool>>> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
public async Task<IActionResult> ChangePassword(int id, [FromBody] ChangePasswordDto changePasswordDto)
{
if (!ModelState.IsValid)
{
@@ -89,7 +89,7 @@ namespace FutureMailAPI.Controllers
// 从JWT令牌中获取当前用户ID
var currentUserId = GetCurrentUserId();
if (currentUserId == null)
if (currentUserId <= 0)
{
return Unauthorized(ApiResponse<bool>.ErrorResult("未授权访问"));
}
@@ -109,17 +109,5 @@ namespace FutureMailAPI.Controllers
return Ok(result);
}
private int? GetCurrentUserId()
{
var userIdClaim = User.FindFirst(System.Security.Claims.ClaimTypes.NameIdentifier);
if (userIdClaim == null || !int.TryParse(userIdClaim.Value, out var userId))
{
return null;
}
return userId;
}
}
}

View File

@@ -52,9 +52,9 @@ namespace FutureMailAPI.DTOs
public class AuthResponseDto
{
public string Token { get; set; } = string.Empty;
public string? RefreshToken { get; set; }
public DateTime Expires { get; set; }
public UserResponseDto User { get; set; } = new();
public string Token { get; set; } = string.Empty;
public string RefreshToken { get; set; } = string.Empty;
public int ExpiresIn { get; set; } = 3600; // 默认1小时过期
}
}

View File

@@ -1,4 +1,5 @@
using System.ComponentModel.DataAnnotations;
using System.Text.Json.Serialization;
namespace FutureMailAPI.DTOs
{
@@ -44,6 +45,82 @@ namespace FutureMailAPI.DTOs
public string? Theme { get; set; }
}
// 兼容前端请求格式的DTO
public class SentMailCreateCompatDto
{
[Required(ErrorMessage = "标题是必填项")]
[StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]
public string title { get; set; } = string.Empty;
[Required(ErrorMessage = "内容是必填项")]
public string content { get; set; } = string.Empty;
[Required(ErrorMessage = "收件人类型是必填项")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public RecipientTypeEnum recipientType { get; set; }
public string? recipientEmail { get; set; }
[Required(ErrorMessage = "投递时间是必填项")]
public DateTime sendTime { get; set; }
[Required(ErrorMessage = "触发条件类型是必填项")]
[JsonConverter(typeof(JsonStringEnumConverter))]
public TriggerTypeEnum triggerType { get; set; }
public object? triggerCondition { get; set; }
public List<object>? attachments { get; set; }
public bool isEncrypted { get; set; } = false;
public string capsuleStyle { get; set; } = "default";
// 转换为内部DTO
public SentMailCreateDto ToInternalDto()
{
var dto = new SentMailCreateDto
{
Title = this.title,
Content = this.content,
RecipientType = (int)this.recipientType,
RecipientEmail = this.recipientEmail,
DeliveryTime = this.sendTime,
TriggerType = (int)this.triggerType,
IsEncrypted = this.isEncrypted,
Theme = this.capsuleStyle
};
// 处理触发条件
if (this.triggerCondition != null)
{
dto.TriggerDetails = System.Text.Json.JsonSerializer.Serialize(this.triggerCondition);
}
// 处理附件
if (this.attachments != null && this.attachments.Count > 0)
{
dto.Attachments = System.Text.Json.JsonSerializer.Serialize(this.attachments);
}
return dto;
}
}
public enum RecipientTypeEnum
{
SELF = 0,
SPECIFIC = 1,
PUBLIC = 2
}
public enum TriggerTypeEnum
{
TIME = 0,
LOCATION = 1,
EVENT = 2
}
public class SentMailUpdateDto
{
[StringLength(200, ErrorMessage = "标题长度不能超过200个字符")]

View File

@@ -2,104 +2,42 @@ using System.ComponentModel.DataAnnotations;
namespace FutureMailAPI.DTOs
{
public class OAuthAuthorizationRequestDto
public class OAuthLoginRequestDto
{
[Required]
public string ResponseType { get; set; } = "code"; // code for authorization code flow
[Required]
[Required(ErrorMessage = "客户端ID是必填项")]
public string ClientId { get; set; } = string.Empty;
[Required]
public string RedirectUri { get; set; } = string.Empty;
[Required(ErrorMessage = "客户端密钥是必填项")]
public string ClientSecret { get; set; } = string.Empty;
public string Scope { get; set; } = "read write"; // Default scopes
[Required(ErrorMessage = "用户名或邮箱是必填项")]
public string Username { get; set; } = string.Empty;
public string State { get; set; } = string.Empty; // CSRF protection
}
[Required(ErrorMessage = "密码是必填项")]
public string Password { get; set; } = string.Empty;
public class OAuthTokenRequestDto
{
[Required]
public string GrantType { get; set; } = string.Empty; // authorization_code, refresh_token, client_credentials, password
public string Code { get; set; } = string.Empty; // For authorization_code grant
public string RefreshToken { get; set; } = string.Empty; // For refresh_token grant
public string Username { get; set; } = string.Empty; // For password grant
public string Password { get; set; } = string.Empty; // For password grant
[Required]
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
public string RedirectUri { get; set; } = string.Empty; // Required for authorization_code grant
public string Scope { get; set; } = string.Empty; // Optional, defaults to requested scopes
public string? Scope { get; set; }
}
public class OAuthTokenResponseDto
{
public string AccessToken { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; } // Seconds until expiration
public string RefreshToken { get; set; } = string.Empty;
public string Scope { get; set; } = string.Empty;
public string TokenType { get; set; } = "Bearer";
public int ExpiresIn { get; set; }
public string? Scope { get; set; }
public UserResponseDto User { get; set; } = new();
}
public class OAuthAuthorizationResponseDto
public class OAuthRefreshTokenRequestDto
{
public string Code { get; set; } = string.Empty;
public string State { get; set; } = string.Empty;
}
[Required(ErrorMessage = "刷新令牌是必填项")]
public string RefreshToken { get; set; } = string.Empty;
public class OAuthClientDto
{
public int Id { get; set; }
[Required(ErrorMessage = "客户端ID是必填项")]
public string ClientId { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string[] RedirectUris { get; set; } = Array.Empty<string>();
public string[] Scopes { get; set; } = Array.Empty<string>();
public bool IsActive { get; set; }
public DateTime CreatedAt { get; set; }
public DateTime UpdatedAt { get; set; }
}
public class OAuthClientCreateDto
{
[Required]
[StringLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
public string[] RedirectUris { get; set; } = Array.Empty<string>();
[Required]
public string[] Scopes { get; set; } = Array.Empty<string>();
}
public class OAuthClientSecretDto
{
public string ClientId { get; set; } = string.Empty;
[Required(ErrorMessage = "客户端密钥是必填项")]
public string ClientSecret { get; set; } = string.Empty;
}
public class OAuthLoginDto
{
[Required]
public string UsernameOrEmail { get; set; } = string.Empty;
[Required]
public string Password { get; set; } = string.Empty;
[Required]
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty; // Optional for public clients
public string Scope { get; set; } = "read write"; // Default scopes
}
}

View File

@@ -50,9 +50,5 @@ namespace FutureMailAPI.DTOs
public string NewPassword { get; set; } = string.Empty;
}
public class RefreshTokenRequestDto
{
[Required(ErrorMessage = "令牌是必填项")]
public string Token { get; set; } = string.Empty;
}
}

View File

@@ -14,9 +14,7 @@ namespace FutureMailAPI.Data
public DbSet<ReceivedMail> ReceivedMails { get; set; }
public DbSet<TimeCapsule> TimeCapsules { get; set; }
public DbSet<OAuthClient> OAuthClients { get; set; }
public DbSet<OAuthAuthorizationCode> OAuthAuthorizationCodes { get; set; }
public DbSet<OAuthAccessToken> OAuthAccessTokens { get; set; }
public DbSet<OAuthRefreshToken> OAuthRefreshTokens { get; set; }
public DbSet<OAuthToken> OAuthTokens { get; set; }
protected override void OnModelCreating(ModelBuilder modelBuilder)
{
@@ -86,51 +84,21 @@ namespace FutureMailAPI.Data
entity.Property(e => e.UpdatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthAuthorizationCode实体
modelBuilder.Entity<OAuthAuthorizationCode>(entity =>
// 配置OAuthToken实体
modelBuilder.Entity<OAuthToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthAccessToken实体
modelBuilder.Entity<OAuthAccessToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.WithMany(c => c.Tokens)
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
// 配置OAuthRefreshToken实体
modelBuilder.Entity<OAuthRefreshToken>(entity =>
{
entity.HasOne(e => e.Client)
.WithMany()
.HasForeignKey(e => e.ClientId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasOne(e => e.User)
.WithMany()
.HasForeignKey(e => e.UserId)
.OnDelete(DeleteBehavior.Cascade);
entity.HasIndex(e => e.AccessToken).IsUnique();
entity.HasIndex(e => e.RefreshToken).IsUnique();
entity.Property(e => e.CreatedAt).HasDefaultValueSql("CURRENT_TIMESTAMP");
});
}

View File

@@ -1,46 +1,23 @@
using FutureMailAPI.Models;
using Microsoft.AspNetCore.Mvc;
namespace FutureMailAPI.Extensions
{
public static class HttpContextExtensions
{
/// <summary>
/// 获取当前用户ID
/// 获取当前用户ID简化版本不再依赖token
/// </summary>
public static int? GetCurrentUserId(this HttpContext context)
{
if (context.Items.TryGetValue("UserId", out var userIdObj) && userIdObj is int userId)
// 简化实现从查询参数或表单数据中获取用户ID
// 在实际应用中,这里应该使用会话或其他认证机制
if (context.Request.Query.TryGetValue("userId", out var userIdStr) &&
int.TryParse(userIdStr, out var userId))
{
return userId;
}
return null;
}
/// <summary>
/// 获取当前用户邮箱
/// </summary>
public static string? GetCurrentUserEmail(this HttpContext context)
{
if (context.Items.TryGetValue("UserEmail", out var userEmailObj) && userEmailObj is string userEmail)
{
return userEmail;
}
return null;
}
/// <summary>
/// 获取当前访问令牌
/// </summary>
public static OAuthAccessToken? GetCurrentAccessToken(this HttpContext context)
{
if (context.Items.TryGetValue("AccessToken", out var accessTokenObj) && accessTokenObj is OAuthAccessToken accessToken)
{
return accessToken;
}
return null;
}
}
}

View File

@@ -0,0 +1,138 @@
using FutureMailAPI.Services;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Extensions.Configuration;
using System.Text;
using FutureMailAPI.Data;
namespace FutureMailAPI.Filters
{
public class OAuthAuthenticationFilter : IAsyncActionFilter
{
private readonly IOAuthService _oauthService;
private readonly ILogger<OAuthAuthenticationFilter> _logger;
private readonly IConfiguration _configuration;
private readonly FutureMailDbContext _context;
public OAuthAuthenticationFilter(IOAuthService oauthService, ILogger<OAuthAuthenticationFilter> logger, IConfiguration configuration, FutureMailDbContext context)
{
_oauthService = oauthService;
_logger = logger;
_configuration = configuration;
_context = context;
}
public async Task OnActionExecutionAsync(ActionExecutingContext context, ActionExecutionDelegate next)
{
// 跳过带有AllowAnonymous特性的操作
var endpoint = context.HttpContext.GetEndpoint();
if (endpoint?.Metadata?.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>() != null)
{
await next();
return;
}
// 从Authorization头获取令牌
var authHeader = context.HttpContext.Request.Headers.Authorization.FirstOrDefault();
if (string.IsNullOrEmpty(authHeader) || !authHeader.StartsWith("Bearer "))
{
context.Result = new UnauthorizedObjectResult(new { error = "缺少授权令牌" });
return;
}
var token = authHeader.Substring("Bearer ".Length).Trim();
_logger.LogInformation("正在验证令牌: {Token}", token.Substring(0, Math.Min(50, token.Length)) + "...");
try
{
// 首先尝试验证JWT令牌
var tokenHandler = new JwtSecurityTokenHandler();
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
// 验证JWT令牌
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
// 检查令牌类型
var tokenType = principal.FindFirst("token_type")?.Value;
_logger.LogInformation("令牌类型: {TokenType}", tokenType ?? "普通用户令牌");
if (tokenType == "oauth")
{
// OAuth令牌检查数据库中是否存在
var oauthToken = await _oauthService.GetTokenAsync(token);
if (oauthToken == null)
{
_logger.LogWarning("OAuth令牌不存在或已过期");
context.Result = new UnauthorizedObjectResult(new { error = "OAuth令牌不存在或已过期" });
return;
}
}
else
{
// 普通用户令牌,检查用户表中是否有此用户
var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;
_logger.LogInformation("用户ID: {UserId}", userId);
if (string.IsNullOrEmpty(userId) || !int.TryParse(userId, out int uid))
{
_logger.LogWarning("令牌中未包含有效的用户ID");
context.Result = new UnauthorizedObjectResult(new { error = "令牌无效" });
return;
}
// 验证用户是否存在
var user = await _context.Users.FindAsync(uid);
if (user == null)
{
_logger.LogWarning("用户ID {UserId} 不存在", uid);
context.Result = new UnauthorizedObjectResult(new { error = "用户不存在" });
return;
}
_logger.LogInformation("用户验证成功: {UserId}", uid);
}
// 设置用户信息
context.HttpContext.User = principal;
await next();
}
catch (SecurityTokenExpiredException)
{
_logger.LogWarning("令牌已过期");
context.Result = new UnauthorizedObjectResult(new { error = "令牌已过期" });
}
catch (SecurityTokenInvalidSignatureException)
{
_logger.LogWarning("令牌签名无效");
context.Result = new UnauthorizedObjectResult(new { error = "令牌签名无效" });
}
catch (SecurityTokenException ex)
{
_logger.LogWarning(ex, "令牌验证失败");
context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
}
catch (Exception ex)
{
_logger.LogError(ex, "OAuth认证时发生错误");
context.Result = new UnauthorizedObjectResult(new { error = "令牌验证失败" });
}
}
}
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

View File

@@ -6,9 +6,14 @@
<ImplicitUsings>enable</ImplicitUsings>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
<NoWarn>$(NoWarn);1591</NoWarn>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<GenerateTargetFrameworkAttribute>false</GenerateTargetFrameworkAttribute>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="BCrypt.Net-Next" Version="4.0.3" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="9.0.9" />
<PackageReference Include="Microsoft.AspNetCore.OpenApi" Version="9.0.9" />
<PackageReference Include="Microsoft.EntityFrameworkCore.Design" Version="9.0.9">
@@ -20,11 +25,13 @@
<IncludeAssets>runtime; build; native; contentfiles; analyzers; buildtransitive</IncludeAssets>
<PrivateAssets>all</PrivateAssets>
</PackageReference>
<PackageReference Include="Microsoft.IdentityModel.Tokens" Version="8.3.0" />
<PackageReference Include="Pomelo.EntityFrameworkCore.MySql" Version="9.0.0" />
<PackageReference Include="Quartz" Version="3.15.0" />
<PackageReference Include="Quartz.Extensions.Hosting" Version="3.15.0" />
<PackageReference Include="Swashbuckle.AspNetCore" Version="9.0.6" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.14.0" />
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="8.3.0" />
</ItemGroup>
</Project>

View File

@@ -1,97 +0,0 @@
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Text;
using FutureMailAPI.Models;
namespace FutureMailAPI.Helpers
{
public interface IJwtHelper
{
string GenerateToken(User user);
string GenerateToken(int userId, string username, string email);
ClaimsPrincipal? ValidateToken(string token);
}
public class JwtHelper : IJwtHelper
{
private readonly IConfiguration _configuration;
public JwtHelper(IConfiguration configuration)
{
_configuration = configuration;
}
public string GenerateToken(User user)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public string GenerateToken(int userId, string username, string email)
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, userId.ToString()),
new Claim(ClaimTypes.Name, username),
new Claim(ClaimTypes.Email, email)
}),
Expires = DateTime.UtcNow.AddDays(7),
Issuer = _configuration["Jwt:Issuer"],
Audience = _configuration["Jwt:Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
public ClaimsPrincipal? ValidateToken(string token)
{
try
{
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes(_configuration["Jwt:Key"]!);
var validationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = _configuration["Jwt:Issuer"],
ValidateAudience = true,
ValidAudience = _configuration["Jwt:Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
var principal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);
return principal;
}
catch
{
return null;
}
}
}
}

View File

@@ -7,6 +7,7 @@ namespace FutureMailAPI.Helpers
{
string HashPassword(string password);
bool VerifyPassword(string password, string hash);
bool VerifyPassword(string password, string hash, string salt);
string HashPassword(string password, string salt);
string GenerateSalt();
}
@@ -59,23 +60,55 @@ namespace FutureMailAPI.Helpers
{
try
{
// 从存储的哈希中提取盐值
// 检查哈希长度判断是完整哈希36字节还是纯哈希20字节
byte[] hashBytes = Convert.FromBase64String(hash);
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
// 使用相同的盐值计算密码的哈希
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] computedHash = pbkdf2.GetBytes(20);
// 比较两个哈希值
for (int i = 0; i < 20; i++)
if (hashBytes.Length == 36)
{
if (hashBytes[i + 16] != computedHash[i])
return false;
}
// 完整哈希格式:盐值(16字节) + 哈希值(20字节)
byte[] salt = new byte[16];
Array.Copy(hashBytes, 0, salt, 0, 16);
return true;
// 使用相同的盐值计算密码的哈希
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
byte[] computedHash = pbkdf2.GetBytes(20);
// 比较两个哈希值
for (int i = 0; i < 20; i++)
{
if (hashBytes[i + 16] != computedHash[i])
return false;
}
return true;
}
else if (hashBytes.Length == 20)
{
// 纯哈希格式:只有哈希值(20字节)
// 这种情况下,我们需要从数据库中获取盐值
// 但由于VerifyPassword方法没有盐值参数我们无法验证这种格式
// 返回false表示验证失败
return false;
}
else
{
// 未知的哈希格式
return false;
}
}
catch
{
return false;
}
}
public bool VerifyPassword(string password, string hash, string salt)
{
try
{
// 使用提供的盐值计算密码的哈希
var computedHash = HashPassword(password, salt);
return hash == computedHash;
}
catch
{

View File

@@ -1,62 +0,0 @@
using FutureMailAPI.Services;
using FutureMailAPI.Models;
namespace FutureMailAPI.Middleware
{
public class OAuthAuthenticationMiddleware
{
private readonly RequestDelegate _next;
private readonly ILogger<OAuthAuthenticationMiddleware> _logger;
public OAuthAuthenticationMiddleware(RequestDelegate next, ILogger<OAuthAuthenticationMiddleware> logger)
{
_next = next;
_logger = logger;
}
public async Task InvokeAsync(HttpContext context, IOAuthService oauthService)
{
// 检查是否需要OAuth认证
var endpoint = context.GetEndpoint();
if (endpoint != null)
{
// 如果端点标记为AllowAnonymous则跳过认证
var allowAnonymousAttribute = endpoint.Metadata.GetMetadata<Microsoft.AspNetCore.Authorization.AllowAnonymousAttribute>();
if (allowAnonymousAttribute != null)
{
await _next(context);
return;
}
}
// 检查Authorization头
var authHeader = context.Request.Headers.Authorization.FirstOrDefault();
if (authHeader != null && authHeader.StartsWith("Bearer "))
{
var token = authHeader.Substring("Bearer ".Length).Trim();
// 验证令牌
var validationResult = await oauthService.ValidateTokenAsync(token);
if (validationResult.Success)
{
// 获取访问令牌信息
var accessToken = await oauthService.GetAccessTokenAsync(token);
if (accessToken != null)
{
// 将用户信息添加到HttpContext
context.Items["UserId"] = accessToken.UserId;
context.Items["UserEmail"] = accessToken.User.Email;
context.Items["AccessToken"] = accessToken;
await _next(context);
return;
}
}
}
// 如果没有有效的令牌返回401未授权
context.Response.StatusCode = 401;
await context.Response.WriteAsync("未授权访问");
}
}
}

View File

@@ -1,559 +0,0 @@
// <auto-generated />
using System;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FutureMailAPI.Migrations
{
[DbContext(typeof(FutureMailDbContext))]
[Migration("20251016011551_AddOAuthEntities")]
partial class AddOAuthEntities
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("IsRead")
.HasColumnType("INTEGER");
b.Property<bool>("IsReplied")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ReadAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ReceivedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int?>("ReplyMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SentMailId");
b.ToTable("ReceivedMails");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Attachments")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DeliveryTime")
.HasColumnType("TEXT");
b.Property<string>("EncryptionKey")
.HasColumnType("TEXT");
b.Property<bool>("IsEncrypted")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int>("RecipientType")
.HasColumnType("INTEGER");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<DateTime>("SentAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<string>("Theme")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("TriggerDetails")
.HasColumnType("TEXT");
b.Property<int>("TriggerType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SenderId");
b.ToTable("SentMails");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Color")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<double>("Opacity")
.HasColumnType("REAL");
b.Property<double>("PositionX")
.HasColumnType("REAL");
b.Property<double>("PositionY")
.HasColumnType("REAL");
b.Property<double>("PositionZ")
.HasColumnType("REAL");
b.Property<double>("Rotation")
.HasColumnType("REAL");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId1")
.HasColumnType("INTEGER");
b.Property<double>("Size")
.HasColumnType("REAL");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SentMailId");
b.HasIndex("SentMailId1");
b.HasIndex("UserId");
b.ToTable("TimeCapsules");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Avatar")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("TEXT");
b.Property<string>("Nickname")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PreferredBackground")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PreferredScene")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany("ReceivedMails")
.HasForeignKey("RecipientId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("SentMail");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId1");
b.HasOne("FutureMailAPI.Models.User", "Sender")
.WithMany("SentMails")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.HasOne("FutureMailAPI.Models.SentMail", null)
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany("TimeCapsules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SentMail");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");
b.Navigation("SentMails");
b.Navigation("TimeCapsules");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -1,180 +0,0 @@
using System;
using Microsoft.EntityFrameworkCore.Migrations;
#nullable disable
namespace FutureMailAPI.Migrations
{
/// <inheritdoc />
public partial class AddOAuthEntities : Migration
{
/// <inheritdoc />
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.CreateTable(
name: "OAuthClients",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
ClientId = table.Column<string>(type: "TEXT", nullable: false),
ClientSecret = table.Column<string>(type: "TEXT", nullable: false),
Name = table.Column<string>(type: "TEXT", nullable: false),
RedirectUris = table.Column<string>(type: "TEXT", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsActive = table.Column<bool>(type: "INTEGER", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP"),
UpdatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthClients", x => x.Id);
});
migrationBuilder.CreateTable(
name: "OAuthAccessTokens",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Token = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsRevoked = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthAccessTokens", x => x.Id);
table.ForeignKey(
name: "FK_OAuthAccessTokens_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthAccessTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OAuthAuthorizationCodes",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Code = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
RedirectUri = table.Column<string>(type: "TEXT", nullable: false),
Scopes = table.Column<string>(type: "TEXT", nullable: false),
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthAuthorizationCodes", x => x.Id);
table.ForeignKey(
name: "FK_OAuthAuthorizationCodes_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthAuthorizationCodes_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateTable(
name: "OAuthRefreshTokens",
columns: table => new
{
Id = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Token = table.Column<string>(type: "TEXT", nullable: false),
ClientId = table.Column<int>(type: "INTEGER", nullable: false),
UserId = table.Column<int>(type: "INTEGER", nullable: false),
IsUsed = table.Column<bool>(type: "INTEGER", nullable: false),
ExpiresAt = table.Column<DateTime>(type: "TEXT", nullable: false),
CreatedAt = table.Column<DateTime>(type: "TEXT", nullable: false, defaultValueSql: "CURRENT_TIMESTAMP")
},
constraints: table =>
{
table.PrimaryKey("PK_OAuthRefreshTokens", x => x.Id);
table.ForeignKey(
name: "FK_OAuthRefreshTokens_OAuthClients_ClientId",
column: x => x.ClientId,
principalTable: "OAuthClients",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
table.ForeignKey(
name: "FK_OAuthRefreshTokens_Users_UserId",
column: x => x.UserId,
principalTable: "Users",
principalColumn: "Id",
onDelete: ReferentialAction.Cascade);
});
migrationBuilder.CreateIndex(
name: "IX_OAuthAccessTokens_ClientId",
table: "OAuthAccessTokens",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAccessTokens_UserId",
table: "OAuthAccessTokens",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAuthorizationCodes_ClientId",
table: "OAuthAuthorizationCodes",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthAuthorizationCodes_UserId",
table: "OAuthAuthorizationCodes",
column: "UserId");
migrationBuilder.CreateIndex(
name: "IX_OAuthClients_ClientId",
table: "OAuthClients",
column: "ClientId",
unique: true);
migrationBuilder.CreateIndex(
name: "IX_OAuthRefreshTokens_ClientId",
table: "OAuthRefreshTokens",
column: "ClientId");
migrationBuilder.CreateIndex(
name: "IX_OAuthRefreshTokens_UserId",
table: "OAuthRefreshTokens",
column: "UserId");
}
/// <inheritdoc />
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "OAuthAccessTokens");
migrationBuilder.DropTable(
name: "OAuthAuthorizationCodes");
migrationBuilder.DropTable(
name: "OAuthRefreshTokens");
migrationBuilder.DropTable(
name: "OAuthClients");
}
}
}

View File

@@ -1,564 +0,0 @@
// <auto-generated />
using System;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage.ValueConversion;
#nullable disable
namespace FutureMailAPI.Migrations
{
[DbContext(typeof(FutureMailDbContext))]
[Migration("20251016012504_AddSaltToUser")]
partial class AddSaltToUser
{
/// <inheritdoc />
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("ClientId")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<string>("Name")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("UpdatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.HasKey("Id");
b.HasIndex("ClientId")
.IsUnique();
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<bool>("IsRead")
.HasColumnType("INTEGER");
b.Property<bool>("IsReplied")
.HasColumnType("INTEGER");
b.Property<DateTime?>("ReadAt")
.HasColumnType("TEXT");
b.Property<DateTime>("ReceivedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int?>("ReplyMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SentMailId");
b.ToTable("ReceivedMails");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Attachments")
.HasColumnType("TEXT");
b.Property<string>("Content")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("DeliveryTime")
.HasColumnType("TEXT");
b.Property<string>("EncryptionKey")
.HasColumnType("TEXT");
b.Property<bool>("IsEncrypted")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId")
.HasColumnType("INTEGER");
b.Property<int?>("RecipientId1")
.HasColumnType("INTEGER");
b.Property<int>("RecipientType")
.HasColumnType("INTEGER");
b.Property<int>("SenderId")
.HasColumnType("INTEGER");
b.Property<DateTime>("SentAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<string>("Theme")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("Title")
.IsRequired()
.HasMaxLength(200)
.HasColumnType("TEXT");
b.Property<string>("TriggerDetails")
.HasColumnType("TEXT");
b.Property<int>("TriggerType")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("RecipientId");
b.HasIndex("RecipientId1");
b.HasIndex("SenderId");
b.ToTable("SentMails");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Color")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<double>("Opacity")
.HasColumnType("REAL");
b.Property<double>("PositionX")
.HasColumnType("REAL");
b.Property<double>("PositionY")
.HasColumnType("REAL");
b.Property<double>("PositionZ")
.HasColumnType("REAL");
b.Property<double>("Rotation")
.HasColumnType("REAL");
b.Property<int>("SentMailId")
.HasColumnType("INTEGER");
b.Property<int>("SentMailId1")
.HasColumnType("INTEGER");
b.Property<double>("Size")
.HasColumnType("REAL");
b.Property<int>("Status")
.HasColumnType("INTEGER");
b.Property<int>("Type")
.HasColumnType("INTEGER");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("SentMailId");
b.HasIndex("SentMailId1");
b.HasIndex("UserId");
b.ToTable("TimeCapsules");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("Avatar")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<string>("Email")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<bool>("IsActive")
.HasColumnType("INTEGER");
b.Property<DateTime?>("LastLoginAt")
.HasColumnType("TEXT");
b.Property<string>("Nickname")
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("PasswordHash")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("PreferredBackground")
.HasMaxLength(50)
.HasColumnType("TEXT");
b.Property<string>("PreferredScene")
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<string>("Username")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.HasKey("Id");
b.HasIndex("Email")
.IsUnique();
b.HasIndex("Username")
.IsUnique();
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany("ReceivedMails")
.HasForeignKey("RecipientId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("SentMail");
});
modelBuilder.Entity("FutureMailAPI.Models.SentMail", b =>
{
b.HasOne("FutureMailAPI.Models.User", null)
.WithMany()
.HasForeignKey("RecipientId")
.OnDelete(DeleteBehavior.SetNull);
b.HasOne("FutureMailAPI.Models.User", "Recipient")
.WithMany()
.HasForeignKey("RecipientId1");
b.HasOne("FutureMailAPI.Models.User", "Sender")
.WithMany("SentMails")
.HasForeignKey("SenderId")
.OnDelete(DeleteBehavior.Restrict)
.IsRequired();
b.Navigation("Recipient");
b.Navigation("Sender");
});
modelBuilder.Entity("FutureMailAPI.Models.TimeCapsule", b =>
{
b.HasOne("FutureMailAPI.Models.SentMail", null)
.WithMany()
.HasForeignKey("SentMailId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.SentMail", "SentMail")
.WithMany()
.HasForeignKey("SentMailId1")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany("TimeCapsules")
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("SentMail");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");
b.Navigation("SentMails");
b.Navigation("TimeCapsules");
});
#pragma warning restore 612, 618
}
}
}

View File

@@ -17,90 +17,6 @@ namespace FutureMailAPI.Migrations
#pragma warning disable 612, 618
modelBuilder.HasAnnotation("ProductVersion", "9.0.9");
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsRevoked")
.HasColumnType("INTEGER");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Token")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAccessTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
b.Property<string>("Code")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
.ValueGeneratedOnAdd()
.HasColumnType("TEXT")
.HasDefaultValueSql("CURRENT_TIMESTAMP");
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("RedirectUri")
.IsRequired()
.HasColumnType("TEXT");
b.Property<string>("Scopes")
.IsRequired()
.HasColumnType("TEXT");
b.Property<int>("UserId")
.HasColumnType("INTEGER");
b.HasKey("Id");
b.HasIndex("ClientId");
b.HasIndex("UserId");
b.ToTable("OAuthAuthorizationCodes");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Property<int>("Id")
@@ -109,10 +25,12 @@ namespace FutureMailAPI.Migrations
b.Property<string>("ClientId")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("ClientSecret")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime>("CreatedAt")
@@ -125,6 +43,7 @@ namespace FutureMailAPI.Migrations
b.Property<string>("Name")
.IsRequired()
.HasMaxLength(100)
.HasColumnType("TEXT");
b.Property<string>("RedirectUris")
@@ -148,12 +67,17 @@ namespace FutureMailAPI.Migrations
b.ToTable("OAuthClients");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
{
b.Property<int>("Id")
.ValueGeneratedOnAdd()
.HasColumnType("INTEGER");
b.Property<string>("AccessToken")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<int>("ClientId")
.HasColumnType("INTEGER");
@@ -165,11 +89,22 @@ namespace FutureMailAPI.Migrations
b.Property<DateTime>("ExpiresAt")
.HasColumnType("TEXT");
b.Property<bool>("IsUsed")
.HasColumnType("INTEGER");
b.Property<string>("Token")
b.Property<string>("RefreshToken")
.IsRequired()
.HasMaxLength(255)
.HasColumnType("TEXT");
b.Property<DateTime?>("RevokedAt")
.HasColumnType("TEXT");
b.Property<string>("Scope")
.HasColumnType("TEXT");
b.Property<string>("TokenType")
.IsRequired()
.HasColumnType("TEXT");
b.Property<DateTime?>("UpdatedAt")
.HasColumnType("TEXT");
b.Property<int>("UserId")
@@ -177,11 +112,17 @@ namespace FutureMailAPI.Migrations
b.HasKey("Id");
b.HasIndex("AccessToken")
.IsUnique();
b.HasIndex("ClientId");
b.HasIndex("RefreshToken")
.IsUnique();
b.HasIndex("UserId");
b.ToTable("OAuthRefreshTokens");
b.ToTable("OAuthTokens");
});
modelBuilder.Entity("FutureMailAPI.Models.ReceivedMail", b =>
@@ -397,6 +338,13 @@ namespace FutureMailAPI.Migrations
.HasMaxLength(20)
.HasColumnType("TEXT");
b.Property<string>("RefreshToken")
.HasMaxLength(500)
.HasColumnType("TEXT");
b.Property<DateTime?>("RefreshTokenExpiryTime")
.HasColumnType("TEXT");
b.Property<string>("Salt")
.IsRequired()
.HasMaxLength(255)
@@ -418,48 +366,10 @@ namespace FutureMailAPI.Migrations
b.ToTable("Users");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAccessToken", b =>
modelBuilder.Entity("FutureMailAPI.Models.OAuthToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthAuthorizationCode", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.HasOne("FutureMailAPI.Models.User", "User")
.WithMany()
.HasForeignKey("UserId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
b.Navigation("Client");
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthRefreshToken", b =>
{
b.HasOne("FutureMailAPI.Models.OAuthClient", "Client")
.WithMany()
.WithMany("Tokens")
.HasForeignKey("ClientId")
.OnDelete(DeleteBehavior.Cascade)
.IsRequired();
@@ -547,6 +457,11 @@ namespace FutureMailAPI.Migrations
b.Navigation("User");
});
modelBuilder.Entity("FutureMailAPI.Models.OAuthClient", b =>
{
b.Navigation("Tokens");
});
modelBuilder.Entity("FutureMailAPI.Models.User", b =>
{
b.Navigation("ReceivedMails");

View File

@@ -0,0 +1,38 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FutureMailAPI.Models
{
public class OAuthClient
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(100)]
public string ClientId { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string ClientSecret { get; set; } = string.Empty;
[Required]
[MaxLength(100)]
public string Name { get; set; } = string.Empty;
[Required]
public string RedirectUris { get; set; } = string.Empty;
[Required]
public string Scopes { get; set; } = string.Empty;
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
// 导航属性
public virtual ICollection<OAuthToken> Tokens { get; set; } = new List<OAuthToken>();
}
}

View File

@@ -1,63 +0,0 @@
namespace FutureMailAPI.Models
{
public class OAuthClient
{
public int Id { get; set; }
public string ClientId { get; set; } = string.Empty;
public string ClientSecret { get; set; } = string.Empty;
public string Name { get; set; } = string.Empty;
public string RedirectUris { get; set; } = string.Empty; // JSON array
public string Scopes { get; set; } = string.Empty; // JSON array
public bool IsActive { get; set; } = true;
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime UpdatedAt { get; set; } = DateTime.UtcNow;
}
public class OAuthAuthorizationCode
{
public int Id { get; set; }
public string Code { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public string RedirectUri { get; set; } = string.Empty;
public string Scopes { get; set; } = string.Empty;
public bool IsUsed { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
public class OAuthRefreshToken
{
public int Id { get; set; }
public string Token { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public bool IsUsed { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
public class OAuthAccessToken
{
public int Id { get; set; }
public string Token { get; set; } = string.Empty;
public int ClientId { get; set; }
public int UserId { get; set; }
public string Scopes { get; set; } = string.Empty;
public bool IsRevoked { get; set; } = false;
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
// Navigation properties
public virtual OAuthClient Client { get; set; } = null!;
public virtual User User { get; set; } = null!;
}
}

View File

@@ -0,0 +1,44 @@
using System.ComponentModel.DataAnnotations;
using System.ComponentModel.DataAnnotations.Schema;
namespace FutureMailAPI.Models
{
public class OAuthToken
{
[Key]
public int Id { get; set; }
[Required]
[MaxLength(255)]
public string AccessToken { get; set; } = string.Empty;
[Required]
[MaxLength(255)]
public string RefreshToken { get; set; } = string.Empty;
[Required]
public string TokenType { get; set; } = "Bearer";
public DateTime ExpiresAt { get; set; }
public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
public DateTime? UpdatedAt { get; set; }
public DateTime? RevokedAt { get; set; }
public string? Scope { get; set; }
// 外键
public int UserId { get; set; }
public int ClientId { get; set; }
// 导航属性
[ForeignKey("UserId")]
public virtual User User { get; set; } = null!;
[ForeignKey("ClientId")]
public virtual OAuthClient Client { get; set; } = null!;
}
}

View File

@@ -42,6 +42,12 @@ namespace FutureMailAPI.Models
[MaxLength(50)]
public string? PreferredBackground { get; set; } = "default";
[MaxLength(500)]
public string? RefreshToken { get; set; }
public DateTime? RefreshTokenExpiryTime { get; set; }
// 导航属性
public virtual ICollection<SentMail> SentMails { get; set; } = new List<SentMail>();
public virtual ICollection<ReceivedMail> ReceivedMails { get; set; } = new List<ReceivedMail>();

View File

@@ -1,12 +0,0 @@
// 测试OAuth 2.0密码授权流程
// 1. 使用密码授权获取访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=password&username=testuser3&password=password123&client_id=futuremail-client&client_secret=futuremail-secret
###
// 2. 使用访问令牌访问受保护的API
GET http://localhost:5001/api/v1/mails
Authorization: Bearer YOUR_ACCESS_TOKEN

View File

@@ -1,37 +0,0 @@
// 测试OAuth 2.0认证流程
// 1. 创建OAuth客户端
POST http://localhost:5001/api/v1/oauth/clients
Content-Type: application/json
{
"clientName": "TestClient",
"redirectUris": ["http://localhost:3000/callback"],
"scopes": ["read", "write"]
}
###
// 2. 获取授权码在浏览器中访问以下URL
// http://localhost:5001/api/v1/oauth/authorize?response_type=code&client_id=test_client&redirect_uri=http://localhost:3000/callback&scope=read&state=xyz
###
// 3. 使用授权码获取访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=authorization_code&code=YOUR_AUTHORIZATION_CODE&redirect_uri=http://localhost:3000/callback&client_id=test_client&client_secret=YOUR_CLIENT_SECRET
###
// 4. 使用访问令牌访问受保护的API
GET http://localhost:5001/api/v1/mails
Authorization: Bearer YOUR_ACCESS_TOKEN
###
// 5. 刷新访问令牌
POST http://localhost:5001/api/v1/oauth/token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=YOUR_REFRESH_TOKEN&client_id=test_client&client_secret=YOUR_CLIENT_SECRET

View File

@@ -1,18 +1,20 @@
using Microsoft.EntityFrameworkCore;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.IdentityModel.Tokens;
using Quartz;
using FutureMailAPI.Data;
using FutureMailAPI.Helpers;
using FutureMailAPI.Services;
using FutureMailAPI.Middleware;
using FutureMailAPI.Extensions;
using FutureMailAPI.Filters;
using Microsoft.Extensions.FileProviders;
using System.Text;
var builder = WebApplication.CreateBuilder(args);
// 配置服务器监听所有网络接口的5001端口
// 配置服务器监听所有网络接口的5003端口
builder.WebHost.ConfigureKestrel(options =>
{
options.ListenAnyIP(5001);
options.ListenAnyIP(5003);
});
// 配置数据库连接
@@ -20,9 +22,6 @@ var connectionString = builder.Configuration.GetConnectionString("DefaultConnect
builder.Services.AddDbContext<FutureMailDbContext>(options =>
options.UseSqlite(connectionString));
// 配置OAuth 2.0认证
// 注意我们使用自定义中间件实现OAuth 2.0认证而不是使用内置的JWT认证
// 配置Swagger/OpenAPI
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen(c =>
@@ -40,13 +39,42 @@ builder.Services.AddScoped<IUserService, UserService>();
builder.Services.AddScoped<IMailService, MailService>();
builder.Services.AddScoped<ITimeCapsuleService, TimeCapsuleService>();
builder.Services.AddScoped<IAuthService, AuthService>();
builder.Services.AddScoped<IOAuthService, OAuthService>();
builder.Services.AddScoped<IAIAssistantService, AIAssistantService>();
builder.Services.AddScoped<IPersonalSpaceService, PersonalSpaceService>();
builder.Services.AddScoped<IFileUploadService, FileUploadService>();
builder.Services.AddScoped<INotificationService, NotificationService>();
builder.Services.AddScoped<IOAuthService, OAuthService>();
builder.Services.AddScoped<IInitializationService, InitializationService>();
// 添加JWT认证
var jwtSettings = builder.Configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
builder.Services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(key),
ValidateIssuer = true,
ValidIssuer = jwtSettings["Issuer"],
ValidateAudience = true,
ValidAudience = jwtSettings["Audience"],
ValidateLifetime = true,
ClockSkew = TimeSpan.Zero
};
});
builder.Services.AddAuthorization();
// 配置Quartz任务调度
builder.Services.AddQuartz(q =>
{
@@ -64,8 +92,11 @@ builder.Services.AddQuartz(q =>
// 添加Quartz主机服务
builder.Services.AddQuartzHostedService(q => q.WaitForJobsToComplete = true);
// 添加控制器
builder.Services.AddControllers();
// 添加控制器并注册OAuth认证过滤器为全局过滤器
builder.Services.AddControllers(options =>
{
options.Filters.Add<OAuthAuthenticationFilter>();
});
// 添加CORS
builder.Services.AddCors(options =>
@@ -106,10 +137,7 @@ app.UseStaticFiles(new StaticFileOptions
app.UseCors("AllowAll");
// 添加OAuth 2.0认证中间件
app.UseMiddleware<OAuthAuthenticationMiddleware>();
app.UseAuthorization();
app.MapControllers();

View File

@@ -5,7 +5,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "http://0.0.0.0:5001",
"applicationUrl": "http://0.0.0.0:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}
@@ -14,7 +14,7 @@
"commandName": "Project",
"dotnetRunMessages": true,
"launchBrowser": false,
"applicationUrl": "https://localhost:7236;http://0.0.0.0:5001",
"applicationUrl": "https://localhost:7236;http://0.0.0.0:5003",
"environmentVariables": {
"ASPNETCORE_ENVIRONMENT": "Development"
}

View File

@@ -1,6 +1,13 @@
using FutureMailAPI.Helpers;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
using FutureMailAPI.Data;
using Microsoft.EntityFrameworkCore;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text;
namespace FutureMailAPI.Services
{
@@ -8,86 +15,73 @@ namespace FutureMailAPI.Services
{
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto);
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
Task<ApiResponse<string>> RefreshTokenAsync(string token);
Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret);
}
public class AuthService : IAuthService
{
private readonly IUserService _userService;
private readonly IPasswordHelper _passwordHelper;
private readonly IOAuthService _oauthService;
private readonly FutureMailDbContext _context;
private readonly IConfiguration _configuration;
public AuthService(
IUserService userService,
IPasswordHelper passwordHelper,
IOAuthService oauthService)
FutureMailDbContext context,
IConfiguration configuration)
{
_userService = userService;
_passwordHelper = passwordHelper;
_oauthService = oauthService;
_context = context;
_configuration = configuration;
}
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
{
// 使用默认客户端ID和密钥进行OAuth登录
// 在实际应用中,这些应该从配置中获取
var defaultClientId = "futuremail_default_client";
var defaultClientSecret = "futuremail_default_secret";
return await LoginWithOAuthAsync(loginDto, defaultClientId, defaultClientSecret);
}
public async Task<ApiResponse<AuthResponseDto>> LoginWithOAuthAsync(UserLoginDto loginDto, string clientId, string clientSecret)
{
// 创建OAuth登录请求
var oauthLoginDto = new OAuthLoginDto
{
UsernameOrEmail = loginDto.UsernameOrEmail,
Password = loginDto.Password,
ClientId = clientId,
ClientSecret = clientSecret,
Scope = "read write" // 默认权限范围
};
// 使用OAuth服务进行登录
var oauthResult = await _oauthService.LoginAsync(oauthLoginDto);
if (!oauthResult.Success)
{
return ApiResponse<AuthResponseDto>.ErrorResult(oauthResult.Message ?? "登录失败");
}
// 获取用户信息
var userResult = await _userService.GetUserByUsernameOrEmailAsync(loginDto.UsernameOrEmail);
if (!userResult.Success || userResult.Data == null)
{
return ApiResponse<AuthResponseDto>.ErrorResult("获取用户信息失败");
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
var user = userResult.Data;
var userDto = userResult.Data;
// 创建用户响应DTO
var userResponse = new UserResponseDto
// 获取原始用户信息用于密码验证
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userDto.Id);
if (user == null)
{
Id = user.Id,
Username = user.Username,
Email = user.Email,
Nickname = user.Nickname,
Avatar = user.Avatar,
CreatedAt = user.CreatedAt,
LastLoginAt = DateTime.UtcNow
};
return ApiResponse<AuthResponseDto>.ErrorResult("用户不存在");
}
// 创建认证响应DTO使用OAuth令牌
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
// 更新用户响应DTO
userDto.LastLoginAt = DateTime.UtcNow;
// 生成JWT令牌
var token = GenerateJwtToken(user);
var refreshToken = GenerateRefreshToken();
// 保存刷新令牌到用户表
user.RefreshToken = refreshToken;
user.RefreshTokenExpiryTime = DateTime.UtcNow.AddDays(7); // 刷新令牌7天过期
await _context.SaveChangesAsync();
// 创建认证响应DTO
var authResponse = new AuthResponseDto
{
Token = oauthResult.Data.AccessToken,
RefreshToken = oauthResult.Data.RefreshToken,
Expires = DateTime.UtcNow.AddSeconds(oauthResult.Data.ExpiresIn),
User = userResponse
User = userDto,
Token = token,
RefreshToken = refreshToken,
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
@@ -119,7 +113,7 @@ namespace FutureMailAPI.Services
return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
}
// 注册成功后,自动使用OAuth登录
// 注册成功后,自动登录
var loginDto = new UserLoginDto
{
UsernameOrEmail = registerDto.Username,
@@ -129,40 +123,35 @@ namespace FutureMailAPI.Services
return await LoginAsync(loginDto);
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
private string GenerateJwtToken(User user)
{
// 注意在OAuth 2.0中令牌验证应该由OAuth中间件处理
// 这里我们暂时返回成功实际使用时应该通过OAuth 2.0的令牌验证流程
return ApiResponse<bool>.SuccessResult(true);
}
public async Task<ApiResponse<string>> RefreshTokenAsync(string token)
{
// 在OAuth 2.0中刷新令牌需要客户端ID和密钥
// 这里我们使用默认客户端凭据
var defaultClientId = "futuremail_default_client";
var defaultClientSecret = "futuremail_default_secret";
// 创建OAuth刷新令牌请求
var oauthTokenRequest = new OAuthTokenRequestDto
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var tokenDescriptor = new SecurityTokenDescriptor
{
ClientId = defaultClientId,
ClientSecret = defaultClientSecret,
RefreshToken = token,
GrantType = "refresh_token",
Scope = "read write"
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = jwtSettings["Issuer"],
Audience = jwtSettings["Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
// 使用OAuth服务刷新令牌
var oauthResult = await _oauthService.RefreshTokenAsync(oauthTokenRequest);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
if (!oauthResult.Success)
{
return ApiResponse<string>.ErrorResult(oauthResult.Message ?? "刷新令牌失败");
}
// 返回新的访问令牌
return ApiResponse<string>.SuccessResult(oauthResult.Data.AccessToken, "令牌刷新成功");
private string GenerateRefreshToken()
{
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@@ -5,15 +5,9 @@ namespace FutureMailAPI.Services
{
public interface IOAuthService
{
Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto);
Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId);
Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request);
Task<ApiResponse<bool>> RevokeTokenAsync(string token);
Task<ApiResponse<bool>> ValidateTokenAsync(string token);
Task<OAuthAccessToken?> GetAccessTokenAsync(string token);
Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret);
Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto);
Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request);
Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request);
Task<bool> RevokeTokenAsync(string accessToken);
Task<OAuthToken?> GetTokenAsync(string accessToken);
}
}

View File

@@ -48,7 +48,7 @@ namespace FutureMailAPI.Services
}
// 创建默认OAuth客户端
var defaultClient = new OAuthClient
var defaultClient = new Models.OAuthClient
{
ClientId = defaultClientId,
ClientSecret = "futuremail_default_secret",

View File

@@ -1,416 +1,204 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using FutureMailAPI.Helpers;
using Microsoft.IdentityModel.Tokens;
using System.IdentityModel.Tokens.Jwt;
using System.Security.Claims;
using System.Security.Cryptography;
using System.Text.Json;
using System.Text;
using FutureMailAPI.Data;
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
namespace FutureMailAPI.Services
{
public class OAuthService : IOAuthService
{
private readonly FutureMailDbContext _context;
private readonly IConfiguration _configuration;
private readonly ILogger<OAuthService> _logger;
private readonly IPasswordHelper _passwordHelper;
public OAuthService(FutureMailDbContext context, ILogger<OAuthService> logger, IPasswordHelper passwordHelper)
public OAuthService(FutureMailDbContext context, IConfiguration configuration, ILogger<OAuthService> logger)
{
_context = context;
_configuration = configuration;
_logger = logger;
_passwordHelper = passwordHelper;
}
public async Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto)
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginRequestDto request)
{
var clientId = GenerateRandomString(32);
var clientSecret = GenerateRandomString(64);
var client = new OAuthClient
try
{
ClientId = clientId,
ClientSecret = clientSecret,
Name = createDto.Name,
RedirectUris = JsonSerializer.Serialize(createDto.RedirectUris),
Scopes = JsonSerializer.Serialize(createDto.Scopes),
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
// 验证OAuth客户端
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.ClientSecret == request.ClientSecret);
_context.OAuthClients.Add(client);
await _context.SaveChangesAsync();
var result = new OAuthClientSecretDto
{
ClientId = clientId,
ClientSecret = clientSecret
};
return ApiResponse<OAuthClientSecretDto>.SuccessResult(result, "OAuth客户端创建成功");
}
public async Task<ApiResponse<OAuthClientDto>> GetClientAsync(string clientId)
{
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
if (client == null)
{
return ApiResponse<OAuthClientDto>.ErrorResult("客户端不存在");
}
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
var scopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
var result = new OAuthClientDto
{
Id = client.Id,
ClientId = client.ClientId,
Name = client.Name,
RedirectUris = redirectUris,
Scopes = scopes,
IsActive = client.IsActive,
CreatedAt = client.CreatedAt,
UpdatedAt = client.UpdatedAt
};
return ApiResponse<OAuthClientDto>.SuccessResult(result);
}
public async Task<ApiResponse<OAuthAuthorizationResponseDto>> AuthorizeAsync(int userId, OAuthAuthorizationRequestDto request)
{
// 验证客户端
var client = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == request.ClientId && c.IsActive);
if (client == null)
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的客户端ID");
}
// 验证重定向URI
var redirectUris = JsonSerializer.Deserialize<string[]>(client.RedirectUris) ?? Array.Empty<string>();
if (!redirectUris.Contains(request.RedirectUri))
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult("无效的重定向URI");
}
// 验证范围
var clientScopes = JsonSerializer.Deserialize<string[]>(client.Scopes) ?? Array.Empty<string>();
var requestedScopes = request.Scope.Split(' ', StringSplitOptions.RemoveEmptyEntries);
foreach (var scope in requestedScopes)
{
if (!clientScopes.Contains(scope))
if (client == null)
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult($"无效的范围: {scope}");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证用户凭据
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == request.Username);
if (user == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户不存在");
}
// 验证密码
if (!BCrypt.Net.BCrypt.Verify(request.Password, user.PasswordHash))
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("密码错误");
}
// 生成访问令牌
var accessToken = GenerateJwtToken(user, client);
var refreshToken = GenerateRefreshToken();
// 保存令牌到数据库
var oauthToken = new OAuthToken
{
AccessToken = accessToken,
RefreshToken = refreshToken,
UserId = user.Id,
ClientId = client.Id,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthTokens.Add(oauthToken);
await _context.SaveChangesAsync();
var response = new OAuthTokenResponseDto
{
AccessToken = accessToken,
RefreshToken = refreshToken,
TokenType = "Bearer",
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
}
// 生成授权码
var code = GenerateRandomString(64);
var authorizationCode = new OAuthAuthorizationCode
catch (Exception ex)
{
Code = code,
ClientId = client.Id,
UserId = userId,
RedirectUri = request.RedirectUri,
Scopes = request.Scope,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddMinutes(10), // 授权码10分钟后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAuthorizationCodes.Add(authorizationCode);
await _context.SaveChangesAsync();
var result = new OAuthAuthorizationResponseDto
{
Code = code,
State = request.State
};
return ApiResponse<OAuthAuthorizationResponseDto>.SuccessResult(result);
_logger.LogError(ex, "OAuth登录时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
}
public async Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request)
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthRefreshTokenRequestDto request)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
try
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
// 查找刷新令牌
var token = await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.RefreshToken == request.RefreshToken);
if (token == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
}
// 生成新的访问令牌
var accessToken = GenerateJwtToken(token.User, token.Client);
var refreshToken = GenerateRefreshToken();
// 更新令牌
token.AccessToken = accessToken;
token.RefreshToken = refreshToken;
token.ExpiresAt = DateTime.UtcNow.AddHours(1);
token.UpdatedAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
var response = new OAuthTokenResponseDto
{
AccessToken = accessToken,
RefreshToken = refreshToken,
TokenType = "Bearer",
ExpiresIn = 3600 // 1小时单位秒
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(response);
}
// 验证授权码
var authCode = await _context.OAuthAuthorizationCodes
.Include(c => c.Client)
.Include(c => c.User)
.FirstOrDefaultAsync(c => c.Code == request.Code && c.ClientId == client.Id);
if (authCode == null || authCode.IsUsed || authCode.ExpiresAt < DateTime.UtcNow)
catch (Exception ex)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的授权码");
_logger.LogError(ex, "OAuth刷新令牌时发生错误");
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("服务器内部错误");
}
// 验证重定向URI
if (authCode.RedirectUri != request.RedirectUri)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("重定向URI不匹配");
}
// 标记授权码为已使用
authCode.IsUsed = true;
await _context.SaveChangesAsync();
// 生成访问令牌和刷新令牌
var accessToken = GenerateRandomString(64);
var refreshToken = GenerateRandomString(64);
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : authCode.Scopes;
var oauthAccessToken = new OAuthAccessToken
{
Token = accessToken,
ClientId = client.Id,
UserId = authCode.UserId,
Scopes = scopes,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
};
var oauthRefreshToken = new OAuthRefreshToken
{
Token = refreshToken,
ClientId = client.Id,
UserId = authCode.UserId,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(oauthAccessToken);
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = accessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = refreshToken,
Scope = scopes
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
}
public async Task<ApiResponse<OAuthTokenResponseDto>> RefreshTokenAsync(OAuthTokenRequestDto request)
public async Task<bool> RevokeTokenAsync(string accessToken)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
try
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
var token = await _context.OAuthTokens
.FirstOrDefaultAsync(t => t.AccessToken == accessToken);
if (token != null)
{
_context.OAuthTokens.Remove(token);
await _context.SaveChangesAsync();
return true;
}
return false;
}
// 验证刷新令牌
var refreshToken = await _context.OAuthRefreshTokens
.Include(t => t.Client)
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == request.RefreshToken && t.ClientId == client.Id);
if (refreshToken == null || refreshToken.IsUsed || refreshToken.ExpiresAt < DateTime.UtcNow)
catch (Exception ex)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
_logger.LogError(ex, "OAuth撤销令牌时发生错误");
return false;
}
// 标记旧刷新令牌为已使用
refreshToken.IsUsed = true;
// 撤销旧访问令牌
var oldAccessTokens = await _context.OAuthAccessTokens
.Where(t => t.UserId == refreshToken.UserId && t.ClientId == client.Id && !t.IsRevoked)
.ToListAsync();
foreach (var token in oldAccessTokens)
{
token.IsRevoked = true;
}
// 生成新的访问令牌和刷新令牌
var newAccessToken = GenerateRandomString(64);
var newRefreshToken = GenerateRandomString(64);
var scopes = !string.IsNullOrEmpty(request.Scope) ? request.Scope : "";
var newOAuthAccessToken = new OAuthAccessToken
{
Token = newAccessToken,
ClientId = client.Id,
UserId = refreshToken.UserId,
Scopes = scopes,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
};
var newOAuthRefreshToken = new OAuthRefreshToken
{
Token = newRefreshToken,
ClientId = client.Id,
UserId = refreshToken.UserId,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(newOAuthAccessToken);
_context.OAuthRefreshTokens.Add(newOAuthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = newAccessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = newRefreshToken,
Scope = scopes
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
}
public async Task<ApiResponse<bool>> RevokeTokenAsync(string token)
public async Task<OAuthToken?> GetTokenAsync(string accessToken)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token);
if (accessToken == null)
try
{
return ApiResponse<bool>.ErrorResult("令牌不存在");
return await _context.OAuthTokens
.Include(t => t.User)
.Include(t => t.Client)
.FirstOrDefaultAsync(t => t.AccessToken == accessToken && t.ExpiresAt > DateTime.UtcNow);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取OAuth令牌时发生错误");
return null;
}
accessToken.IsRevoked = true;
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "令牌已撤销");
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
private string GenerateJwtToken(User user, OAuthClient client)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
if (accessToken == null)
var jwtSettings = _configuration.GetSection("Jwt");
var key = Encoding.ASCII.GetBytes(jwtSettings["Key"] ?? throw new InvalidOperationException("JWT密钥未配置"));
var tokenDescriptor = new SecurityTokenDescriptor
{
return ApiResponse<bool>.ErrorResult("无效的令牌");
}
return ApiResponse<bool>.SuccessResult(true);
}
public async Task<OAuthAccessToken?> GetAccessTokenAsync(string token)
{
return await _context.OAuthAccessTokens
.Include(t => t.Client)
.Include(t => t.User)
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
}
public async Task<OAuthClient?> GetClientByCredentialsAsync(string clientId, string clientSecret)
{
if (string.IsNullOrEmpty(clientSecret))
{
// 公开客户端只验证客户端ID
return await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.IsActive);
}
// 机密客户端验证客户端ID和密钥
return await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == clientId && c.ClientSecret == clientSecret && c.IsActive);
}
public async Task<ApiResponse<OAuthTokenResponseDto>> LoginAsync(OAuthLoginDto loginDto)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(loginDto.ClientId, loginDto.ClientSecret);
if (client == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证用户凭据
var user = await _context.Users
.FirstOrDefaultAsync(u => (u.Email == loginDto.UsernameOrEmail || u.Nickname == loginDto.UsernameOrEmail));
if (user == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
}
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("用户名或密码错误");
}
// 生成访问令牌和刷新令牌
var accessToken = GenerateRandomString(64);
var refreshToken = GenerateRandomString(64);
var oauthAccessToken = new OAuthAccessToken
{
Token = accessToken,
ClientId = client.Id,
UserId = user.Id,
Scopes = loginDto.Scope,
IsRevoked = false,
ExpiresAt = DateTime.UtcNow.AddHours(1), // 访问令牌1小时后过期
CreatedAt = DateTime.UtcNow
Subject = new ClaimsIdentity(new[]
{
new Claim(ClaimTypes.NameIdentifier, user.Id.ToString()),
new Claim(ClaimTypes.Name, user.Username),
new Claim(ClaimTypes.Email, user.Email),
new Claim("client_id", client.ClientId)
}),
Expires = DateTime.UtcNow.AddHours(1),
Issuer = jwtSettings["Issuer"],
Audience = jwtSettings["Audience"],
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)
};
var oauthRefreshToken = new OAuthRefreshToken
{
Token = refreshToken,
ClientId = client.Id,
UserId = user.Id,
IsUsed = false,
ExpiresAt = DateTime.UtcNow.AddDays(30), // 刷新令牌30天后过期
CreatedAt = DateTime.UtcNow
};
_context.OAuthAccessTokens.Add(oauthAccessToken);
_context.OAuthRefreshTokens.Add(oauthRefreshToken);
await _context.SaveChangesAsync();
var result = new OAuthTokenResponseDto
{
AccessToken = accessToken,
TokenType = "Bearer",
ExpiresIn = 3600, // 1小时
RefreshToken = refreshToken,
Scope = loginDto.Scope
};
return ApiResponse<OAuthTokenResponseDto>.SuccessResult(result);
var tokenHandler = new JwtSecurityTokenHandler();
var token = tokenHandler.CreateToken(tokenDescriptor);
return tokenHandler.WriteToken(token);
}
private string GenerateRandomString(int length)
private string GenerateRefreshToken()
{
const string chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
var random = new Random();
var result = new char[length];
for (int i = 0; i < length; i++)
{
result[i] = chars[random.Next(chars.Length)];
}
return new string(result);
var randomNumber = new byte[32];
using var rng = RandomNumberGenerator.Create();
rng.GetBytes(randomNumber);
return Convert.ToBase64String(randomNumber);
}
}
}

View File

@@ -50,10 +50,10 @@ namespace FutureMailAPI.Services
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
// 生成盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
// 创建新用户(使用正确的密码哈希方法)
var user = new User
{
Username = registerDto.Username,
@@ -94,7 +94,7 @@ namespace FutureMailAPI.Services
}
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash, user.Salt))
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
@@ -103,13 +103,9 @@ namespace FutureMailAPI.Services
user.LastLoginAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
// 注意这里不再生成JWT令牌因为我们将使用OAuth 2.0
// 在OAuth 2.0流程中令牌是通过OAuth端点生成的
// 创建认证响应无token版本
var authResponse = new AuthResponseDto
{
Token = "", // 临时空字符串实际使用OAuth 2.0令牌
Expires = DateTime.UtcNow.AddDays(7),
User = MapToUserResponseDto(user)
};
@@ -225,7 +221,7 @@ namespace FutureMailAPI.Services
}
// 验证当前密码
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash))
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash, user.Salt))
{
return ApiResponse<bool>.ErrorResult("当前密码错误");
}
@@ -259,10 +255,10 @@ namespace FutureMailAPI.Services
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
// 生成盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
// 创建新用户(使用正确的密码哈希方法)
var user = new User
{
Username = registerDto.Username,

View File

@@ -1,26 +0,0 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
var optionsBuilder = new DbContextOptionsBuilder<FutureMailDbContext>();
optionsBuilder.UseSqlite("Data Source=FutureMail.db");
using var context = new FutureMailDbContext(optionsBuilder.Options);
// 检查OAuth客户端数据
var clients = await context.OAuthClients.ToListAsync();
Console.WriteLine($"OAuth客户端数量: {clients.Count}");
foreach (var client in clients)
{
Console.WriteLine($"客户端ID: {client.ClientId}, 名称: {client.Name}, 是否激活: {client.IsActive}");
}
// 检查用户数据
var users = await context.Users.ToListAsync();
Console.WriteLine($"用户数量: {users.Count}");
foreach (var user in users)
{
Console.WriteLine($"用户ID: {user.Id}, 用户名: {user.Username}, 邮箱: {user.Email}");
}

View File

@@ -1,53 +0,0 @@
using System;
using System.Net.Http;
using System.Text;
using System.Text.Json;
using System.Threading.Tasks;
class TestRegisterProgram
{
static async Task Main(string[] args)
{
var client = new HttpClient();
// 生成随机用户名
var random = new Random();
var username = $"testuser{random.Next(1000, 9999)}";
var email = $"{username}@example.com";
Console.WriteLine($"尝试注册用户: {username}, 邮箱: {email}");
// 创建注册请求
var registerData = new
{
Username = username,
Email = email,
Password = "password123"
};
var json = JsonSerializer.Serialize(registerData);
var content = new StringContent(json, Encoding.UTF8, "application/json");
try
{
// 发送注册请求
var response = await client.PostAsync("http://localhost:5001/api/v1/auth/register", content);
if (response.IsSuccessStatusCode)
{
var responseContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"注册成功! 响应内容: {responseContent}");
}
else
{
var errorContent = await response.Content.ReadAsStringAsync();
Console.WriteLine($"注册失败! 状态码: {response.StatusCode}");
Console.WriteLine($"错误内容: {errorContent}");
}
}
catch (Exception ex)
{
Console.WriteLine($"发生异常: {ex.Message}");
}
}
}

View File

@@ -9,20 +9,14 @@
"ConnectionStrings": {
"DefaultConnection": "Data Source=FutureMail.db"
},
"JwtSettings": {
"SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient",
"ExpirationInMinutes": 1440
},
"Jwt": {
"Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient"
},
"FileUpload": {
"UploadPath": "uploads",
"BaseUrl": "http://localhost:5054/uploads",
"MaxFileSize": 104857600
},
"Jwt": {
"Key": "ThisIsASecretKeyForJWTTokenGenerationAndValidation123456789",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient"
}
}

Binary file not shown.

View File

@@ -8,20 +8,30 @@
".NETCoreApp,Version=v9.0": {
"FutureMailAPI/1.0.0": {
"dependencies": {
"BCrypt.Net-Next": "4.0.3",
"Microsoft.AspNetCore.Authentication.JwtBearer": "9.0.9",
"Microsoft.AspNetCore.OpenApi": "9.0.9",
"Microsoft.EntityFrameworkCore.Design": "9.0.9",
"Microsoft.EntityFrameworkCore.Sqlite": "9.0.9",
"Microsoft.IdentityModel.Tokens": "8.3.0",
"Pomelo.EntityFrameworkCore.MySql": "9.0.0",
"Quartz": "3.15.0",
"Quartz.Extensions.Hosting": "3.15.0",
"Swashbuckle.AspNetCore": "9.0.6",
"System.IdentityModel.Tokens.Jwt": "8.14.0"
"System.IdentityModel.Tokens.Jwt": "8.3.0"
},
"runtime": {
"FutureMailAPI.dll": {}
}
},
"BCrypt.Net-Next/4.0.3": {
"runtime": {
"lib/net6.0/BCrypt.Net-Next.dll": {
"assemblyVersion": "4.0.3.0",
"fileVersion": "4.0.3.0"
}
}
},
"Humanizer.Core/2.14.1": {
"runtime": {
"lib/net6.0/Humanizer.dll": {
@@ -539,39 +549,39 @@
}
}
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"Microsoft.IdentityModel.Abstractions/8.3.0": {
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
"assemblyVersion": "8.3.0.0",
"fileVersion": "8.3.0.51204"
}
}
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.14.0"
"Microsoft.IdentityModel.Tokens": "8.3.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
"assemblyVersion": "8.3.0.0",
"fileVersion": "8.3.0.51204"
}
}
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"Microsoft.IdentityModel.Logging/8.3.0": {
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.14.0"
"Microsoft.IdentityModel.Abstractions": "8.3.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
"assemblyVersion": "8.3.0.0",
"fileVersion": "8.3.0.51204"
}
}
},
"Microsoft.IdentityModel.Protocols/8.0.1": {
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.14.0"
"Microsoft.IdentityModel.Tokens": "8.3.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Protocols.dll": {
@@ -583,7 +593,7 @@
"Microsoft.IdentityModel.Protocols.OpenIdConnect/8.0.1": {
"dependencies": {
"Microsoft.IdentityModel.Protocols": "8.0.1",
"System.IdentityModel.Tokens.Jwt": "8.14.0"
"System.IdentityModel.Tokens.Jwt": "8.3.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Protocols.OpenIdConnect.dll": {
@@ -592,15 +602,14 @@
}
}
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"Microsoft.IdentityModel.Tokens/8.3.0": {
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "9.0.9",
"Microsoft.IdentityModel.Logging": "8.14.0"
"Microsoft.IdentityModel.Logging": "8.3.0"
},
"runtime": {
"lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
"assemblyVersion": "8.3.0.0",
"fileVersion": "8.3.0.51204"
}
}
},
@@ -927,15 +936,15 @@
}
}
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"System.IdentityModel.Tokens.Jwt/8.3.0": {
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
"Microsoft.IdentityModel.Tokens": "8.14.0"
"Microsoft.IdentityModel.JsonWebTokens": "8.3.0",
"Microsoft.IdentityModel.Tokens": "8.3.0"
},
"runtime": {
"lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
"assemblyVersion": "8.14.0.0",
"fileVersion": "8.14.0.60815"
"assemblyVersion": "8.3.0.0",
"fileVersion": "8.3.0.51204"
}
}
}
@@ -947,6 +956,13 @@
"serviceable": false,
"sha512": ""
},
"BCrypt.Net-Next/4.0.3": {
"type": "package",
"serviceable": true,
"sha512": "sha512-W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==",
"path": "bcrypt.net-next/4.0.3",
"hashPath": "bcrypt.net-next.4.0.3.nupkg.sha512"
},
"Humanizer.Core/2.14.1": {
"type": "package",
"serviceable": true,
@@ -1136,26 +1152,26 @@
"path": "microsoft.extensions.primitives/9.0.9",
"hashPath": "microsoft.extensions.primitives.9.0.9.nupkg.sha512"
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"Microsoft.IdentityModel.Abstractions/8.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==",
"path": "microsoft.identitymodel.abstractions/8.14.0",
"hashPath": "microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512"
"sha512": "sha512-jNin7yvWZu+K3U24q+6kD+LmGSRfbkHl9Px8hN1XrGwq6ZHgKGi/zuTm5m08G27fwqKfVXIWuIcUeq4Y1VQUOg==",
"path": "microsoft.identitymodel.abstractions/8.3.0",
"hashPath": "microsoft.identitymodel.abstractions.8.3.0.nupkg.sha512"
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
"path": "microsoft.identitymodel.jsonwebtokens/8.14.0",
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512"
"sha512": "sha512-4SVXLT8sDG7CrHiszEBrsDYi+aDW0W9d+fuWUGdZPBdan56aM6fGXJDjbI0TVGEDjJhXbACQd8F/BnC7a+m2RQ==",
"path": "microsoft.identitymodel.jsonwebtokens/8.3.0",
"hashPath": "microsoft.identitymodel.jsonwebtokens.8.3.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"Microsoft.IdentityModel.Logging/8.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
"path": "microsoft.identitymodel.logging/8.14.0",
"hashPath": "microsoft.identitymodel.logging.8.14.0.nupkg.sha512"
"sha512": "sha512-4w4pSIGHhCCLTHqtVNR2Cc/zbDIUWIBHTZCu/9ZHm2SVwrXY3RJMcZ7EFGiKqmKZMQZJzA0bpwCZ6R8Yb7i5VQ==",
"path": "microsoft.identitymodel.logging/8.3.0",
"hashPath": "microsoft.identitymodel.logging.8.3.0.nupkg.sha512"
},
"Microsoft.IdentityModel.Protocols/8.0.1": {
"type": "package",
@@ -1171,12 +1187,12 @@
"path": "microsoft.identitymodel.protocols.openidconnect/8.0.1",
"hashPath": "microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512"
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"Microsoft.IdentityModel.Tokens/8.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
"path": "microsoft.identitymodel.tokens/8.14.0",
"hashPath": "microsoft.identitymodel.tokens.8.14.0.nupkg.sha512"
"sha512": "sha512-yGzqmk+kInH50zeSEH/L1/J0G4/yqTQNq4YmdzOhpE7s/86tz37NS2YbbY2ievbyGjmeBI1mq26QH+yBR6AK3Q==",
"path": "microsoft.identitymodel.tokens/8.3.0",
"hashPath": "microsoft.identitymodel.tokens.8.3.0.nupkg.sha512"
},
"Microsoft.OpenApi/1.6.25": {
"type": "package",
@@ -1332,12 +1348,12 @@
"path": "system.composition.typedparts/7.0.0",
"hashPath": "system.composition.typedparts.7.0.0.nupkg.sha512"
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"System.IdentityModel.Tokens.Jwt/8.3.0": {
"type": "package",
"serviceable": true,
"sha512": "sha512-EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
"path": "system.identitymodel.tokens.jwt/8.14.0",
"hashPath": "system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512"
"sha512": "sha512-9GESpDG0Zb17HD5mBW/uEWi2yz/uKPmCthX2UhyLnk42moGH2FpMgXA2Y4l2Qc7P75eXSUTA6wb/c9D9GSVkzw==",
"path": "system.identitymodel.tokens.jwt/8.3.0",
"hashPath": "system.identitymodel.tokens.jwt.8.3.0.nupkg.sha512"
}
}
}

View File

@@ -25,11 +25,35 @@
<param name="request">未来预测请求</param>
<returns>未来预测结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.AIController.GetCurrentUserId">
<member name="T:FutureMailAPI.Controllers.BaseController">
<summary>
从JWT令牌中获取当前用户ID
基础控制器,提供通用的用户身份验证方法
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserId">
<summary>
获取当前用户ID
兼容OAuth中间件和JWT令牌两种验证方式
</summary>
<returns>用户ID如果未认证则返回0</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserEmail">
<summary>
获取当前用户邮箱
</summary>
<returns>用户邮箱,如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUsername">
<summary>
获取当前用户名
</summary>
<returns>用户名,如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentClientId">
<summary>
获取当前客户端ID
</summary>
<returns>客户端ID如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
@@ -59,12 +83,6 @@
<param name="fileId">文件ID</param>
<returns>文件信息</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.GetCurrentUserId">
<summary>
从当前请求中获取用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
<summary>
注册设备
@@ -78,47 +96,6 @@
</summary>
<returns>通知设置</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.Login(FutureMailAPI.DTOs.OAuthLoginDto)">
<summary>
OAuth登录端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.CreateClient(FutureMailAPI.DTOs.OAuthClientCreateDto)">
<summary>
创建OAuth客户端
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.GetClient(System.String)">
<summary>
获取OAuth客户端信息
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.Authorize(FutureMailAPI.DTOs.OAuthAuthorizationRequestDto)">
<summary>
OAuth授权端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.ExchangeToken(FutureMailAPI.DTOs.OAuthTokenRequestDto)">
<summary>
OAuth令牌端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.RevokeToken(System.String,System.String)">
<summary>
撤销令牌
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.IntrospectToken(System.String)">
<summary>
验证令牌
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
@@ -146,24 +123,12 @@
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetCurrentUserId">
<summary>
从当前请求中获取用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
<summary>
获取用户统计数据
</summary>
<returns>用户统计数据</returns>
</member>
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
@@ -173,12 +138,6 @@
<param name="endDate">结束日期</param>
<returns>用户时间线</returns>
</member>
<member name="M:FutureMailAPI.Controllers.TimelineController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传附件
@@ -193,12 +152,6 @@
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
<summary>
获取用户订阅信息
@@ -211,25 +164,9 @@
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户ID
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户邮箱
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前访问令牌
获取当前用户ID简化版本不再依赖token
</summary>
</member>
<member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
@@ -261,18 +198,6 @@
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddSaltToUser">
<inheritdoc />
</member>
@@ -282,8 +207,5 @@
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
</members>
</doc>

View File

@@ -9,20 +9,14 @@
"ConnectionStrings": {
"DefaultConnection": "Data Source=FutureMail.db"
},
"JwtSettings": {
"SecretKey": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient",
"ExpirationInMinutes": 1440
},
"Jwt": {
"Key": "FutureMailSecretKey2024!@#LongerKeyForHMACSHA256",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient"
},
"FileUpload": {
"UploadPath": "uploads",
"BaseUrl": "http://localhost:5054/uploads",
"MaxFileSize": 104857600
},
"Jwt": {
"Key": "ThisIsASecretKeyForJWTTokenGenerationAndValidation123456789",
"Issuer": "FutureMailAPI",
"Audience": "FutureMailClient"
}
}

View File

@@ -1,11 +0,0 @@
{
"createDto": {
"title": "我的第一封未来邮件",
"content": "这是一封测试邮件,将在未来某个时间点发送。",
"recipientType": 0,
"deliveryTime": "2025-12-31T23:59:59Z",
"triggerType": 0,
"isEncrypted": false,
"theme": "default"
}
}

View File

@@ -1 +0,0 @@
{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}

View File

@@ -1 +0,0 @@
{"createDto":{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}}

View File

@@ -1,4 +0,0 @@
// <autogenerated />
using System;
using System.Reflection;
[assembly: global::System.Runtime.Versioning.TargetFrameworkAttribute(".NETCoreApp,Version=v9.0", FrameworkDisplayName = ".NET 9.0")]

View File

@@ -1,22 +0,0 @@
//------------------------------------------------------------------------------
// <auto-generated>
// This code was generated by a tool.
//
// Changes to this file may cause incorrect behavior and will be lost if
// the code is regenerated.
// </auto-generated>
//------------------------------------------------------------------------------
using System;
using System.Reflection;
[assembly: System.Reflection.AssemblyCompanyAttribute("FutureMailAPI")]
[assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")]
[assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")]
[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+4e3cd6f3650e353b2eb1c64551c9d04565131e90")]
[assembly: System.Reflection.AssemblyProductAttribute("FutureMailAPI")]
[assembly: System.Reflection.AssemblyTitleAttribute("FutureMailAPI")]
[assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")]
// 由 MSBuild WriteCodeFragment 类生成。

View File

@@ -1 +0,0 @@
9c51bfff94b782fcc97f29ab7088592309efeb7bdb74c602e7b8fc76443617ec

View File

@@ -1 +1 @@
ea3656a12946e5802d7338be6d7da9f40d863a7215e7081b0501689571314e33
51372bde626c0ba3aa5386f92d0ea465adcc4b558852e21737182a326708e608

View File

@@ -1,19 +1,19 @@
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.AssemblyReference.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rpswa.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.GeneratedMSBuildEditorConfig.editorconfig
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.AssemblyInfoInputs.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.AssemblyInfo.cs
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.csproj.CoreCompileInputs.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cs
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.MvcApplicationPartsAssemblyInfo.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.Development.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\appsettings.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.runtime.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.endpoints.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.exe
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.deps.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.runtimeconfig.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.pdb
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.xml
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Humanizer.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.Authentication.JwtBearer.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.AspNetCore.OpenApi.dll
@@ -25,10 +25,12 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.CodeAnalysis.Workspaces.MSBuild.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Data.Sqlite.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Abstractions.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Design.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Relational.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Sqlite.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Abstractions.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Caching.Memory.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Extensions.Configuration.Abstractions.dll
@@ -52,6 +54,9 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.DependencyInjection.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Quartz.Extensions.Hosting.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.batteries_v2.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.core.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.provider.e_sqlite3.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.Swagger.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerGen.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Swashbuckle.AspNetCore.SwaggerUI.dll
@@ -127,27 +132,6 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\tr\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hans\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\zh-Hant\Microsoft.CodeAnalysis.Workspaces.MSBuild.BuildHost.resources.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjimswa.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmrazor.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmcshtml.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\scopedcss\bundle\FutureMailAPI.styles.css
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.development.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.endpoints.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\swae.build.ex.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMa.9A5350ED.Up2Date
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\refint\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.pdb
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.genruntimeconfig.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\ref\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.staticwebassets.runtime.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.Data.Sqlite.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\Microsoft.EntityFrameworkCore.Sqlite.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.batteries_v2.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.core.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\SQLitePCLRaw.provider.e_sqlite3.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\browser-wasm\nativeassets\net9.0\e_sqlite3.a
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm\native\libe_sqlite3.so
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\linux-arm64\native\libe_sqlite3.so
@@ -169,9 +153,20 @@ C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-arm64\native\e_sqlite3.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x64\native\e_sqlite3.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\runtimes\win-x86\native\e_sqlite3.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\FutureMailAPI.xml
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjimswa.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmrazor.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\rjsmcshtml.dswa.cache.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\scopedcss\bundle\FutureMailAPI.styles.css
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.json.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.development.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\staticwebassets.build.endpoints.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\swae.build.ex.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMa.9A5350ED.Up2Date
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\refint\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.xml
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\temp_register.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail_direct.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\test_mail_simple.json
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.pdb
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\FutureMailAPI.genruntimeconfig.cache
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\obj\Debug\net9.0\ref\FutureMailAPI.dll
C:\Users\Administrator\Desktop\快乐转盘\未来邮箱02APi\FutureMailAPI\bin\Debug\net9.0\BCrypt.Net-Next.dll

View File

@@ -25,11 +25,35 @@
<param name="request">未来预测请求</param>
<returns>未来预测结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.AIController.GetCurrentUserId">
<member name="T:FutureMailAPI.Controllers.BaseController">
<summary>
从JWT令牌中获取当前用户ID
基础控制器,提供通用的用户身份验证方法
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserId">
<summary>
获取当前用户ID
兼容OAuth中间件和JWT令牌两种验证方式
</summary>
<returns>用户ID如果未认证则返回0</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUserEmail">
<summary>
获取当前用户邮箱
</summary>
<returns>用户邮箱,如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentUsername">
<summary>
获取当前用户名
</summary>
<returns>用户名,如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.BaseController.GetCurrentClientId">
<summary>
获取当前客户端ID
</summary>
<returns>客户端ID如果未认证则返回空字符串</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
@@ -59,12 +83,6 @@
<param name="fileId">文件ID</param>
<returns>文件信息</returns>
</member>
<member name="M:FutureMailAPI.Controllers.FileUploadController.GetCurrentUserId">
<summary>
从当前请求中获取用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.RegisterDevice(FutureMailAPI.DTOs.NotificationDeviceRequestDto)">
<summary>
注册设备
@@ -78,47 +96,6 @@
</summary>
<returns>通知设置</returns>
</member>
<member name="M:FutureMailAPI.Controllers.NotificationController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.Login(FutureMailAPI.DTOs.OAuthLoginDto)">
<summary>
OAuth登录端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.CreateClient(FutureMailAPI.DTOs.OAuthClientCreateDto)">
<summary>
创建OAuth客户端
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.GetClient(System.String)">
<summary>
获取OAuth客户端信息
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.Authorize(FutureMailAPI.DTOs.OAuthAuthorizationRequestDto)">
<summary>
OAuth授权端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.ExchangeToken(FutureMailAPI.DTOs.OAuthTokenRequestDto)">
<summary>
OAuth令牌端点
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.RevokeToken(System.String,System.String)">
<summary>
撤销令牌
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.OAuthController.IntrospectToken(System.String)">
<summary>
验证令牌
</summary>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
@@ -146,24 +123,12 @@
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Controllers.PersonalSpaceController.GetCurrentUserId">
<summary>
从当前请求中获取用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetStatistics">
<summary>
获取用户统计数据
</summary>
<returns>用户统计数据</returns>
</member>
<member name="M:FutureMailAPI.Controllers.StatisticsController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.TimelineController.GetTimeline(FutureMailAPI.DTOs.TimelineType,System.Nullable{System.DateTime},System.Nullable{System.DateTime})">
<summary>
获取用户时间线
@@ -173,12 +138,6 @@
<param name="endDate">结束日期</param>
<returns>用户时间线</returns>
</member>
<member name="M:FutureMailAPI.Controllers.TimelineController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.UploadAttachment(FutureMailAPI.DTOs.FileUploadWithFileRequestDto)">
<summary>
上传附件
@@ -193,12 +152,6 @@
<param name="request">文件上传请求</param>
<returns>上传结果</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UploadController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetSubscription">
<summary>
获取用户订阅信息
@@ -211,25 +164,9 @@
</summary>
<returns>用户资料</returns>
</member>
<member name="M:FutureMailAPI.Controllers.UserController.GetCurrentUserId">
<summary>
从JWT令牌中获取当前用户ID
</summary>
<returns>用户ID</returns>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserId(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户ID
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentUserEmail(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前用户邮箱
</summary>
</member>
<member name="M:FutureMailAPI.Extensions.HttpContextExtensions.GetCurrentAccessToken(Microsoft.AspNetCore.Http.HttpContext)">
<summary>
获取当前访问令牌
获取当前用户ID简化版本不再依赖token
</summary>
</member>
<member name="T:FutureMailAPI.Helpers.FileUploadOperationFilter">
@@ -261,18 +198,6 @@
<member name="M:FutureMailAPI.Migrations.AddUserPreferences.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddOAuthEntities">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Up(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddOAuthEntities.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
<member name="T:FutureMailAPI.Migrations.AddSaltToUser">
<inheritdoc />
</member>
@@ -282,8 +207,5 @@
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.Down(Microsoft.EntityFrameworkCore.Migrations.MigrationBuilder)">
<inheritdoc />
</member>
<member name="M:FutureMailAPI.Migrations.AddSaltToUser.BuildTargetModel(Microsoft.EntityFrameworkCore.ModelBuilder)">
<inheritdoc />
</member>
</members>
</doc>

File diff suppressed because it is too large Load Diff

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"1nyXR9zdL54Badakr4zt6ZsTCwUunwdqRSmf7XLLUwI=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4=","pIoP9frnT632kkjB7SjrifWUQVG7c11SzIkVZRRvB50=","9kb7O83kAqQlgU/oLY\u002BLtIyvuGGaaCehodVF5CULg2w=","KtJa1U2aUQv2tuOjiicNgBLGEaKYqPhKaVpzqk4t85k=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","OWMR9yjuLx9nyoGL7u9arYiy/fkFHjayjhOVF\u002BiRuS0=","MbGDnaS5Z1urQXC0aeolLZu50a5W0ICU1IGUKW06M0s=","Qf4B5yCiEiASjhutpOPj/Oq2gQPQj6e4MCKc90vHMnw=","rnxpfH7HwD1\u002BMHTk01\u002BjopiQ58RyX\u002B9ZqhNY56R608M="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"1nyXR9zdL54Badakr4zt6ZsTCwUunwdqRSmf7XLLUwI=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU=","/ljuLPXzsWglGr0uHSpqYQARCPlOQrAX6k6LWku0gdQ=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","talZRfyIQIv4aZc27HTn01\u002B12VbY6LMFOy3\u002B3r448jo=","8GY2MSzr2E/Yc9xFiAqxn3IhTsjnylxYZUVwMfrW5vw="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"hRzLFfhtQD0jC2zNthCXf5A5W0LGZjQdHMs0v5Enof8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4=","pIoP9frnT632kkjB7SjrifWUQVG7c11SzIkVZRRvB50=","9kb7O83kAqQlgU/oLY\u002BLtIyvuGGaaCehodVF5CULg2w=","KtJa1U2aUQv2tuOjiicNgBLGEaKYqPhKaVpzqk4t85k=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","OWMR9yjuLx9nyoGL7u9arYiy/fkFHjayjhOVF\u002BiRuS0=","MbGDnaS5Z1urQXC0aeolLZu50a5W0ICU1IGUKW06M0s=","Qf4B5yCiEiASjhutpOPj/Oq2gQPQj6e4MCKc90vHMnw=","rnxpfH7HwD1\u002BMHTk01\u002BjopiQ58RyX\u002B9ZqhNY56R608M="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"hRzLFfhtQD0jC2zNthCXf5A5W0LGZjQdHMs0v5Enof8=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU=","/ljuLPXzsWglGr0uHSpqYQARCPlOQrAX6k6LWku0gdQ=","XAE6ulqLzJRH50\u002Bc9Nteizd/x9s3rvSHUFwFL265XX4=","talZRfyIQIv4aZc27HTn01\u002B12VbY6LMFOy3\u002B3r448jo=","8GY2MSzr2E/Yc9xFiAqxn3IhTsjnylxYZUVwMfrW5vw="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -1 +1 @@
{"GlobalPropertiesHash":"Bto0zkaTl4M6gb1C4K2QiQDhFhr9nJky761xwMrocfc=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","Dh2M8KitOfKPR8IeSNkaC81VB\u002BMSAUycC8vgnJB96As=","t2RF\u002B1UZM5Hw6aYb0b251h1IYJ0pLy2XznEprAc7\u002Bok=","2FbYKHJBLTvh9uGYDfvrsS/W7A7tAnJew9pbpj5fD0c=","2w8tqUWtgtJR3X6RHUFwY6ycOgc9but1QXTeF3XoSAs=","1UDkwWB80qnO\u002B89ANbOkMura0UnCvX\u002B8qPfDZHovrt4="],"CachedAssets":{},"CachedCopyCandidates":{}}
{"GlobalPropertiesHash":"Bto0zkaTl4M6gb1C4K2QiQDhFhr9nJky761xwMrocfc=","FingerprintPatternsHash":"gq3WsqcKBUGTSNle7RKKyXRIwh7M8ccEqOqYvIzoM04=","PropertyOverridesHash":"8ZRc1sGeVrPBx4lD717BgRaQekyh78QKV9SKsdt638U=","InputHashes":["PSBb4S8lcZQPImBE8id7O4eeN8h3whFn6j1jGYFQciQ=","FLPLXKwQVK16UZsWKzZrjH5kq4sMEtCZqAIkpUwa2pU="],"CachedAssets":{},"CachedCopyCandidates":{}}

View File

@@ -51,6 +51,10 @@
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"BCrypt.Net-Next": {
"target": "Package",
"version": "[4.0.3, )"
},
"Microsoft.AspNetCore.Authentication.JwtBearer": {
"target": "Package",
"version": "[9.0.9, )"
@@ -75,6 +79,10 @@
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.IdentityModel.Tokens": {
"target": "Package",
"version": "[8.3.0, )"
},
"Pomelo.EntityFrameworkCore.MySql": {
"target": "Package",
"version": "[9.0.0, )"
@@ -93,7 +101,7 @@
},
"System.IdentityModel.Tokens.Jwt": {
"target": "Package",
"version": "[8.14.0, )"
"version": "[8.3.0, )"
}
},
"imports": [

View File

@@ -2,9 +2,9 @@
<Project ToolsVersion="14.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<ImportGroup Condition=" '$(ExcludeRestorePackageImports)' != 'true' ">
<Import Project="$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets" Condition="Exists('$(NuGetPackageRoot)system.text.json\9.0.9\buildTransitive\net8.0\System.Text.Json.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.apidescription.server\9.0.0\build\Microsoft.Extensions.ApiDescription.Server.targets')" />
<Import Project="$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets" Condition="Exists('$(NuGetPackageRoot)sqlitepclraw.lib.e_sqlite3\2.1.10\buildTransitive\net9.0\SQLitePCLRaw.lib.e_sqlite3.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.logging.abstractions\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Logging.Abstractions.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.extensions.options\9.0.9\buildTransitive\net8.0\Microsoft.Extensions.Options.targets')" />
<Import Project="$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets" Condition="Exists('$(NuGetPackageRoot)mono.texttemplating\3.0.0\buildTransitive\Mono.TextTemplating.targets')" />
<Import Project="$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets" Condition="Exists('$(NuGetPackageRoot)microsoft.codeanalysis.analyzers\3.3.4\buildTransitive\Microsoft.CodeAnalysis.Analyzers.targets')" />

View File

@@ -2,6 +2,19 @@
"version": 3,
"targets": {
"net9.0": {
"BCrypt.Net-Next/4.0.3": {
"type": "package",
"compile": {
"lib/net6.0/BCrypt.Net-Next.dll": {
"related": ".xml"
}
},
"runtime": {
"lib/net6.0/BCrypt.Net-Next.dll": {
"related": ".xml"
}
}
},
"Humanizer.Core/2.14.1": {
"type": "package",
"compile": {
@@ -813,7 +826,7 @@
"buildTransitive/net8.0/_._": {}
}
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"Microsoft.IdentityModel.Abstractions/8.3.0": {
"type": "package",
"compile": {
"lib/net9.0/Microsoft.IdentityModel.Abstractions.dll": {
@@ -826,10 +839,10 @@
}
}
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
"type": "package",
"dependencies": {
"Microsoft.IdentityModel.Tokens": "8.14.0"
"Microsoft.IdentityModel.Tokens": "8.3.0"
},
"compile": {
"lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.dll": {
@@ -842,10 +855,10 @@
}
}
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"Microsoft.IdentityModel.Logging/8.3.0": {
"type": "package",
"dependencies": {
"Microsoft.IdentityModel.Abstractions": "8.14.0"
"Microsoft.IdentityModel.Abstractions": "8.3.0"
},
"compile": {
"lib/net9.0/Microsoft.IdentityModel.Logging.dll": {
@@ -891,11 +904,10 @@
}
}
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"Microsoft.IdentityModel.Tokens/8.3.0": {
"type": "package",
"dependencies": {
"Microsoft.Extensions.Logging.Abstractions": "8.0.0",
"Microsoft.IdentityModel.Logging": "8.14.0"
"Microsoft.IdentityModel.Logging": "8.3.0"
},
"compile": {
"lib/net9.0/Microsoft.IdentityModel.Tokens.dll": {
@@ -1364,11 +1376,11 @@
"buildTransitive/net6.0/_._": {}
}
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"System.IdentityModel.Tokens.Jwt/8.3.0": {
"type": "package",
"dependencies": {
"Microsoft.IdentityModel.JsonWebTokens": "8.14.0",
"Microsoft.IdentityModel.Tokens": "8.14.0"
"Microsoft.IdentityModel.JsonWebTokens": "8.3.0",
"Microsoft.IdentityModel.Tokens": "8.3.0"
},
"compile": {
"lib/net9.0/System.IdentityModel.Tokens.Jwt.dll": {
@@ -1476,6 +1488,37 @@
}
},
"libraries": {
"BCrypt.Net-Next/4.0.3": {
"sha512": "W+U9WvmZQgi5cX6FS5GDtDoPzUCV4LkBLkywq/kRZhuDwcbavOzcDAr3LXJFqHUi952Yj3LEYoWW0jbEUQChsA==",
"type": "package",
"path": "bcrypt.net-next/4.0.3",
"files": [
".nupkg.metadata",
".signature.p7s",
"bcrypt.net-next.4.0.3.nupkg.sha512",
"bcrypt.net-next.nuspec",
"ico.png",
"lib/net20/BCrypt.Net-Next.dll",
"lib/net20/BCrypt.Net-Next.xml",
"lib/net35/BCrypt.Net-Next.dll",
"lib/net35/BCrypt.Net-Next.xml",
"lib/net462/BCrypt.Net-Next.dll",
"lib/net462/BCrypt.Net-Next.xml",
"lib/net472/BCrypt.Net-Next.dll",
"lib/net472/BCrypt.Net-Next.xml",
"lib/net48/BCrypt.Net-Next.dll",
"lib/net48/BCrypt.Net-Next.xml",
"lib/net5.0/BCrypt.Net-Next.dll",
"lib/net5.0/BCrypt.Net-Next.xml",
"lib/net6.0/BCrypt.Net-Next.dll",
"lib/net6.0/BCrypt.Net-Next.xml",
"lib/netstandard2.0/BCrypt.Net-Next.dll",
"lib/netstandard2.0/BCrypt.Net-Next.xml",
"lib/netstandard2.1/BCrypt.Net-Next.dll",
"lib/netstandard2.1/BCrypt.Net-Next.xml",
"readme.md"
]
},
"Humanizer.Core/2.14.1": {
"sha512": "lQKvtaTDOXnoVJ20ibTuSIOf2i0uO0MPbDhd1jm238I+U/2ZnRENj0cktKZhtchBMtCUSRQ5v4xBCUbKNmyVMw==",
"type": "package",
@@ -3345,10 +3388,10 @@
"useSharedDesignerContext.txt"
]
},
"Microsoft.IdentityModel.Abstractions/8.14.0": {
"sha512": "iwbCpSjD3ehfTwBhtSNEtKPK0ICun6ov7Ibx6ISNA9bfwIyzI2Siwyi9eJFCJBwxowK9xcA1mj+jBWiigeqgcQ==",
"Microsoft.IdentityModel.Abstractions/8.3.0": {
"sha512": "jNin7yvWZu+K3U24q+6kD+LmGSRfbkHl9Px8hN1XrGwq6ZHgKGi/zuTm5m08G27fwqKfVXIWuIcUeq4Y1VQUOg==",
"type": "package",
"path": "microsoft.identitymodel.abstractions/8.14.0",
"path": "microsoft.identitymodel.abstractions/8.3.0",
"files": [
".nupkg.metadata",
".signature.p7s",
@@ -3365,14 +3408,14 @@
"lib/net9.0/Microsoft.IdentityModel.Abstractions.xml",
"lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.dll",
"lib/netstandard2.0/Microsoft.IdentityModel.Abstractions.xml",
"microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512",
"microsoft.identitymodel.abstractions.8.3.0.nupkg.sha512",
"microsoft.identitymodel.abstractions.nuspec"
]
},
"Microsoft.IdentityModel.JsonWebTokens/8.14.0": {
"sha512": "4jOpiA4THdtpLyMdAb24dtj7+6GmvhOhxf5XHLYWmPKF8ApEnApal1UnJsKO4HxUWRXDA6C4WQVfYyqsRhpNpQ==",
"Microsoft.IdentityModel.JsonWebTokens/8.3.0": {
"sha512": "4SVXLT8sDG7CrHiszEBrsDYi+aDW0W9d+fuWUGdZPBdan56aM6fGXJDjbI0TVGEDjJhXbACQd8F/BnC7a+m2RQ==",
"type": "package",
"path": "microsoft.identitymodel.jsonwebtokens/8.14.0",
"path": "microsoft.identitymodel.jsonwebtokens/8.3.0",
"files": [
".nupkg.metadata",
".signature.p7s",
@@ -3389,14 +3432,14 @@
"lib/net9.0/Microsoft.IdentityModel.JsonWebTokens.xml",
"lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.dll",
"lib/netstandard2.0/Microsoft.IdentityModel.JsonWebTokens.xml",
"microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512",
"microsoft.identitymodel.jsonwebtokens.8.3.0.nupkg.sha512",
"microsoft.identitymodel.jsonwebtokens.nuspec"
]
},
"Microsoft.IdentityModel.Logging/8.14.0": {
"sha512": "eqqnemdW38CKZEHS6diA50BV94QICozDZEvSrsvN3SJXUFwVB9gy+/oz76gldP7nZliA16IglXjXTCTdmU/Ejg==",
"Microsoft.IdentityModel.Logging/8.3.0": {
"sha512": "4w4pSIGHhCCLTHqtVNR2Cc/zbDIUWIBHTZCu/9ZHm2SVwrXY3RJMcZ7EFGiKqmKZMQZJzA0bpwCZ6R8Yb7i5VQ==",
"type": "package",
"path": "microsoft.identitymodel.logging/8.14.0",
"path": "microsoft.identitymodel.logging/8.3.0",
"files": [
".nupkg.metadata",
".signature.p7s",
@@ -3413,7 +3456,7 @@
"lib/net9.0/Microsoft.IdentityModel.Logging.xml",
"lib/netstandard2.0/Microsoft.IdentityModel.Logging.dll",
"lib/netstandard2.0/Microsoft.IdentityModel.Logging.xml",
"microsoft.identitymodel.logging.8.14.0.nupkg.sha512",
"microsoft.identitymodel.logging.8.3.0.nupkg.sha512",
"microsoft.identitymodel.logging.nuspec"
]
},
@@ -3463,10 +3506,10 @@
"microsoft.identitymodel.protocols.openidconnect.nuspec"
]
},
"Microsoft.IdentityModel.Tokens/8.14.0": {
"sha512": "lKIZiBiGd36k02TCdMHp1KlNWisyIvQxcYJvIkz7P4gSQ9zi8dgh6S5Grj8NNG7HWYIPfQymGyoZ6JB5d1Lo1g==",
"Microsoft.IdentityModel.Tokens/8.3.0": {
"sha512": "yGzqmk+kInH50zeSEH/L1/J0G4/yqTQNq4YmdzOhpE7s/86tz37NS2YbbY2ievbyGjmeBI1mq26QH+yBR6AK3Q==",
"type": "package",
"path": "microsoft.identitymodel.tokens/8.14.0",
"path": "microsoft.identitymodel.tokens/8.3.0",
"files": [
".nupkg.metadata",
".signature.p7s",
@@ -3483,7 +3526,7 @@
"lib/net9.0/Microsoft.IdentityModel.Tokens.xml",
"lib/netstandard2.0/Microsoft.IdentityModel.Tokens.dll",
"lib/netstandard2.0/Microsoft.IdentityModel.Tokens.xml",
"microsoft.identitymodel.tokens.8.14.0.nupkg.sha512",
"microsoft.identitymodel.tokens.8.3.0.nupkg.sha512",
"microsoft.identitymodel.tokens.nuspec"
]
},
@@ -3992,10 +4035,10 @@
"useSharedDesignerContext.txt"
]
},
"System.IdentityModel.Tokens.Jwt/8.14.0": {
"sha512": "EYGgN/S+HK7S6F3GaaPLFAfK0UzMrkXFyWCvXpQWFYmZln3dqtbyIO7VuTM/iIIPMzkelg8ZLlBPvMhxj6nOAA==",
"System.IdentityModel.Tokens.Jwt/8.3.0": {
"sha512": "9GESpDG0Zb17HD5mBW/uEWi2yz/uKPmCthX2UhyLnk42moGH2FpMgXA2Y4l2Qc7P75eXSUTA6wb/c9D9GSVkzw==",
"type": "package",
"path": "system.identitymodel.tokens.jwt/8.14.0",
"path": "system.identitymodel.tokens.jwt/8.3.0",
"files": [
".nupkg.metadata",
".signature.p7s",
@@ -4012,7 +4055,7 @@
"lib/net9.0/System.IdentityModel.Tokens.Jwt.xml",
"lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.dll",
"lib/netstandard2.0/System.IdentityModel.Tokens.Jwt.xml",
"system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512",
"system.identitymodel.tokens.jwt.8.3.0.nupkg.sha512",
"system.identitymodel.tokens.jwt.nuspec"
]
},
@@ -4220,16 +4263,18 @@
},
"projectFileDependencyGroups": {
"net9.0": [
"BCrypt.Net-Next >= 4.0.3",
"Microsoft.AspNetCore.Authentication.JwtBearer >= 9.0.9",
"Microsoft.AspNetCore.OpenApi >= 9.0.9",
"Microsoft.EntityFrameworkCore.Design >= 9.0.9",
"Microsoft.EntityFrameworkCore.Sqlite >= 9.0.9",
"Microsoft.EntityFrameworkCore.Tools >= 9.0.9",
"Microsoft.IdentityModel.Tokens >= 8.3.0",
"Pomelo.EntityFrameworkCore.MySql >= 9.0.0",
"Quartz >= 3.15.0",
"Quartz.Extensions.Hosting >= 3.15.0",
"Swashbuckle.AspNetCore >= 9.0.6",
"System.IdentityModel.Tokens.Jwt >= 8.14.0"
"System.IdentityModel.Tokens.Jwt >= 8.3.0"
]
},
"packageFolders": {
@@ -4283,6 +4328,10 @@
"net9.0": {
"targetAlias": "net9.0",
"dependencies": {
"BCrypt.Net-Next": {
"target": "Package",
"version": "[4.0.3, )"
},
"Microsoft.AspNetCore.Authentication.JwtBearer": {
"target": "Package",
"version": "[9.0.9, )"
@@ -4307,6 +4356,10 @@
"target": "Package",
"version": "[9.0.9, )"
},
"Microsoft.IdentityModel.Tokens": {
"target": "Package",
"version": "[8.3.0, )"
},
"Pomelo.EntityFrameworkCore.MySql": {
"target": "Package",
"version": "[9.0.0, )"
@@ -4325,7 +4378,7 @@
},
"System.IdentityModel.Tokens.Jwt": {
"target": "Package",
"version": "[8.14.0, )"
"version": "[8.3.0, )"
}
},
"imports": [

View File

@@ -1,9 +1,10 @@
{
"version": 2,
"dgSpecHash": "u8eeDj4lDD4=",
"dgSpecHash": "s9N/V2Z2v1w=",
"success": true,
"projectFilePath": "C:\\Users\\Administrator\\Desktop\\快乐转盘\\未来邮箱02APi\\FutureMailAPI\\FutureMailAPI.csproj",
"expectedPackageFiles": [
"C:\\Users\\Administrator\\.nuget\\packages\\bcrypt.net-next\\4.0.3\\bcrypt.net-next.4.0.3.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\humanizer.core\\2.14.1\\humanizer.core.2.14.1.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.aspnetcore.authentication.jwtbearer\\9.0.9\\microsoft.aspnetcore.authentication.jwtbearer.9.0.9.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.aspnetcore.openapi\\9.0.9\\microsoft.aspnetcore.openapi.9.0.9.nupkg.sha512",
@@ -39,12 +40,12 @@
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.extensions.logging.abstractions\\9.0.9\\microsoft.extensions.logging.abstractions.9.0.9.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.extensions.options\\9.0.9\\microsoft.extensions.options.9.0.9.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.extensions.primitives\\9.0.9\\microsoft.extensions.primitives.9.0.9.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.14.0\\microsoft.identitymodel.abstractions.8.14.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.14.0\\microsoft.identitymodel.jsonwebtokens.8.14.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.logging\\8.14.0\\microsoft.identitymodel.logging.8.14.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.abstractions\\8.3.0\\microsoft.identitymodel.abstractions.8.3.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.jsonwebtokens\\8.3.0\\microsoft.identitymodel.jsonwebtokens.8.3.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.logging\\8.3.0\\microsoft.identitymodel.logging.8.3.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.protocols\\8.0.1\\microsoft.identitymodel.protocols.8.0.1.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.protocols.openidconnect\\8.0.1\\microsoft.identitymodel.protocols.openidconnect.8.0.1.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.tokens\\8.14.0\\microsoft.identitymodel.tokens.8.14.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.identitymodel.tokens\\8.3.0\\microsoft.identitymodel.tokens.8.3.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\microsoft.openapi\\1.6.25\\microsoft.openapi.1.6.25.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\mono.texttemplating\\3.0.0\\mono.texttemplating.3.0.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\mysqlconnector\\2.4.0\\mysqlconnector.2.4.0.nupkg.sha512",
@@ -68,7 +69,7 @@
"C:\\Users\\Administrator\\.nuget\\packages\\system.composition.hosting\\7.0.0\\system.composition.hosting.7.0.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.composition.runtime\\7.0.0\\system.composition.runtime.7.0.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.composition.typedparts\\7.0.0\\system.composition.typedparts.7.0.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.identitymodel.tokens.jwt\\8.14.0\\system.identitymodel.tokens.jwt.8.14.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.identitymodel.tokens.jwt\\8.3.0\\system.identitymodel.tokens.jwt.8.3.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.io.pipelines\\7.0.0\\system.io.pipelines.7.0.0.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.memory\\4.5.3\\system.memory.4.5.3.nupkg.sha512",
"C:\\Users\\Administrator\\.nuget\\packages\\system.reflection.metadata\\7.0.0\\system.reflection.metadata.7.0.0.nupkg.sha512",

View File

@@ -1,11 +0,0 @@
{
"createDto": {
"title": "我的第一封未来邮件",
"content": "这是一封测试邮件,将在未来某个时间点发送。",
"recipientType": 0,
"deliveryTime": "2025-12-31T23:59:59Z",
"triggerType": 0,
"isEncrypted": false,
"theme": "default"
}
}

View File

@@ -1 +0,0 @@
{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}

View File

@@ -1 +0,0 @@
{"createDto":{"title":"My First Future Mail","content":"This is a test email that will be sent in the future.","recipientType":0,"deliveryTime":"2025-12-31T23:59:59Z","triggerType":0,"isEncrypted":false,"theme":"default"}}

63
test_mail.html Normal file
View File

@@ -0,0 +1,63 @@
<!DOCTYPE html>
<html>
<head>
<title>邮件创建测试</title>
<script src="https://cdn.jsdelivr.net/npm/axios/dist/axios.min.js"></script>
</head>
<body>
<h1>邮件创建测试</h1>
<button onclick="testLogin()">登录</button>
<button onclick="testCreateMail()">创建邮件</button>
<div id="result"></div>
<script>
let authToken = '';
async function testLogin() {
try {
const response = await axios.post('http://localhost:5003/api/v1/auth/login', {
email: 'test@example.com',
password: 'test123'
});
authToken = response.data.data.token;
document.getElementById('result').innerHTML = '<pre>登录成功,令牌: ' + authToken + '</pre>';
} catch (error) {
document.getElementById('result').innerHTML = '<pre>登录失败: ' + JSON.stringify(error.response.data, null, 2) + '</pre>';
}
}
async function testCreateMail() {
if (!authToken) {
document.getElementById('result').innerHTML = '<pre>请先登录</pre>';
return;
}
try {
const mailData = {
title: "测试邮件标题",
content: "这是一封测试邮件的内容",
recipientType: "SELF",
sendTime: "2026-10-16T08:03:58.479Z",
triggerType: "TIME",
triggerCondition: {},
attachments: [],
isEncrypted: false,
capsuleStyle: "default"
};
const response = await axios.post('http://localhost:5003/api/v1/mails/create', mailData, {
headers: {
'Authorization': 'Bearer ' + authToken,
'Content-Type': 'application/json'
}
});
document.getElementById('result').innerHTML = '<pre>邮件创建成功: ' + JSON.stringify(response.data, null, 2) + '</pre>';
} catch (error) {
document.getElementById('result').innerHTML = '<pre>邮件创建失败: ' + JSON.stringify(error.response.data, null, 2) + '</pre>';
}
}
</script>
</body>
</html>

11
test_mail_fixed.json Normal file
View File

@@ -0,0 +1,11 @@
{
"title": "测试邮件标题",
"content": "这是一封测试邮件的内容",
"recipientType": "SELF",
"sendTime": "2026-10-16T08:03:58.479Z",
"triggerType": "TIME",
"triggerCondition": {},
"attachments": [],
"isEncrypted": false,
"capsuleStyle": "default"
}