初始化
This commit is contained in:
36
FutureMailAPI/Helpers/FileUploadOperationFilter.cs
Normal file
36
FutureMailAPI/Helpers/FileUploadOperationFilter.cs
Normal file
@@ -0,0 +1,36 @@
|
||||
using Microsoft.OpenApi.Models;
|
||||
using Swashbuckle.AspNetCore.SwaggerGen;
|
||||
using Microsoft.AspNetCore.Http;
|
||||
using System.Reflection;
|
||||
using System.Linq;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
/// <summary>
|
||||
/// Swagger文件上传操作过滤器
|
||||
/// </summary>
|
||||
public class FileUploadOperationFilter : IOperationFilter
|
||||
{
|
||||
public void Apply(OpenApiOperation operation, OperationFilterContext context)
|
||||
{
|
||||
var fileUploadMime = "multipart/form-data";
|
||||
if (operation.RequestBody == null || !operation.RequestBody.Content.Any(x => x.Key.Equals(fileUploadMime, StringComparison.InvariantCultureIgnoreCase)))
|
||||
return;
|
||||
|
||||
var fileParams = context.MethodInfo.GetParameters()
|
||||
.Where(p => p.ParameterType == typeof(IFormFile) ||
|
||||
(p.ParameterType.IsClass && p.ParameterType.GetProperties().Any(prop => prop.PropertyType == typeof(IFormFile))))
|
||||
.ToList();
|
||||
|
||||
if (!fileParams.Any())
|
||||
return;
|
||||
|
||||
operation.RequestBody.Content[fileUploadMime].Schema.Properties =
|
||||
fileParams.ToDictionary(k => k.Name!, v => new OpenApiSchema()
|
||||
{
|
||||
Type = "string",
|
||||
Format = "binary"
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
97
FutureMailAPI/Helpers/JwtHelper.cs
Normal file
97
FutureMailAPI/Helpers/JwtHelper.cs
Normal file
@@ -0,0 +1,97 @@
|
||||
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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
102
FutureMailAPI/Helpers/MailDeliveryJob.cs
Normal file
102
FutureMailAPI/Helpers/MailDeliveryJob.cs
Normal file
@@ -0,0 +1,102 @@
|
||||
using Microsoft.EntityFrameworkCore;
|
||||
using Quartz;
|
||||
using FutureMailAPI.Data;
|
||||
using FutureMailAPI.Models;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
[DisallowConcurrentExecution]
|
||||
public class MailDeliveryJob : IJob
|
||||
{
|
||||
private readonly FutureMailDbContext _context;
|
||||
private readonly ILogger<MailDeliveryJob> _logger;
|
||||
|
||||
public MailDeliveryJob(FutureMailDbContext context, ILogger<MailDeliveryJob> logger)
|
||||
{
|
||||
_context = context;
|
||||
_logger = logger;
|
||||
}
|
||||
|
||||
public async Task Execute(IJobExecutionContext context)
|
||||
{
|
||||
_logger.LogInformation("开始执行邮件投递任务: {time}", DateTime.Now);
|
||||
|
||||
try
|
||||
{
|
||||
// 查找所有需要投递的邮件
|
||||
var mailsToDeliver = await _context.SentMails
|
||||
.Where(m => m.Status == 1 && m.DeliveryTime <= DateTime.UtcNow)
|
||||
.Include(m => m.Sender)
|
||||
.ToListAsync();
|
||||
|
||||
_logger.LogInformation("找到 {count} 封待投递邮件", mailsToDeliver.Count);
|
||||
|
||||
foreach (var mail in mailsToDeliver)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 更新邮件状态为投递中
|
||||
mail.Status = 2;
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
// 根据收件人类型创建接收邮件记录
|
||||
if (mail.RecipientType == 0) // 发给自己
|
||||
{
|
||||
var receivedMail = new ReceivedMail
|
||||
{
|
||||
SentMailId = mail.Id,
|
||||
RecipientId = mail.SenderId,
|
||||
ReceivedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ReceivedMails.Add(receivedMail);
|
||||
}
|
||||
else if (mail.RecipientType == 1 && mail.RecipientId.HasValue) // 发给指定用户
|
||||
{
|
||||
var receivedMail = new ReceivedMail
|
||||
{
|
||||
SentMailId = mail.Id,
|
||||
RecipientId = mail.RecipientId.Value,
|
||||
ReceivedAt = DateTime.UtcNow
|
||||
};
|
||||
|
||||
_context.ReceivedMails.Add(receivedMail);
|
||||
}
|
||||
else if (mail.RecipientType == 2) // 公开时间胶囊
|
||||
{
|
||||
// 公开时间胶囊的处理逻辑,可能需要更复杂的机制
|
||||
// 这里简化处理,暂时不实现
|
||||
}
|
||||
|
||||
// 更新邮件状态为已送达
|
||||
mail.Status = 3;
|
||||
|
||||
// 更新对应的时间胶囊状态
|
||||
var timeCapsule = await _context.TimeCapsules
|
||||
.FirstOrDefaultAsync(tc => tc.SentMailId == mail.Id);
|
||||
|
||||
if (timeCapsule != null)
|
||||
{
|
||||
timeCapsule.Status = 3; // 已开启
|
||||
}
|
||||
|
||||
await _context.SaveChangesAsync();
|
||||
|
||||
_logger.LogInformation("邮件 {mailId} 投递成功", mail.Id);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "投递邮件 {mailId} 时发生错误", mail.Id);
|
||||
// 可以添加重试逻辑或错误通知
|
||||
}
|
||||
}
|
||||
|
||||
_logger.LogInformation("邮件投递任务完成: {time}", DateTime.Now);
|
||||
}
|
||||
catch (Exception ex)
|
||||
{
|
||||
_logger.LogError(ex, "执行邮件投递任务时发生错误");
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
86
FutureMailAPI/Helpers/PasswordHelper.cs
Normal file
86
FutureMailAPI/Helpers/PasswordHelper.cs
Normal file
@@ -0,0 +1,86 @@
|
||||
using System.Security.Cryptography;
|
||||
using System.Text;
|
||||
|
||||
namespace FutureMailAPI.Helpers
|
||||
{
|
||||
public interface IPasswordHelper
|
||||
{
|
||||
string HashPassword(string password);
|
||||
bool VerifyPassword(string password, string hash);
|
||||
string HashPassword(string password, string salt);
|
||||
string GenerateSalt();
|
||||
}
|
||||
|
||||
public class PasswordHelper : IPasswordHelper
|
||||
{
|
||||
public string HashPassword(string password)
|
||||
{
|
||||
// 生成随机盐值
|
||||
byte[] salt;
|
||||
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
|
||||
|
||||
// 使用PBKDF2算法生成哈希
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, salt, 10000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
|
||||
// 组合盐值和哈希值
|
||||
byte[] hashBytes = new byte[36];
|
||||
Array.Copy(salt, 0, hashBytes, 0, 16);
|
||||
Array.Copy(hash, 0, hashBytes, 16, 20);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(hashBytes);
|
||||
}
|
||||
|
||||
public string GenerateSalt()
|
||||
{
|
||||
// 生成随机盐值
|
||||
byte[] salt;
|
||||
new RNGCryptoServiceProvider().GetBytes(salt = new byte[16]);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(salt);
|
||||
}
|
||||
|
||||
public string HashPassword(string password, string salt)
|
||||
{
|
||||
// 将Base64盐值转换为字节数组
|
||||
byte[] saltBytes = Convert.FromBase64String(salt);
|
||||
|
||||
// 使用PBKDF2算法生成哈希
|
||||
var pbkdf2 = new Rfc2898DeriveBytes(password, saltBytes, 10000);
|
||||
byte[] hash = pbkdf2.GetBytes(20);
|
||||
|
||||
// 转换为Base64字符串
|
||||
return Convert.ToBase64String(hash);
|
||||
}
|
||||
|
||||
public bool VerifyPassword(string password, string hash)
|
||||
{
|
||||
try
|
||||
{
|
||||
// 从存储的哈希中提取盐值
|
||||
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[i + 16] != computedHash[i])
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
catch
|
||||
{
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user