初始化

This commit is contained in:
2025-10-16 09:56:36 +08:00
commit de704db577
272 changed files with 37331 additions and 0 deletions

View File

@@ -0,0 +1,307 @@
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public class AIAssistantService : IAIAssistantService
{
private readonly ILogger<AIAssistantService> _logger;
public AIAssistantService(ILogger<AIAssistantService> logger)
{
_logger = logger;
}
public async Task<ApiResponse<WritingAssistantResponseDto>> GetWritingAssistanceAsync(WritingAssistantRequestDto request)
{
try
{
// 在实际应用中这里会调用真实的AI服务如OpenAI GPT
// 目前我们使用模拟数据
var response = new WritingAssistantResponseDto
{
Content = GenerateWritingContent(request),
Suggestions = GenerateWritingSuggestions(request),
EstimatedTime = EstimateWritingTime(request)
};
return ApiResponse<WritingAssistantResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取写作辅助时发生错误");
return ApiResponse<WritingAssistantResponseDto>.ErrorResult("获取写作辅助失败");
}
}
public async Task<ApiResponse<SentimentAnalysisResponseDto>> AnalyzeSentimentAsync(SentimentAnalysisRequestDto request)
{
try
{
// 在实际应用中,这里会调用真实的情感分析服务
// 目前我们使用模拟数据
var response = AnalyzeSentiment(request.Content);
return ApiResponse<SentimentAnalysisResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "分析情感时发生错误");
return ApiResponse<SentimentAnalysisResponseDto>.ErrorResult("情感分析失败");
}
}
public async Task<ApiResponse<FuturePredictionResponseDto>> PredictFutureAsync(FuturePredictionRequestDto request)
{
try
{
// 在实际应用中,这里会调用真实的预测服务
// 目前我们使用模拟数据
var response = GenerateFuturePrediction(request);
return ApiResponse<FuturePredictionResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "预测未来时发生错误");
return ApiResponse<FuturePredictionResponseDto>.ErrorResult("未来预测失败");
}
}
private string GenerateWritingContent(WritingAssistantRequestDto request)
{
// 模拟AI生成内容
return request.Type switch
{
WritingAssistantType.OUTLINE => GenerateOutline(request.Prompt, request.Tone, request.Length),
WritingAssistantType.DRAFT => GenerateDraft(request.Prompt, request.Tone, request.Length),
WritingAssistantType.COMPLETE => GenerateCompleteContent(request.Prompt, request.Tone, request.Length),
_ => "抱歉,无法生成内容。"
};
}
private string GenerateOutline(string prompt, WritingTone tone, WritingLength length)
{
return $"基于您的提示\"{prompt}\",我为您生成了以下大纲:\n\n1. 引言\n2. 主要观点\n3. 支持论据\n4. 结论\n\n这个大纲适合{GetToneDescription(tone)}的写作风格,预计可以写成{GetLengthDescription(length)}的内容。";
}
private string GenerateDraft(string prompt, WritingTone tone, WritingLength length)
{
return $"关于\"{prompt}\"的草稿:\n\n{GetToneDescription(tone)}的开场白...\n\n主要内容的初步构思...\n\n需要进一步完善的结尾部分。\n\n这是一个初步草稿您可以根据需要进一步修改和完善。";
}
private string GenerateCompleteContent(string prompt, WritingTone tone, WritingLength length)
{
return $"关于\"{prompt}\"的完整内容:\n\n{GetToneDescription(tone)}的开场白,引出主题...\n\n详细阐述主要观点包含丰富的细节和例子...\n\n深入分析并提供有力的支持论据...\n\n{GetToneDescription(tone)}的结尾,总结全文并留下深刻印象。\n\n这是一篇完整的{GetLengthDescription(length)}文章,您可以直接使用或根据需要进行微调。";
}
private List<string> GenerateWritingSuggestions(WritingAssistantRequestDto request)
{
var suggestions = new List<string>();
switch (request.Type)
{
case WritingAssistantType.OUTLINE:
suggestions.Add("考虑添加更多子论点来丰富大纲结构");
suggestions.Add("为每个主要观点添加关键词或简短描述");
break;
case WritingAssistantType.DRAFT:
suggestions.Add("添加更多具体例子来支持您的观点");
suggestions.Add("考虑调整段落顺序以改善逻辑流程");
break;
case WritingAssistantType.COMPLETE:
suggestions.Add("检查语法和拼写错误");
suggestions.Add("考虑添加过渡词来改善段落间的连接");
break;
}
suggestions.Add($"尝试使用{GetToneDescription(request.Tone)}的表达方式");
suggestions.Add($"考虑将内容调整到{GetLengthDescription(request.Length)}的长度");
return suggestions;
}
private int EstimateWritingTime(WritingAssistantRequestDto request)
{
return request.Type switch
{
WritingAssistantType.OUTLINE => 5,
WritingAssistantType.DRAFT => 15,
WritingAssistantType.COMPLETE => 30,
_ => 10
};
}
private string GetToneDescription(WritingTone tone)
{
return tone switch
{
WritingTone.FORMAL => "正式",
WritingTone.CASUAL => "轻松随意",
WritingTone.EMOTIONAL => "情感丰富",
WritingTone.INSPIRATIONAL => "鼓舞人心",
_ => "中性"
};
}
private string GetLengthDescription(WritingLength length)
{
return length switch
{
WritingLength.SHORT => "简短",
WritingLength.MEDIUM => "中等长度",
WritingLength.LONG => "长篇",
_ => "适中"
};
}
private SentimentAnalysisResponseDto AnalyzeSentiment(string content)
{
// 模拟情感分析
var random = new Random();
// 简单的关键词分析(实际应用中应使用更复杂的算法)
var positiveKeywords = new[] { "开心", "快乐", "爱", "美好", "成功", "希望", "感谢", "幸福" };
var negativeKeywords = new[] { "悲伤", "难过", "失败", "痛苦", "失望", "愤怒", "焦虑", "恐惧" };
var positiveCount = positiveKeywords.Count(keyword => content.Contains(keyword));
var negativeCount = negativeKeywords.Count(keyword => content.Contains(keyword));
SentimentType sentiment;
if (positiveCount > negativeCount)
sentiment = SentimentType.POSITIVE;
else if (negativeCount > positiveCount)
sentiment = SentimentType.NEGATIVE;
else if (positiveCount > 0 && negativeCount > 0)
sentiment = SentimentType.MIXED;
else
sentiment = SentimentType.NEUTRAL;
var emotions = new List<EmotionScore>();
// 根据情感类型生成情绪分数
switch (sentiment)
{
case SentimentType.POSITIVE:
emotions.Add(new EmotionScore { Type = EmotionType.HAPPY, Score = 0.8 });
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.6 });
emotions.Add(new EmotionScore { Type = EmotionType.GRATEFUL, Score = 0.5 });
break;
case SentimentType.NEGATIVE:
emotions.Add(new EmotionScore { Type = EmotionType.SAD, Score = 0.8 });
emotions.Add(new EmotionScore { Type = EmotionType.ANXIOUS, Score = 0.6 });
emotions.Add(new EmotionScore { Type = EmotionType.CONFUSED, Score = 0.4 });
break;
case SentimentType.MIXED:
emotions.Add(new EmotionScore { Type = EmotionType.NOSTALGIC, Score = 0.7 });
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.5 });
emotions.Add(new EmotionScore { Type = EmotionType.SAD, Score = 0.4 });
break;
default:
emotions.Add(new EmotionScore { Type = EmotionType.CONFUSED, Score = 0.3 });
emotions.Add(new EmotionScore { Type = EmotionType.HOPEFUL, Score = 0.3 });
break;
}
// 提取关键词(简单实现)
var words = content.Split(new[] { ' ', ',', '.', '!', '?', ';', ':', '', '。', '', '', '', '' }, StringSplitOptions.RemoveEmptyEntries);
var keywords = words.Where(word => word.Length > 3).GroupBy(word => word)
.OrderByDescending(g => g.Count())
.Take(5)
.Select(g => g.Key)
.ToList();
// 生成摘要
var summary = content.Length > 100 ? content.Substring(0, 100) + "..." : content;
return new SentimentAnalysisResponseDto
{
Sentiment = sentiment,
Confidence = 0.7 + random.NextDouble() * 0.3, // 0.7-1.0之间的随机数
Emotions = emotions,
Keywords = keywords,
Summary = summary
};
}
private FuturePredictionResponseDto GenerateFuturePrediction(FuturePredictionRequestDto request)
{
// 模拟未来预测
var random = new Random();
var prediction = request.Type switch
{
PredictionType.CAREER => GenerateCareerPrediction(request.Content, request.DaysAhead),
PredictionType.RELATIONSHIP => GenerateRelationshipPrediction(request.Content, request.DaysAhead),
PredictionType.HEALTH => GenerateHealthPrediction(request.Content, request.DaysAhead),
PredictionType.FINANCIAL => GenerateFinancialPrediction(request.Content, request.DaysAhead),
PredictionType.PERSONAL_GROWTH => GeneratePersonalGrowthPrediction(request.Content, request.DaysAhead),
_ => "无法进行预测。"
};
var factors = GeneratePredictionFactors(request.Type);
var suggestions = GeneratePredictionSuggestions(request.Type);
return new FuturePredictionResponseDto
{
Prediction = prediction,
Confidence = 0.6 + random.NextDouble() * 0.4, // 0.6-1.0之间的随机数
Factors = factors,
Suggestions = suggestions
};
}
private string GenerateCareerPrediction(string content, int daysAhead)
{
return $"基于您提供的信息\"{content}\",预测在未来{daysAhead}天内,您可能会遇到新的职业机会。这可能是一个晋升机会、一个新项目或是一个学习新技能的机会。建议您保持开放的心态,积极接受挑战,这将有助于您的职业发展。";
}
private string GenerateRelationshipPrediction(string content, int daysAhead)
{
return $"根据您描述的\"{content}\",预测在未来{daysAhead}天内,您的人际关系可能会有积极的变化。可能会与老朋友重新联系,或者结识新的朋友。建议您保持真诚和开放的态度,这将有助于建立更深厚的人际关系。";
}
private string GenerateHealthPrediction(string content, int daysAhead)
{
return $"基于您提供的\"{content}\"信息,预测在未来{daysAhead}天内,您的健康状况可能会有所改善。建议您保持良好的作息习惯,适当运动,并注意饮食均衡。这些小的改变可能会带来显著的健康效益。";
}
private string GenerateFinancialPrediction(string content, int daysAhead)
{
return $"根据您描述的\"{content}\",预测在未来{daysAhead}天内,您的财务状况可能会趋于稳定。可能会有意外的收入或节省开支的机会。建议您制定合理的预算计划,并考虑长期投资策略。";
}
private string GeneratePersonalGrowthPrediction(string content, int daysAhead)
{
return $"基于您分享的\"{content}\",预测在未来{daysAhead}天内,您将有机会在个人成长方面取得进展。可能会发现新的兴趣爱好,或者在学习新技能方面取得突破。建议您保持好奇心,勇于尝试新事物。";
}
private List<string> GeneratePredictionFactors(PredictionType type)
{
return type switch
{
PredictionType.CAREER => new List<string> { "行业趋势", "个人技能", "经济环境", "人脉资源" },
PredictionType.RELATIONSHIP => new List<string> { "沟通方式", "共同兴趣", "价值观", "情感需求" },
PredictionType.HEALTH => new List<string> { "生活习惯", "遗传因素", "环境因素", "心理状态" },
PredictionType.FINANCIAL => new List<string> { "收入水平", "消费习惯", "投资决策", "市场环境" },
PredictionType.PERSONAL_GROWTH => new List<string> { "学习能力", "自我认知", "生活经历", "目标设定" },
_ => new List<string> { "未知因素" }
};
}
private List<string> GeneratePredictionSuggestions(PredictionType type)
{
return type switch
{
PredictionType.CAREER => new List<string> { "持续学习新技能", "扩展人脉网络", "设定明确的职业目标", "保持积极的工作态度" },
PredictionType.RELATIONSHIP => new List<string> { "保持真诚沟通", "尊重他人观点", "定期维护关系", "表达感激之情" },
PredictionType.HEALTH => new List<string> { "保持规律作息", "均衡饮食", "适量运动", "定期体检" },
PredictionType.FINANCIAL => new List<string> { "制定预算计划", "减少不必要开支", "考虑长期投资", "建立应急基金" },
PredictionType.PERSONAL_GROWTH => new List<string> { "设定学习目标", "尝试新体验", "反思自我", "寻求反馈" },
_ => new List<string> { "保持积极态度" }
};
}
}
}

View File

@@ -0,0 +1,168 @@
using FutureMailAPI.Helpers;
using FutureMailAPI.DTOs;
using System.Security.Claims;
namespace FutureMailAPI.Services
{
public interface IAuthService
{
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;
public AuthService(
IUserService userService,
IPasswordHelper passwordHelper,
IOAuthService oauthService)
{
_userService = userService;
_passwordHelper = passwordHelper;
_oauthService = oauthService;
}
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("获取用户信息失败");
}
var user = userResult.Data;
// 创建用户响应DTO
var userResponse = new UserResponseDto
{
Id = user.Id,
Username = user.Username,
Email = user.Email,
Nickname = user.Nickname,
Avatar = user.Avatar,
CreatedAt = user.CreatedAt,
LastLoginAt = DateTime.UtcNow
};
// 创建认证响应DTO使用OAuth令牌
var authResponse = new AuthResponseDto
{
Token = oauthResult.Data.AccessToken,
RefreshToken = oauthResult.Data.RefreshToken,
Expires = DateTime.UtcNow.AddSeconds(oauthResult.Data.ExpiresIn),
User = userResponse
};
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
}
public async Task<ApiResponse<AuthResponseDto>> RegisterAsync(UserRegisterDto registerDto)
{
// 检查用户名是否已存在
var existingUserResult = await _userService.GetUserByUsernameAsync(registerDto.Username);
if (existingUserResult.Success && existingUserResult.Data != null)
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名已存在");
}
// 检查邮箱是否已存在
var existingEmailResult = await _userService.GetUserByEmailAsync(registerDto.Email);
if (existingEmailResult.Success && existingEmailResult.Data != null)
{
return ApiResponse<AuthResponseDto>.ErrorResult("邮箱已被注册");
}
// 创建用户
var createUserResult = await _userService.CreateUserAsync(registerDto);
if (!createUserResult.Success)
{
return ApiResponse<AuthResponseDto>.ErrorResult(createUserResult.Message ?? "注册失败");
}
// 注册成功后自动使用OAuth登录
var loginDto = new UserLoginDto
{
UsernameOrEmail = registerDto.Username,
Password = registerDto.Password
};
return await LoginAsync(loginDto);
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
{
// 注意在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
{
ClientId = defaultClientId,
ClientSecret = defaultClientSecret,
RefreshToken = token,
GrantType = "refresh_token",
Scope = "read write"
};
// 使用OAuth服务刷新令牌
var oauthResult = await _oauthService.RefreshTokenAsync(oauthTokenRequest);
if (!oauthResult.Success)
{
return ApiResponse<string>.ErrorResult(oauthResult.Message ?? "刷新令牌失败");
}
// 返回新的访问令牌
return ApiResponse<string>.SuccessResult(oauthResult.Data.AccessToken, "令牌刷新成功");
}
}
}

View File

@@ -0,0 +1,319 @@
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Configuration;
using FutureMailAPI.DTOs;
using System.Security.Cryptography;
using System.Text;
namespace FutureMailAPI.Services
{
public class FileUploadService : IFileUploadService
{
private readonly IConfiguration _configuration;
private readonly ILogger<FileUploadService> _logger;
private readonly string _uploadPath;
private readonly string _baseUrl;
private readonly long _maxFileSize;
private readonly List<string> _allowedImageTypes;
private readonly List<string> _allowedVideoTypes;
private readonly List<string> _allowedDocumentTypes;
private readonly List<string> _allowedAudioTypes;
public FileUploadService(IConfiguration configuration, ILogger<FileUploadService> logger)
{
_configuration = configuration;
_logger = logger;
// 从配置中获取上传路径和基础URL
_uploadPath = configuration["FileUpload:UploadPath"] ?? "uploads";
_baseUrl = configuration["FileUpload:BaseUrl"] ?? "http://localhost:5054/uploads";
_maxFileSize = long.Parse(configuration["FileUpload:MaxFileSize"] ?? "104857600"); // 默认100MB
// 允许的文件类型
_allowedImageTypes = new List<string> { "image/jpeg", "image/png", "image/gif", "image/webp" };
_allowedVideoTypes = new List<string> { "video/mp4", "video/avi", "video/mov", "video/wmv" };
_allowedDocumentTypes = new List<string> { "application/pdf", "application/msword", "application/vnd.openxmlformats-officedocument.wordprocessingml.document" };
_allowedAudioTypes = new List<string> { "audio/mpeg", "audio/wav", "audio/ogg" };
// 确保上传目录存在
EnsureUploadDirectoryExists();
}
public async Task<ApiResponse<FileUploadResponseDto>> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request)
{
try
{
if (file == null || file.Length == 0)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("请选择要上传的文件");
}
// 检查文件大小
if (file.Length > _maxFileSize)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult($"文件大小不能超过 {_maxFileSize / (1024 * 1024)}MB");
}
// 检查文件类型
var contentType = file.ContentType.ToLower();
if (!IsAllowedFileType(contentType, request.Type))
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("不支持的文件类型");
}
// 生成唯一文件名
var fileId = GenerateFileId();
var fileExtension = Path.GetExtension(file.FileName);
var fileName = $"{fileId}{fileExtension}";
// 创建用户目录
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
var categoryDirectory = string.IsNullOrEmpty(request.Category)
? userDirectory
: Path.Combine(userDirectory, request.Category);
Directory.CreateDirectory(categoryDirectory);
// 保存文件
var filePath = Path.Combine(categoryDirectory, fileName);
using (var stream = new FileStream(filePath, FileMode.Create))
{
await file.CopyToAsync(stream);
}
// 生成缩略图(如果是图片)
string thumbnailUrl = string.Empty;
if (request.Type == AttachmentType.IMAGE)
{
thumbnailUrl = await GenerateThumbnailAsync(filePath, fileName, categoryDirectory);
}
// 构建文件URL
var relativePath = Path.GetRelativePath(_uploadPath, filePath).Replace("\\", "/");
var fileUrl = $"{_baseUrl}/{relativePath}";
// 构建缩略图URL
if (!string.IsNullOrEmpty(thumbnailUrl))
{
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailUrl).Replace("\\", "/");
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
}
var response = new FileUploadResponseDto
{
FileId = fileId,
FileName = file.FileName,
FileUrl = fileUrl,
ThumbnailUrl = thumbnailUrl,
FileSize = file.Length,
ContentType = file.ContentType,
Type = request.Type,
UploadedAt = DateTime.UtcNow
};
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "上传文件时发生错误");
return ApiResponse<FileUploadResponseDto>.ErrorResult("上传文件失败");
}
}
public async Task<ApiResponse<bool>> DeleteFileAsync(string fileId, int userId)
{
try
{
// 在实际应用中,这里应该从数据库中查找文件信息
// 目前我们只是简单地根据文件ID删除文件
// 查找用户目录下的所有文件
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
if (!Directory.Exists(userDirectory))
{
return ApiResponse<bool>.ErrorResult("文件不存在");
}
// 查找匹配的文件
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
if (files.Length == 0)
{
return ApiResponse<bool>.ErrorResult("文件不存在");
}
// 删除所有相关文件(包括缩略图)
foreach (var file in files)
{
try
{
System.IO.File.Delete(file);
}
catch (Exception ex)
{
_logger.LogWarning(ex, $"删除文件 {file} 失败");
}
}
return ApiResponse<bool>.SuccessResult(true);
}
catch (Exception ex)
{
_logger.LogError(ex, "删除文件时发生错误");
return ApiResponse<bool>.ErrorResult("删除文件失败");
}
}
public async Task<ApiResponse<FileUploadResponseDto>> GetFileAsync(string fileId, int userId)
{
try
{
// 在实际应用中,这里应该从数据库中查找文件信息
// 目前我们只是简单地根据文件ID返回文件信息
// 查找用户目录下的所有文件
var userDirectory = Path.Combine(_uploadPath, userId.ToString());
if (!Directory.Exists(userDirectory))
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
}
// 查找匹配的文件
var files = Directory.GetFiles(userDirectory, $"{fileId}*", SearchOption.AllDirectories);
if (files.Length == 0)
{
return ApiResponse<FileUploadResponseDto>.ErrorResult("文件不存在");
}
// 获取主文件(排除缩略图)
var mainFile = files.FirstOrDefault(f => !f.Contains("_thumb."));
if (mainFile == null)
{
mainFile = files[0]; // 如果没有找到主文件,使用第一个文件
}
var fileInfo = new FileInfo(mainFile);
var relativePath = Path.GetRelativePath(_uploadPath, mainFile).Replace("\\", "/");
var fileUrl = $"{_baseUrl}/{relativePath}";
// 查找缩略图
var thumbnailFile = files.FirstOrDefault(f => f.Contains("_thumb."));
string thumbnailUrl = string.Empty;
if (thumbnailFile != null)
{
var relativeThumbnailPath = Path.GetRelativePath(_uploadPath, thumbnailFile).Replace("\\", "/");
thumbnailUrl = $"{_baseUrl}/{relativeThumbnailPath}";
}
// 确定文件类型
var contentType = GetContentType(fileInfo.Extension);
var attachmentType = GetAttachmentType(contentType);
var response = new FileUploadResponseDto
{
FileId = fileId,
FileName = fileInfo.Name,
FileUrl = fileUrl,
ThumbnailUrl = thumbnailUrl,
FileSize = fileInfo.Length,
ContentType = contentType,
Type = attachmentType,
UploadedAt = fileInfo.CreationTimeUtc
};
return ApiResponse<FileUploadResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取文件信息时发生错误");
return ApiResponse<FileUploadResponseDto>.ErrorResult("获取文件信息失败");
}
}
private void EnsureUploadDirectoryExists()
{
if (!Directory.Exists(_uploadPath))
{
Directory.CreateDirectory(_uploadPath);
}
}
private bool IsAllowedFileType(string contentType, AttachmentType type)
{
return type switch
{
AttachmentType.IMAGE => _allowedImageTypes.Contains(contentType),
AttachmentType.VIDEO => _allowedVideoTypes.Contains(contentType),
AttachmentType.DOCUMENT => _allowedDocumentTypes.Contains(contentType),
AttachmentType.VOICE => _allowedAudioTypes.Contains(contentType),
_ => true // 其他类型暂时允许
};
}
private string GenerateFileId()
{
return Guid.NewGuid().ToString("N");
}
private async Task<string> GenerateThumbnailAsync(string filePath, string fileName, string directory)
{
try
{
// 在实际应用中这里应该使用图像处理库如ImageSharp生成缩略图
// 目前我们只是创建一个简单的缩略图文件名
var fileExtension = Path.GetExtension(fileName);
var thumbnailFileName = $"{Path.GetFileNameWithoutExtension(fileName)}_thumb{fileExtension}";
var thumbnailPath = Path.Combine(directory, thumbnailFileName);
// 这里应该添加实际的缩略图生成代码
// 暂时只是复制原文件作为缩略图
System.IO.File.Copy(filePath, thumbnailPath);
return thumbnailPath;
}
catch (Exception ex)
{
_logger.LogWarning(ex, "生成缩略图失败");
return string.Empty;
}
}
private string GetContentType(string extension)
{
return extension.ToLower() switch
{
".jpg" or ".jpeg" => "image/jpeg",
".png" => "image/png",
".gif" => "image/gif",
".webp" => "image/webp",
".mp4" => "video/mp4",
".avi" => "video/avi",
".mov" => "video/mov",
".wmv" => "video/wmv",
".mp3" => "audio/mpeg",
".wav" => "audio/wav",
".ogg" => "audio/ogg",
".pdf" => "application/pdf",
".doc" => "application/msword",
".docx" => "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
_ => "application/octet-stream"
};
}
private AttachmentType GetAttachmentType(string contentType)
{
if (_allowedImageTypes.Contains(contentType))
return AttachmentType.IMAGE;
if (_allowedVideoTypes.Contains(contentType))
return AttachmentType.VIDEO;
if (_allowedAudioTypes.Contains(contentType))
return AttachmentType.VOICE;
if (_allowedDocumentTypes.Contains(contentType))
return AttachmentType.DOCUMENT;
return AttachmentType.OTHER;
}
}
}

View File

@@ -0,0 +1,11 @@
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public interface IAIAssistantService
{
Task<ApiResponse<WritingAssistantResponseDto>> GetWritingAssistanceAsync(WritingAssistantRequestDto request);
Task<ApiResponse<SentimentAnalysisResponseDto>> AnalyzeSentimentAsync(SentimentAnalysisRequestDto request);
Task<ApiResponse<FuturePredictionResponseDto>> PredictFutureAsync(FuturePredictionRequestDto request);
}
}

View File

@@ -0,0 +1,12 @@
using Microsoft.AspNetCore.Http;
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public interface IFileUploadService
{
Task<ApiResponse<FileUploadResponseDto>> UploadFileAsync(IFormFile file, int userId, FileUploadRequestDto request);
Task<ApiResponse<bool>> DeleteFileAsync(string fileId, int userId);
Task<ApiResponse<FileUploadResponseDto>> GetFileAsync(string fileId, int userId);
}
}

View File

@@ -0,0 +1,21 @@
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
namespace FutureMailAPI.Services
{
public interface IMailService
{
Task<ApiResponse<SentMailResponseDto>> CreateMailAsync(int userId, SentMailCreateDto createDto);
Task<ApiResponse<SentMailResponseDto>> GetSentMailByIdAsync(int userId, int mailId);
Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetSentMailsAsync(int userId, MailListQueryDto queryDto);
Task<ApiResponse<SentMailResponseDto>> GetMailByIdAsync(int userId, int mailId);
Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetMailsAsync(int userId, MailListQueryDto queryDto);
Task<ApiResponse<SentMailResponseDto>> UpdateMailAsync(int userId, int mailId, SentMailUpdateDto updateDto);
Task<ApiResponse<bool>> DeleteMailAsync(int userId, int mailId);
Task<ApiResponse<PagedResponse<ReceivedMailResponseDto>>> GetReceivedMailsAsync(int userId, MailListQueryDto queryDto);
Task<ApiResponse<ReceivedMailResponseDto>> GetReceivedMailByIdAsync(int userId, int mailId);
Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId);
Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId);
Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId);
}
}

View File

@@ -0,0 +1,16 @@
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public interface INotificationService
{
Task<ApiResponse<NotificationDeviceResponseDto>> RegisterDeviceAsync(int userId, NotificationDeviceRequestDto request);
Task<ApiResponse<bool>> UnregisterDeviceAsync(int userId, string deviceId);
Task<ApiResponse<NotificationSettingsDto>> GetNotificationSettingsAsync(int userId);
Task<ApiResponse<bool>> UpdateNotificationSettingsAsync(int userId, NotificationSettingsDto settings);
Task<ApiResponse<NotificationListResponseDto>> GetNotificationsAsync(int userId, NotificationListQueryDto query);
Task<ApiResponse<bool>> MarkNotificationAsReadAsync(int userId, string notificationId);
Task<ApiResponse<bool>> MarkAllNotificationsAsReadAsync(int userId);
Task<ApiResponse<bool>> SendNotificationAsync(int userId, NotificationMessageDto notification);
}
}

View File

@@ -0,0 +1,19 @@
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
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);
}
}

View File

@@ -0,0 +1,12 @@
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public interface IPersonalSpaceService
{
Task<ApiResponse<TimelineResponseDto>> GetTimelineAsync(int userId, TimelineQueryDto query);
Task<ApiResponse<StatisticsResponseDto>> GetStatisticsAsync(int userId);
Task<ApiResponse<SubscriptionResponseDto>> GetSubscriptionAsync(int userId);
Task<ApiResponse<UserProfileResponseDto>> GetUserProfileAsync(int userId);
}
}

View File

@@ -0,0 +1,17 @@
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public interface ITimeCapsuleService
{
Task<ApiResponse<TimeCapsuleResponseDto>> CreateTimeCapsuleAsync(int userId, TimeCapsuleCreateDto createDto);
Task<ApiResponse<TimeCapsuleResponseDto>> GetTimeCapsuleByIdAsync(int userId, int capsuleId);
Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetTimeCapsulesAsync(int userId, TimeCapsuleListQueryDto queryDto);
Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleAsync(int userId, int capsuleId, TimeCapsuleUpdateDto updateDto);
Task<ApiResponse<bool>> DeleteTimeCapsuleAsync(int userId, int capsuleId);
Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetPublicTimeCapsulesAsync(TimeCapsuleListQueryDto queryDto);
Task<ApiResponse<TimeCapsuleResponseDto>> ClaimPublicCapsuleAsync(int userId, int capsuleId);
Task<ApiResponse<TimeCapsuleViewResponseDto>> GetTimeCapsuleViewAsync(int userId);
Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleStyleAsync(int userId, int capsuleId, TimeCapsuleStyleUpdateDto updateDto);
}
}

View File

@@ -0,0 +1,69 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using System.Text.Json;
namespace FutureMailAPI.Services
{
public interface IInitializationService
{
Task InitializeAsync();
}
public class InitializationService : IInitializationService
{
private readonly FutureMailDbContext _context;
private readonly ILogger<InitializationService> _logger;
public InitializationService(FutureMailDbContext context, ILogger<InitializationService> logger)
{
_context = context;
_logger = logger;
}
public async Task InitializeAsync()
{
// 确保数据库已创建
await _context.Database.EnsureCreatedAsync();
// 创建默认OAuth客户端如果不存在
await CreateDefaultOAuthClientAsync();
_logger.LogInformation("系统初始化完成");
}
private async Task CreateDefaultOAuthClientAsync()
{
var defaultClientId = "futuremail_default_client";
// 检查默认客户端是否已存在
var existingClient = await _context.OAuthClients
.FirstOrDefaultAsync(c => c.ClientId == defaultClientId);
if (existingClient != null)
{
_logger.LogInformation("默认OAuth客户端已存在");
return;
}
// 创建默认OAuth客户端
var defaultClient = new OAuthClient
{
ClientId = defaultClientId,
ClientSecret = "futuremail_default_secret",
Name = "FutureMail默认客户端",
RedirectUris = JsonSerializer.Serialize(new[] { "http://localhost:3000/callback", "http://localhost:8080/callback" }),
Scopes = JsonSerializer.Serialize(new[] { "read", "write" }),
IsActive = true,
CreatedAt = DateTime.UtcNow,
UpdatedAt = DateTime.UtcNow
};
_context.OAuthClients.Add(defaultClient);
await _context.SaveChangesAsync();
_logger.LogInformation("默认OAuth客户端创建成功");
}
}
}

View File

@@ -0,0 +1,475 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public class MailService : IMailService
{
private readonly FutureMailDbContext _context;
public MailService(FutureMailDbContext context)
{
_context = context;
}
public async Task<ApiResponse<SentMailResponseDto>> CreateMailAsync(int userId, SentMailCreateDto createDto)
{
// 检查投递时间是否在未来
if (createDto.DeliveryTime <= DateTime.UtcNow)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("投递时间必须是未来时间");
}
// 如果是指定用户,检查用户是否存在
int? recipientId = null;
if (createDto.RecipientType == 1 && !string.IsNullOrEmpty(createDto.RecipientEmail))
{
var recipient = await _context.Users
.FirstOrDefaultAsync(u => u.Email == createDto.RecipientEmail);
if (recipient == null)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("收件人不存在");
}
recipientId = recipient.Id;
}
// 创建邮件
var mail = new SentMail
{
Title = createDto.Title,
Content = createDto.Content,
SenderId = userId,
RecipientType = createDto.RecipientType,
RecipientId = recipientId,
DeliveryTime = createDto.DeliveryTime,
SentAt = DateTime.UtcNow, // 显式设置发送时间
Status = createDto.DeliveryTime > DateTime.UtcNow ? 1 : 0, // 根据投递时间直接设置状态
TriggerType = createDto.TriggerType,
TriggerDetails = createDto.TriggerDetails,
Attachments = createDto.Attachments,
IsEncrypted = createDto.IsEncrypted,
EncryptionKey = createDto.EncryptionKey,
Theme = createDto.Theme
};
_context.SentMails.Add(mail);
await _context.SaveChangesAsync();
// 如果是发送状态(不是草稿),创建时间胶囊
if (mail.Status == 1) // 已发送(待投递)
{
// 创建时间胶囊
var timeCapsule = new TimeCapsule
{
UserId = userId,
SentMailId = mail.Id,
PositionX = 0,
PositionY = 0,
PositionZ = 0,
Status = 1, // 漂浮中
Type = 0 // 普通
};
_context.TimeCapsules.Add(timeCapsule);
await _context.SaveChangesAsync();
}
var mailResponse = await GetSentMailWithDetailsAsync(mail.Id);
return ApiResponse<SentMailResponseDto>.SuccessResult(mailResponse, "邮件创建成功");
}
public async Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetSentMailsAsync(int userId, MailListQueryDto queryDto)
{
var query = _context.SentMails
.Where(m => m.SenderId == userId)
.Include(m => m.Sender)
.Include(m => m.Recipient)
.AsQueryable();
// 应用筛选条件
if (queryDto.Status.HasValue)
{
query = query.Where(m => m.Status == queryDto.Status.Value);
}
if (queryDto.RecipientType.HasValue)
{
query = query.Where(m => m.RecipientType == queryDto.RecipientType.Value);
}
if (!string.IsNullOrEmpty(queryDto.Keyword))
{
query = query.Where(m => m.Title.Contains(queryDto.Keyword) || m.Content.Contains(queryDto.Keyword));
}
if (queryDto.StartDate.HasValue)
{
query = query.Where(m => m.SentAt >= queryDto.StartDate.Value);
}
if (queryDto.EndDate.HasValue)
{
query = query.Where(m => m.SentAt <= queryDto.EndDate.Value);
}
// 排序
query = query.OrderByDescending(m => m.SentAt);
// 分页
var totalCount = await query.CountAsync();
var mails = await query
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
.Take(queryDto.PageSize)
.ToListAsync();
var mailDtos = mails.Select(MapToSentMailResponseDto).ToList();
var pagedResponse = new PagedResponse<SentMailResponseDto>(
mailDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
return ApiResponse<PagedResponse<SentMailResponseDto>>.SuccessResult(pagedResponse);
}
public async Task<ApiResponse<SentMailResponseDto>> GetSentMailByIdAsync(int userId, int mailId)
{
var mail = await _context.SentMails
.Include(m => m.Sender)
.Include(m => m.Recipient)
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
if (mail == null)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("邮件不存在");
}
var mailDto = MapToSentMailResponseDto(mail);
return ApiResponse<SentMailResponseDto>.SuccessResult(mailDto);
}
public async Task<ApiResponse<SentMailResponseDto>> UpdateMailAsync(int userId, int mailId, SentMailUpdateDto updateDto)
{
var mail = await _context.SentMails
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
if (mail == null)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("邮件不存在");
}
// 检查邮件是否已投递,已投递的邮件不能修改
if (mail.Status >= 2)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("已投递的邮件不能修改");
}
// 更新邮件信息
if (updateDto.Title != null)
{
mail.Title = updateDto.Title;
}
if (updateDto.Content != null)
{
mail.Content = updateDto.Content;
}
if (updateDto.DeliveryTime.HasValue)
{
if (updateDto.DeliveryTime.Value <= DateTime.UtcNow)
{
return ApiResponse<SentMailResponseDto>.ErrorResult("投递时间必须是未来时间");
}
mail.DeliveryTime = updateDto.DeliveryTime.Value;
}
if (updateDto.TriggerDetails != null)
{
mail.TriggerDetails = updateDto.TriggerDetails;
}
if (updateDto.Attachments != null)
{
mail.Attachments = updateDto.Attachments;
}
if (updateDto.Theme != null)
{
mail.Theme = updateDto.Theme;
}
await _context.SaveChangesAsync();
var mailResponse = await GetSentMailWithDetailsAsync(mail.Id);
return ApiResponse<SentMailResponseDto>.SuccessResult(mailResponse, "邮件更新成功");
}
public async Task<ApiResponse<bool>> DeleteMailAsync(int userId, int mailId)
{
var mail = await _context.SentMails
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
if (mail == null)
{
return ApiResponse<bool>.ErrorResult("邮件不存在");
}
// 检查邮件是否已投递,已投递的邮件不能删除
if (mail.Status >= 2)
{
return ApiResponse<bool>.ErrorResult("已投递的邮件不能删除");
}
// 删除相关的时间胶囊
var timeCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.SentMailId == mailId);
if (timeCapsule != null)
{
_context.TimeCapsules.Remove(timeCapsule);
}
// 删除邮件
_context.SentMails.Remove(mail);
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "邮件删除成功");
}
public async Task<ApiResponse<PagedResponse<ReceivedMailResponseDto>>> GetReceivedMailsAsync(int userId, MailListQueryDto queryDto)
{
var query = _context.ReceivedMails
.Where(r => r.RecipientId == userId)
.Include(r => r.SentMail)
.ThenInclude(m => m.Sender)
.AsQueryable();
// 应用筛选条件
if (queryDto.Status.HasValue)
{
if (queryDto.Status.Value == 0) // 未读
{
query = query.Where(r => !r.IsRead);
}
else if (queryDto.Status.Value == 1) // 已读
{
query = query.Where(r => r.IsRead);
}
}
if (!string.IsNullOrEmpty(queryDto.Keyword))
{
query = query.Where(r => r.SentMail.Title.Contains(queryDto.Keyword) || r.SentMail.Content.Contains(queryDto.Keyword));
}
if (queryDto.StartDate.HasValue)
{
query = query.Where(r => r.ReceivedAt >= queryDto.StartDate.Value);
}
if (queryDto.EndDate.HasValue)
{
query = query.Where(r => r.ReceivedAt <= queryDto.EndDate.Value);
}
// 排序
query = query.OrderByDescending(r => r.ReceivedAt);
// 分页
var totalCount = await query.CountAsync();
var receivedMails = await query
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
.Take(queryDto.PageSize)
.ToListAsync();
var mailDtos = receivedMails.Select(MapToReceivedMailResponseDto).ToList();
var pagedResponse = new PagedResponse<ReceivedMailResponseDto>(
mailDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
return ApiResponse<PagedResponse<ReceivedMailResponseDto>>.SuccessResult(pagedResponse);
}
public async Task<ApiResponse<ReceivedMailResponseDto>> GetReceivedMailByIdAsync(int userId, int mailId)
{
var receivedMail = await _context.ReceivedMails
.Include(r => r.SentMail)
.ThenInclude(m => m.Sender)
.FirstOrDefaultAsync(r => r.Id == mailId && r.RecipientId == userId);
if (receivedMail == null)
{
return ApiResponse<ReceivedMailResponseDto>.ErrorResult("邮件不存在");
}
var mailDto = MapToReceivedMailResponseDto(receivedMail);
return ApiResponse<ReceivedMailResponseDto>.SuccessResult(mailDto);
}
public async Task<ApiResponse<bool>> MarkReceivedMailAsReadAsync(int userId, int mailId)
{
var receivedMail = await _context.ReceivedMails
.FirstOrDefaultAsync(r => r.Id == mailId && r.RecipientId == userId);
if (receivedMail == null)
{
return ApiResponse<bool>.ErrorResult("邮件不存在");
}
if (!receivedMail.IsRead)
{
receivedMail.IsRead = true;
receivedMail.ReadAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
}
return ApiResponse<bool>.SuccessResult(true, "邮件已标记为已读");
}
public Task<ApiResponse<SentMailResponseDto>> GetMailByIdAsync(int userId, int mailId)
{
return GetSentMailByIdAsync(userId, mailId);
}
public Task<ApiResponse<PagedResponse<SentMailResponseDto>>> GetMailsAsync(int userId, MailListQueryDto queryDto)
{
return GetSentMailsAsync(userId, queryDto);
}
public Task<ApiResponse<bool>> MarkAsReadAsync(int userId, int mailId)
{
return MarkReceivedMailAsReadAsync(userId, mailId);
}
public async Task<ApiResponse<bool>> RevokeMailAsync(int userId, int mailId)
{
var mail = await _context.SentMails
.FirstOrDefaultAsync(m => m.Id == mailId && m.SenderId == userId);
if (mail == null)
{
return ApiResponse<bool>.ErrorResult("邮件不存在");
}
// 检查邮件是否已投递,已投递的邮件不能撤销
if (mail.Status >= 2)
{
return ApiResponse<bool>.ErrorResult("已投递的邮件不能撤销");
}
// 更新邮件状态为已撤销
mail.Status = 4; // 4-已撤销
await _context.SaveChangesAsync();
// 更新相关的时间胶囊状态
var timeCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.SentMailId == mailId);
if (timeCapsule != null)
{
timeCapsule.Status = 3; // 3-已撤销
await _context.SaveChangesAsync();
}
return ApiResponse<bool>.SuccessResult(true, "邮件已撤销");
}
private async Task<SentMailResponseDto> GetSentMailWithDetailsAsync(int mailId)
{
var mail = await _context.SentMails
.Include(m => m.Sender)
.Include(m => m.Recipient)
.FirstOrDefaultAsync(m => m.Id == mailId);
return MapToSentMailResponseDto(mail!);
}
private static SentMailResponseDto MapToSentMailResponseDto(SentMail mail)
{
return new SentMailResponseDto
{
Id = mail.Id,
Title = mail.Title,
Content = mail.Content,
SenderId = mail.SenderId,
SenderUsername = mail.Sender?.Username ?? "",
RecipientType = mail.RecipientType,
RecipientId = mail.RecipientId,
RecipientUsername = mail.Recipient?.Username ?? "",
SentAt = mail.SentAt,
DeliveryTime = mail.DeliveryTime,
Status = mail.Status,
StatusText = GetStatusText(mail.Status),
TriggerType = mail.TriggerType,
TriggerTypeText = GetTriggerTypeText(mail.TriggerType),
TriggerDetails = mail.TriggerDetails,
Attachments = mail.Attachments,
IsEncrypted = mail.IsEncrypted,
Theme = mail.Theme,
RecipientTypeText = GetRecipientTypeText(mail.RecipientType),
DaysUntilDelivery = (int)(mail.DeliveryTime - DateTime.UtcNow).TotalDays
};
}
private static ReceivedMailResponseDto MapToReceivedMailResponseDto(ReceivedMail receivedMail)
{
return new ReceivedMailResponseDto
{
Id = receivedMail.Id,
SentMailId = receivedMail.SentMailId,
Title = receivedMail.SentMail.Title,
Content = receivedMail.SentMail.Content,
SenderUsername = receivedMail.SentMail.Sender?.Username ?? "",
SentAt = receivedMail.SentMail.SentAt,
ReceivedAt = receivedMail.ReceivedAt,
IsRead = receivedMail.IsRead,
ReadAt = receivedMail.ReadAt,
IsReplied = receivedMail.IsReplied,
ReplyMailId = receivedMail.ReplyMailId,
Theme = receivedMail.SentMail.Theme
};
}
private static string GetStatusText(int status)
{
return status switch
{
0 => "草稿",
1 => "已发送(待投递)",
2 => "投递中",
3 => "已送达",
_ => "未知"
};
}
private static string GetRecipientTypeText(int recipientType)
{
return recipientType switch
{
0 => "自己",
1 => "指定用户",
2 => "公开时间胶囊",
_ => "未知"
};
}
private static string GetTriggerTypeText(int triggerType)
{
return triggerType switch
{
0 => "时间",
1 => "地点",
2 => "事件",
_ => "未知"
};
}
}
}

View File

@@ -0,0 +1,104 @@
using FutureMailAPI.DTOs;
using FutureMailAPI.Models;
namespace FutureMailAPI.Services
{
public class NotificationService : INotificationService
{
private readonly ILogger<NotificationService> _logger;
public NotificationService(ILogger<NotificationService> logger)
{
_logger = logger;
}
public Task<ApiResponse<NotificationDeviceResponseDto>> RegisterDeviceAsync(int userId, NotificationDeviceRequestDto request)
{
_logger.LogInformation($"Registering device for user {userId}");
var response = new NotificationDeviceResponseDto
{
DeviceId = "device_" + Guid.NewGuid().ToString("N"),
DeviceType = request.DeviceType,
IsActive = true,
RegisteredAt = DateTime.UtcNow
};
return Task.FromResult(ApiResponse<NotificationDeviceResponseDto>.SuccessResult(response));
}
public Task<ApiResponse<bool>> UnregisterDeviceAsync(int userId, string deviceId)
{
_logger.LogInformation($"Unregistering device {deviceId} for user {userId}");
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
}
public Task<ApiResponse<NotificationSettingsDto>> GetNotificationSettingsAsync(int userId)
{
_logger.LogInformation($"Getting notification settings for user {userId}");
var settings = new NotificationSettingsDto
{
EmailDelivery = true,
PushNotification = true,
InAppNotification = true,
DeliveryReminder = true,
ReceivedNotification = true,
SystemUpdates = false,
QuietHoursStart = "22:00",
QuietHoursEnd = "08:00",
EnableQuietHours = false
};
return Task.FromResult(ApiResponse<NotificationSettingsDto>.SuccessResult(settings));
}
public Task<ApiResponse<bool>> UpdateNotificationSettingsAsync(int userId, NotificationSettingsDto settings)
{
_logger.LogInformation($"Updating notification settings for user {userId}");
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
}
public Task<ApiResponse<NotificationListResponseDto>> GetNotificationsAsync(int userId, NotificationListQueryDto query)
{
_logger.LogInformation($"Getting notifications for user {userId}");
var notifications = new List<NotificationMessageDto>
{
new NotificationMessageDto
{
Id = "notif_1",
UserId = userId,
Title = "Test Notification",
Body = "This is a test notification",
Type = "Info",
RelatedEntityId = "mail_1",
IsRead = false,
CreatedAt = DateTime.UtcNow.AddDays(-1)
}
};
var response = new NotificationListResponseDto
{
Notifications = notifications,
Total = notifications.Count,
UnreadCount = 1,
Page = query.Page,
Size = query.Size
};
return Task.FromResult(ApiResponse<NotificationListResponseDto>.SuccessResult(response));
}
public Task<ApiResponse<bool>> MarkNotificationAsReadAsync(int userId, string notificationId)
{
_logger.LogInformation($"Marking notification {notificationId} as read for user {userId}");
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
}
public Task<ApiResponse<bool>> MarkAllNotificationsAsReadAsync(int userId)
{
_logger.LogInformation($"Marking all notifications as read for user {userId}");
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
}
public Task<ApiResponse<bool>> SendNotificationAsync(int userId, NotificationMessageDto notification)
{
_logger.LogInformation($"Sending notification to user {userId}");
return Task.FromResult(ApiResponse<bool>.SuccessResult(true));
}
}
}

View File

@@ -0,0 +1,416 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using FutureMailAPI.Helpers;
using System.Security.Cryptography;
using System.Text.Json;
namespace FutureMailAPI.Services
{
public class OAuthService : IOAuthService
{
private readonly FutureMailDbContext _context;
private readonly ILogger<OAuthService> _logger;
private readonly IPasswordHelper _passwordHelper;
public OAuthService(FutureMailDbContext context, ILogger<OAuthService> logger, IPasswordHelper passwordHelper)
{
_context = context;
_logger = logger;
_passwordHelper = passwordHelper;
}
public async Task<ApiResponse<OAuthClientSecretDto>> CreateClientAsync(int userId, OAuthClientCreateDto createDto)
{
var clientId = GenerateRandomString(32);
var clientSecret = GenerateRandomString(64);
var client = new OAuthClient
{
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
};
_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))
{
return ApiResponse<OAuthAuthorizationResponseDto>.ErrorResult($"无效的范围: {scope}");
}
}
// 生成授权码
var code = GenerateRandomString(64);
var authorizationCode = new OAuthAuthorizationCode
{
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);
}
public async Task<ApiResponse<OAuthTokenResponseDto>> ExchangeCodeForTokenAsync(OAuthTokenRequestDto request)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证授权码
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)
{
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)
{
// 验证客户端
var client = await GetClientByCredentialsAsync(request.ClientId, request.ClientSecret);
if (client == null)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的客户端凭据");
}
// 验证刷新令牌
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)
{
return ApiResponse<OAuthTokenResponseDto>.ErrorResult("无效的刷新令牌");
}
// 标记旧刷新令牌为已使用
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)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token);
if (accessToken == null)
{
return ApiResponse<bool>.ErrorResult("令牌不存在");
}
accessToken.IsRevoked = true;
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "令牌已撤销");
}
public async Task<ApiResponse<bool>> ValidateTokenAsync(string token)
{
var accessToken = await _context.OAuthAccessTokens
.FirstOrDefaultAsync(t => t.Token == token && !t.IsRevoked && t.ExpiresAt > DateTime.UtcNow);
if (accessToken == null)
{
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
};
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);
}
private string GenerateRandomString(int length)
{
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);
}
}
}

View File

@@ -0,0 +1,419 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using System.Text.Json;
namespace FutureMailAPI.Services
{
public class PersonalSpaceService : IPersonalSpaceService
{
private readonly FutureMailDbContext _context;
private readonly ILogger<PersonalSpaceService> _logger;
public PersonalSpaceService(FutureMailDbContext context, ILogger<PersonalSpaceService> logger)
{
_context = context;
_logger = logger;
}
public async Task<ApiResponse<TimelineResponseDto>> GetTimelineAsync(int userId, TimelineQueryDto query)
{
try
{
var response = new TimelineResponseDto();
var timelineDict = new Dictionary<string, TimelineDateDto>();
// 获取发送的邮件
if (query.Type == TimelineType.ALL || query.Type == TimelineType.SENT)
{
var sentMailsQuery = _context.SentMails
.Where(m => m.SenderId == userId)
.Include(m => m.Recipient)
.AsQueryable();
if (query.StartDate.HasValue)
{
sentMailsQuery = sentMailsQuery.Where(m => m.SentAt >= query.StartDate.Value);
}
if (query.EndDate.HasValue)
{
sentMailsQuery = sentMailsQuery.Where(m => m.SentAt <= query.EndDate.Value);
}
var sentMails = await sentMailsQuery
.OrderBy(m => m.SentAt)
.ToListAsync();
foreach (var mail in sentMails)
{
var dateKey = mail.SentAt.ToString("yyyy-MM-dd");
if (!timelineDict.ContainsKey(dateKey))
{
timelineDict[dateKey] = new TimelineDateDto
{
Date = dateKey,
Events = new List<TimelineEventDto>()
};
}
var recipientName = mail.Recipient?.Username ?? "自己";
var recipientAvatar = mail.Recipient?.Avatar;
timelineDict[dateKey].Events.Add(new TimelineEventDto
{
Type = TimelineEventType.SENT,
MailId = mail.Id,
Title = mail.Title,
Time = mail.SentAt.ToString("HH:mm"),
WithUser = new UserInfoDto
{
UserId = mail.RecipientId ?? 0,
Username = recipientName,
Avatar = recipientAvatar
},
Emotion = AnalyzeMailEmotion(mail.Content)
});
}
}
// 获取接收的邮件
if (query.Type == TimelineType.ALL || query.Type == TimelineType.RECEIVED)
{
var receivedMailsQuery = _context.ReceivedMails
.Where(r => r.RecipientId == userId)
.Include(r => r.SentMail)
.ThenInclude(m => m.Sender)
.AsQueryable();
if (query.StartDate.HasValue)
{
receivedMailsQuery = receivedMailsQuery.Where(r => r.ReceivedAt >= query.StartDate.Value);
}
if (query.EndDate.HasValue)
{
receivedMailsQuery = receivedMailsQuery.Where(r => r.ReceivedAt <= query.EndDate.Value);
}
var receivedMails = await receivedMailsQuery
.OrderBy(r => r.ReceivedAt)
.ToListAsync();
foreach (var receivedMail in receivedMails)
{
var dateKey = receivedMail.ReceivedAt.ToString("yyyy-MM-dd");
if (!timelineDict.ContainsKey(dateKey))
{
timelineDict[dateKey] = new TimelineDateDto
{
Date = dateKey,
Events = new List<TimelineEventDto>()
};
}
var senderName = receivedMail.SentMail.Sender.Username;
var senderAvatar = receivedMail.SentMail.Sender.Avatar;
timelineDict[dateKey].Events.Add(new TimelineEventDto
{
Type = TimelineEventType.RECEIVED,
MailId = receivedMail.SentMailId,
Title = receivedMail.SentMail.Title,
Time = receivedMail.ReceivedAt.ToString("HH:mm"),
WithUser = new UserInfoDto
{
UserId = receivedMail.SentMail.SenderId,
Username = senderName,
Avatar = senderAvatar
},
Emotion = AnalyzeMailEmotion(receivedMail.SentMail.Content)
});
}
}
// 对每个日期的事件按时间排序
foreach (var dateEntry in timelineDict)
{
dateEntry.Value.Events = dateEntry.Value.Events
.OrderBy(e => e.Time)
.ToList();
}
// 按日期排序
response.Timeline = timelineDict
.OrderBy(kvp => kvp.Key)
.Select(kvp => kvp.Value)
.ToList();
return ApiResponse<TimelineResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取时间线时发生错误");
return ApiResponse<TimelineResponseDto>.ErrorResult("获取时间线失败");
}
}
public async Task<ApiResponse<StatisticsResponseDto>> GetStatisticsAsync(int userId)
{
try
{
var response = new StatisticsResponseDto();
// 获取发送的邮件统计
var sentMails = await _context.SentMails
.Where(m => m.SenderId == userId)
.ToListAsync();
response.TotalSent = sentMails.Count;
// 获取接收的邮件统计
var receivedMails = await _context.ReceivedMails
.Where(r => r.RecipientId == userId)
.Include(r => r.SentMail)
.ThenInclude(m => m.Sender)
.ToListAsync();
response.TotalReceived = receivedMails.Count;
// 计算时间旅行时长(天)
if (sentMails.Any())
{
var earliestDelivery = sentMails.Min(m => m.DeliveryTime);
var latestDelivery = sentMails.Max(m => m.DeliveryTime);
response.TimeTravelDuration = (latestDelivery - earliestDelivery).Days;
}
// 找出最频繁的收件人
var recipientCounts = sentMails
.Where(m => m.RecipientId.HasValue)
.GroupBy(m => m.RecipientId)
.Select(g => new { RecipientId = g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count)
.FirstOrDefault();
if (recipientCounts != null)
{
var recipient = await _context.Users
.FirstOrDefaultAsync(u => u.Id == recipientCounts.RecipientId);
response.MostFrequentRecipient = recipient?.Username ?? "未知用户";
}
// 找出最常见的年份(投递年份)
if (sentMails.Any())
{
var yearCounts = sentMails
.GroupBy(m => m.DeliveryTime.Year)
.Select(g => new { Year = g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count)
.FirstOrDefault();
response.MostCommonYear = yearCounts?.Year ?? DateTime.Now.Year;
}
// 生成关键词云
var allContents = sentMails.Select(m => m.Content).ToList();
allContents.AddRange(receivedMails.Select(r => r.SentMail.Content));
response.KeywordCloud = GenerateKeywordCloud(allContents);
// 生成月度统计
response.MonthlyStats = GenerateMonthlyStats(sentMails, receivedMails);
return ApiResponse<StatisticsResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取统计数据时发生错误");
return ApiResponse<StatisticsResponseDto>.ErrorResult("获取统计数据失败");
}
}
public async Task<ApiResponse<SubscriptionResponseDto>> GetSubscriptionAsync(int userId)
{
try
{
// 在实际应用中,这里会从数据库或订阅服务中获取用户的订阅信息
// 目前我们使用模拟数据
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<SubscriptionResponseDto>.ErrorResult("用户不存在");
}
// 模拟订阅数据
var response = new SubscriptionResponseDto
{
Plan = SubscriptionPlan.FREE, // 默认为免费计划
RemainingMails = 10 - await _context.SentMails.CountAsync(m => m.SenderId == userId && m.SentAt.Month == DateTime.Now.Month),
MaxAttachmentSize = 5 * 1024 * 1024, // 5MB
Features = new SubscriptionFeaturesDto
{
AdvancedTriggers = false,
CustomCapsules = false,
AIAssistant = true,
UnlimitedStorage = false,
PriorityDelivery = false
},
ExpireDate = null // 免费计划没有过期时间
};
// 如果用户创建时间超过30天可以升级为高级用户模拟
if (user.CreatedAt.AddDays(30) < DateTime.UtcNow)
{
response.Plan = SubscriptionPlan.PREMIUM;
response.RemainingMails = 100;
response.MaxAttachmentSize = 50 * 1024 * 1024; // 50MB
response.Features.AdvancedTriggers = true;
response.Features.CustomCapsules = true;
response.Features.UnlimitedStorage = true;
response.ExpireDate = DateTime.UtcNow.AddMonths(1);
}
return ApiResponse<SubscriptionResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取订阅信息时发生错误");
return ApiResponse<SubscriptionResponseDto>.ErrorResult("获取订阅信息失败");
}
}
public async Task<ApiResponse<UserProfileResponseDto>> GetUserProfileAsync(int userId)
{
try
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<UserProfileResponseDto>.ErrorResult("用户不存在");
}
// 获取订阅信息
var subscriptionResult = await GetSubscriptionAsync(userId);
var response = new UserProfileResponseDto
{
Id = user.Id,
Username = user.Username,
Email = user.Email,
Nickname = user.Nickname,
Avatar = user.Avatar,
CreatedAt = user.CreatedAt,
LastLoginAt = user.LastLoginAt,
Subscription = subscriptionResult.Data ?? new SubscriptionResponseDto()
};
return ApiResponse<UserProfileResponseDto>.SuccessResult(response);
}
catch (Exception ex)
{
_logger.LogError(ex, "获取用户资料时发生错误");
return ApiResponse<UserProfileResponseDto>.ErrorResult("获取用户资料失败");
}
}
private string AnalyzeMailEmotion(string content)
{
// 简单的情感分析
var positiveKeywords = new[] { "开心", "快乐", "爱", "美好", "成功", "希望", "感谢", "幸福" };
var negativeKeywords = new[] { "悲伤", "难过", "失败", "痛苦", "失望", "愤怒", "焦虑", "恐惧" };
var positiveCount = positiveKeywords.Count(keyword => content.Contains(keyword));
var negativeCount = negativeKeywords.Count(keyword => content.Contains(keyword));
if (positiveCount > negativeCount)
return "积极";
else if (negativeCount > positiveCount)
return "消极";
else
return "中性";
}
private List<KeywordCloudDto> GenerateKeywordCloud(List<string> contents)
{
// 简单的关键词提取
var allWords = new List<string>();
foreach (var content in contents)
{
var words = content.Split(new[] { ' ', ',', '.', '!', '?', ';', ':', '', '。', '', '', '', '' }, StringSplitOptions.RemoveEmptyEntries);
allWords.AddRange(words.Where(word => word.Length > 3));
}
var wordCounts = allWords
.GroupBy(word => word)
.Select(g => new { Word = g.Key, Count = g.Count() })
.OrderByDescending(g => g.Count)
.Take(20)
.ToList();
var maxCount = wordCounts.FirstOrDefault()?.Count ?? 1;
return wordCounts.Select(w => new KeywordCloudDto
{
Word = w.Word,
Count = w.Count,
Size = (int)((double)w.Count / maxCount * 10) + 1 // 1-10的大小
}).ToList();
}
private List<MonthlyStatsDto> GenerateMonthlyStats(List<SentMail> sentMails, List<ReceivedMail> receivedMails)
{
var monthlyStats = new Dictionary<string, MonthlyStatsDto>();
// 处理发送的邮件
foreach (var mail in sentMails)
{
var monthKey = mail.SentAt.ToString("yyyy-MM");
if (!monthlyStats.ContainsKey(monthKey))
{
monthlyStats[monthKey] = new MonthlyStatsDto
{
Month = monthKey,
Sent = 0,
Received = 0
};
}
monthlyStats[monthKey].Sent++;
}
// 处理接收的邮件
foreach (var receivedMail in receivedMails)
{
var monthKey = receivedMail.ReceivedAt.ToString("yyyy-MM");
if (!monthlyStats.ContainsKey(monthKey))
{
monthlyStats[monthKey] = new MonthlyStatsDto
{
Month = monthKey,
Sent = 0,
Received = 0
};
}
monthlyStats[monthKey].Received++;
}
// 按月份排序只返回最近12个月的数据
return monthlyStats
.OrderBy(kvp => kvp.Key)
.TakeLast(12)
.Select(kvp => kvp.Value)
.ToList();
}
}
}

View File

@@ -0,0 +1,436 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
namespace FutureMailAPI.Services
{
public class TimeCapsuleService : ITimeCapsuleService
{
private readonly FutureMailDbContext _context;
public TimeCapsuleService(FutureMailDbContext context)
{
_context = context;
}
public async Task<ApiResponse<TimeCapsuleResponseDto>> CreateTimeCapsuleAsync(int userId, TimeCapsuleCreateDto createDto)
{
// 检查邮件是否存在且属于当前用户
var mail = await _context.SentMails
.FirstOrDefaultAsync(m => m.Id == createDto.SentMailId && m.SenderId == userId);
if (mail == null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("邮件不存在或无权限");
}
// 检查是否已存在时间胶囊
var existingCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.SentMailId == createDto.SentMailId);
if (existingCapsule != null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("该邮件已创建时间胶囊");
}
// 创建时间胶囊
var timeCapsule = new TimeCapsule
{
UserId = userId,
SentMailId = createDto.SentMailId,
PositionX = createDto.PositionX,
PositionY = createDto.PositionY,
PositionZ = createDto.PositionZ,
Size = createDto.Size,
Color = createDto.Color,
Opacity = createDto.Opacity,
Rotation = createDto.Rotation,
Status = 1, // 漂浮中
Type = createDto.Type
};
_context.TimeCapsules.Add(timeCapsule);
await _context.SaveChangesAsync();
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊创建成功");
}
public async Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetTimeCapsulesAsync(int userId, TimeCapsuleListQueryDto queryDto)
{
var query = _context.TimeCapsules
.Where(tc => tc.UserId == userId)
.Include(tc => tc.User)
.Include(tc => tc.SentMail)
.AsQueryable();
// 应用筛选条件
if (queryDto.Status.HasValue)
{
query = query.Where(tc => tc.Status == queryDto.Status.Value);
}
if (queryDto.Type.HasValue)
{
query = query.Where(tc => tc.Type == queryDto.Type.Value);
}
if (queryDto.IncludeExpired.HasValue && !queryDto.IncludeExpired.Value)
{
query = query.Where(tc => tc.SentMail.DeliveryTime > DateTime.UtcNow);
}
// 排序
query = query.OrderByDescending(tc => tc.CreatedAt);
// 分页
var totalCount = await query.CountAsync();
var capsules = await query
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
.Take(queryDto.PageSize)
.ToListAsync();
var capsuleDtos = capsules.Select(MapToTimeCapsuleResponseDto).ToList();
var pagedResponse = new PagedResponse<TimeCapsuleResponseDto>(
capsuleDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
return ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.SuccessResult(pagedResponse);
}
public async Task<ApiResponse<TimeCapsuleResponseDto>> GetTimeCapsuleByIdAsync(int userId, int capsuleId)
{
var timeCapsule = await _context.TimeCapsules
.Include(tc => tc.User)
.Include(tc => tc.SentMail)
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
if (timeCapsule == null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
}
var capsuleDto = MapToTimeCapsuleResponseDto(timeCapsule);
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleDto);
}
public async Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleAsync(int userId, int capsuleId, TimeCapsuleUpdateDto updateDto)
{
var timeCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
if (timeCapsule == null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
}
// 检查胶囊是否已开启,已开启的胶囊不能修改
if (timeCapsule.Status >= 3)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("已开启的时间胶囊不能修改");
}
// 更新胶囊信息
if (updateDto.PositionX.HasValue)
{
timeCapsule.PositionX = updateDto.PositionX.Value;
}
if (updateDto.PositionY.HasValue)
{
timeCapsule.PositionY = updateDto.PositionY.Value;
}
if (updateDto.PositionZ.HasValue)
{
timeCapsule.PositionZ = updateDto.PositionZ.Value;
}
if (updateDto.Size.HasValue)
{
timeCapsule.Size = updateDto.Size.Value;
}
if (updateDto.Color != null)
{
timeCapsule.Color = updateDto.Color;
}
if (updateDto.Opacity.HasValue)
{
timeCapsule.Opacity = updateDto.Opacity.Value;
}
if (updateDto.Rotation.HasValue)
{
timeCapsule.Rotation = updateDto.Rotation.Value;
}
await _context.SaveChangesAsync();
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊更新成功");
}
public async Task<ApiResponse<bool>> DeleteTimeCapsuleAsync(int userId, int capsuleId)
{
var timeCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
if (timeCapsule == null)
{
return ApiResponse<bool>.ErrorResult("时间胶囊不存在");
}
// 检查胶囊是否已开启,已开启的胶囊不能删除
if (timeCapsule.Status >= 3)
{
return ApiResponse<bool>.ErrorResult("已开启的时间胶囊不能删除");
}
// 删除时间胶囊
_context.TimeCapsules.Remove(timeCapsule);
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "时间胶囊删除成功");
}
public async Task<ApiResponse<PagedResponse<TimeCapsuleResponseDto>>> GetPublicTimeCapsulesAsync(TimeCapsuleListQueryDto queryDto)
{
var query = _context.TimeCapsules
.Where(tc => tc.SentMail.RecipientType == 2) // 公开时间胶囊
.Include(tc => tc.User)
.Include(tc => tc.SentMail)
.AsQueryable();
// 应用筛选条件
if (queryDto.Status.HasValue)
{
query = query.Where(tc => tc.Status == queryDto.Status.Value);
}
if (queryDto.Type.HasValue)
{
query = query.Where(tc => tc.Type == queryDto.Type.Value);
}
if (queryDto.IncludeExpired.HasValue && !queryDto.IncludeExpired.Value)
{
query = query.Where(tc => tc.SentMail.DeliveryTime > DateTime.UtcNow);
}
// 排序
query = query.OrderByDescending(tc => tc.CreatedAt);
// 分页
var totalCount = await query.CountAsync();
var capsules = await query
.Skip((queryDto.PageIndex - 1) * queryDto.PageSize)
.Take(queryDto.PageSize)
.ToListAsync();
var capsuleDtos = capsules.Select(MapToTimeCapsuleResponseDto).ToList();
var pagedResponse = new PagedResponse<TimeCapsuleResponseDto>(
capsuleDtos, queryDto.PageIndex, queryDto.PageSize, totalCount);
return ApiResponse<PagedResponse<TimeCapsuleResponseDto>>.SuccessResult(pagedResponse);
}
public async Task<ApiResponse<TimeCapsuleResponseDto>> ClaimPublicCapsuleAsync(int userId, int capsuleId)
{
var timeCapsule = await _context.TimeCapsules
.Include(tc => tc.SentMail)
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.SentMail.RecipientType == 2);
if (timeCapsule == null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在或不是公开胶囊");
}
// 检查胶囊是否已开启
if (timeCapsule.Status >= 3)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊已开启");
}
// 检查是否已认领
var existingReceivedMail = await _context.ReceivedMails
.FirstOrDefaultAsync(rm => rm.SentMailId == timeCapsule.SentMailId && rm.RecipientId == userId);
if (existingReceivedMail != null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("您已认领此时间胶囊");
}
// 创建接收邮件记录
var receivedMail = new ReceivedMail
{
SentMailId = timeCapsule.SentMailId,
RecipientId = userId,
ReceivedAt = DateTime.UtcNow
};
_context.ReceivedMails.Add(receivedMail);
await _context.SaveChangesAsync();
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊认领成功");
}
public async Task<ApiResponse<TimeCapsuleViewResponseDto>> GetTimeCapsuleViewAsync(int userId)
{
// 获取用户的所有时间胶囊
var capsules = await _context.TimeCapsules
.Where(tc => tc.UserId == userId)
.Include(tc => tc.SentMail)
.ToListAsync();
// 转换为视图DTO
var capsuleViews = capsules.Select(capsule =>
{
var totalDays = (capsule.SentMail.DeliveryTime - capsule.SentMail.SentAt).TotalDays;
var elapsedDays = (DateTime.UtcNow - capsule.SentMail.SentAt).TotalDays;
var progress = totalDays > 0 ? Math.Min(1, elapsedDays / totalDays) : 0;
return new TimeCapsuleViewDto
{
CapsuleId = capsule.Id,
MailId = capsule.SentMailId,
Title = capsule.SentMail.Title,
SendTime = capsule.SentMail.SentAt,
DeliveryTime = capsule.SentMail.DeliveryTime,
Progress = progress,
Position = new TimeCapsulePosition
{
X = capsule.PositionX,
Y = capsule.PositionY,
Z = capsule.PositionZ
},
Style = capsule.Color ?? "#FFFFFF",
GlowIntensity = capsule.Status == 1 ? 0.8 : 0.3 // 漂浮中的胶囊发光更亮
};
}).ToList();
// 获取用户偏好设置(如果有)
var user = await _context.Users.FindAsync(userId);
var scene = user?.PreferredScene ?? "SPACE";
var background = user?.PreferredBackground ?? "default";
var viewResponse = new TimeCapsuleViewResponseDto
{
Capsules = capsuleViews,
Scene = scene,
Background = background
};
return ApiResponse<TimeCapsuleViewResponseDto>.SuccessResult(viewResponse);
}
public async Task<ApiResponse<TimeCapsuleResponseDto>> UpdateTimeCapsuleStyleAsync(int userId, int capsuleId, TimeCapsuleStyleUpdateDto updateDto)
{
var timeCapsule = await _context.TimeCapsules
.FirstOrDefaultAsync(tc => tc.Id == capsuleId && tc.UserId == userId);
if (timeCapsule == null)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("时间胶囊不存在");
}
// 检查胶囊是否已开启,已开启的胶囊不能修改
if (timeCapsule.Status >= 3)
{
return ApiResponse<TimeCapsuleResponseDto>.ErrorResult("已开启的时间胶囊不能修改");
}
// 更新胶囊样式
timeCapsule.Color = updateDto.Style;
if (updateDto.GlowIntensity.HasValue)
{
// 将发光强度转换为透明度(反向关系)
timeCapsule.Opacity = 1.0 - (updateDto.GlowIntensity.Value * 0.5);
}
await _context.SaveChangesAsync();
var capsuleResponse = await GetTimeCapsuleWithDetailsAsync(timeCapsule.Id);
return ApiResponse<TimeCapsuleResponseDto>.SuccessResult(capsuleResponse, "时间胶囊样式更新成功");
}
private async Task<TimeCapsuleResponseDto> GetTimeCapsuleWithDetailsAsync(int capsuleId)
{
var timeCapsule = await _context.TimeCapsules
.Include(tc => tc.User)
.Include(tc => tc.SentMail)
.FirstOrDefaultAsync(tc => tc.Id == capsuleId);
return MapToTimeCapsuleResponseDto(timeCapsule!);
}
private static TimeCapsuleResponseDto MapToTimeCapsuleResponseDto(TimeCapsule timeCapsule)
{
var daysUntilDelivery = (int)(timeCapsule.SentMail.DeliveryTime - DateTime.UtcNow).TotalDays;
var distanceFromNow = CalculateDistanceFromNow(daysUntilDelivery);
return new TimeCapsuleResponseDto
{
Id = timeCapsule.Id,
UserId = timeCapsule.UserId,
SentMailId = timeCapsule.SentMailId,
MailTitle = timeCapsule.SentMail.Title,
PositionX = timeCapsule.PositionX,
PositionY = timeCapsule.PositionY,
PositionZ = timeCapsule.PositionZ,
Size = timeCapsule.Size,
Color = timeCapsule.Color,
Opacity = timeCapsule.Opacity,
Rotation = timeCapsule.Rotation,
Status = timeCapsule.Status,
StatusText = GetStatusText(timeCapsule.Status),
Type = timeCapsule.Type,
TypeText = GetTypeText(timeCapsule.Type),
CreatedAt = timeCapsule.CreatedAt,
DeliveryTime = timeCapsule.SentMail.DeliveryTime,
DaysUntilDelivery = daysUntilDelivery,
DistanceFromNow = distanceFromNow
};
}
private static string GetStatusText(int status)
{
return status switch
{
0 => "未激活",
1 => "漂浮中",
2 => "即将到达",
3 => "已开启",
_ => "未知"
};
}
private static string GetTypeText(int type)
{
return type switch
{
0 => "普通",
1 => "特殊",
2 => "限时",
_ => "未知"
};
}
private static double CalculateDistanceFromNow(int daysUntilDelivery)
{
// 简单的距离计算,可以根据需要调整
// 距离"现在"越远,距离值越大
return Math.Max(1, daysUntilDelivery / 30.0); // 以月为单位
}
}
}

View File

@@ -0,0 +1,298 @@
using Microsoft.EntityFrameworkCore;
using FutureMailAPI.Data;
using FutureMailAPI.Models;
using FutureMailAPI.DTOs;
using FutureMailAPI.Helpers;
namespace FutureMailAPI.Services
{
public interface IUserService
{
Task<ApiResponse<UserResponseDto>> RegisterAsync(UserRegisterDto registerDto);
Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto);
Task<ApiResponse<UserResponseDto>> GetUserByIdAsync(int userId);
Task<ApiResponse<UserResponseDto>> GetUserByUsernameAsync(string username);
Task<ApiResponse<UserResponseDto>> GetUserByEmailAsync(string email);
Task<ApiResponse<UserResponseDto>> GetUserByUsernameOrEmailAsync(string usernameOrEmail);
Task<ApiResponse<UserResponseDto>> UpdateUserAsync(int userId, UserUpdateDto updateDto);
Task<ApiResponse<bool>> ChangePasswordAsync(int userId, ChangePasswordDto changePasswordDto);
Task<ApiResponse<UserResponseDto>> CreateUserAsync(UserRegisterDto registerDto);
}
public class UserService : IUserService
{
private readonly FutureMailDbContext _context;
private readonly IPasswordHelper _passwordHelper;
public UserService(FutureMailDbContext context, IPasswordHelper passwordHelper)
{
_context = context;
_passwordHelper = passwordHelper;
}
public async Task<ApiResponse<UserResponseDto>> RegisterAsync(UserRegisterDto registerDto)
{
// 检查用户名是否已存在
var existingUserByUsername = await _context.Users
.FirstOrDefaultAsync(u => u.Username == registerDto.Username);
if (existingUserByUsername != null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户名已存在");
}
// 检查邮箱是否已存在
var existingUserByEmail = await _context.Users
.FirstOrDefaultAsync(u => u.Email == registerDto.Email);
if (existingUserByEmail != null)
{
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
var user = new User
{
Username = registerDto.Username,
Email = registerDto.Email,
PasswordHash = _passwordHelper.HashPassword(registerDto.Password, salt),
Salt = salt,
Nickname = registerDto.Nickname ?? registerDto.Username,
CreatedAt = DateTime.UtcNow
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "注册成功");
}
public async Task<ApiResponse<AuthResponseDto>> LoginAsync(UserLoginDto loginDto)
{
// 查找用户(通过用户名或邮箱)
User? user;
if (loginDto.UsernameOrEmail.Contains("@"))
{
user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == loginDto.UsernameOrEmail);
}
else
{
user = await _context.Users
.FirstOrDefaultAsync(u => u.Username == loginDto.UsernameOrEmail);
}
if (user == null)
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
// 验证密码
if (!_passwordHelper.VerifyPassword(loginDto.Password, user.PasswordHash))
{
return ApiResponse<AuthResponseDto>.ErrorResult("用户名或密码错误");
}
// 更新最后登录时间
user.LastLoginAt = DateTime.UtcNow;
await _context.SaveChangesAsync();
// 注意这里不再生成JWT令牌因为我们将使用OAuth 2.0
// 在OAuth 2.0流程中令牌是通过OAuth端点生成的
var authResponse = new AuthResponseDto
{
Token = "", // 临时空字符串实际使用OAuth 2.0令牌
Expires = DateTime.UtcNow.AddDays(7),
User = MapToUserResponseDto(user)
};
return ApiResponse<AuthResponseDto>.SuccessResult(authResponse, "登录成功");
}
public async Task<ApiResponse<UserResponseDto>> GetUserByIdAsync(int userId)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
}
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
}
public async Task<ApiResponse<UserResponseDto>> GetUserByUsernameAsync(string username)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Username == username);
if (user == null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
}
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
}
public async Task<ApiResponse<UserResponseDto>> GetUserByEmailAsync(string email)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == email);
if (user == null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
}
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
}
public async Task<ApiResponse<UserResponseDto>> GetUserByUsernameOrEmailAsync(string usernameOrEmail)
{
User? user;
if (usernameOrEmail.Contains("@"))
{
user = await _context.Users
.FirstOrDefaultAsync(u => u.Email == usernameOrEmail);
}
else
{
user = await _context.Users
.FirstOrDefaultAsync(u => u.Username == usernameOrEmail);
}
if (user == null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
}
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto);
}
public async Task<ApiResponse<UserResponseDto>> UpdateUserAsync(int userId, UserUpdateDto updateDto)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户不存在");
}
// 更新用户信息
if (updateDto.Nickname != null)
{
user.Nickname = updateDto.Nickname;
}
if (updateDto.Avatar != null)
{
user.Avatar = updateDto.Avatar;
}
await _context.SaveChangesAsync();
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "更新成功");
}
public async Task<ApiResponse<bool>> ChangePasswordAsync(int userId, ChangePasswordDto changePasswordDto)
{
var user = await _context.Users
.FirstOrDefaultAsync(u => u.Id == userId);
if (user == null)
{
return ApiResponse<bool>.ErrorResult("用户不存在");
}
// 验证当前密码
if (!_passwordHelper.VerifyPassword(changePasswordDto.CurrentPassword, user.PasswordHash))
{
return ApiResponse<bool>.ErrorResult("当前密码错误");
}
// 更新密码
var salt = _passwordHelper.GenerateSalt();
user.PasswordHash = _passwordHelper.HashPassword(changePasswordDto.NewPassword, salt);
user.Salt = salt;
await _context.SaveChangesAsync();
return ApiResponse<bool>.SuccessResult(true, "密码修改成功");
}
public async Task<ApiResponse<UserResponseDto>> CreateUserAsync(UserRegisterDto registerDto)
{
// 检查用户名是否已存在
var existingUserByUsername = await _context.Users
.FirstOrDefaultAsync(u => u.Username == registerDto.Username);
if (existingUserByUsername != null)
{
return ApiResponse<UserResponseDto>.ErrorResult("用户名已存在");
}
// 检查邮箱是否已存在
var existingUserByEmail = await _context.Users
.FirstOrDefaultAsync(u => u.Email == registerDto.Email);
if (existingUserByEmail != null)
{
return ApiResponse<UserResponseDto>.ErrorResult("邮箱已被注册");
}
// 生成随机盐值
var salt = _passwordHelper.GenerateSalt();
// 创建新用户
var user = new User
{
Username = registerDto.Username,
Email = registerDto.Email,
PasswordHash = _passwordHelper.HashPassword(registerDto.Password, salt),
Salt = salt,
Nickname = registerDto.Nickname ?? registerDto.Username,
CreatedAt = DateTime.UtcNow
};
_context.Users.Add(user);
await _context.SaveChangesAsync();
var userDto = MapToUserResponseDto(user);
return ApiResponse<UserResponseDto>.SuccessResult(userDto, "用户创建成功");
}
private static UserResponseDto MapToUserResponseDto(User user)
{
return new UserResponseDto
{
Id = user.Id,
Username = user.Username,
Email = user.Email,
Nickname = user.Nickname,
Avatar = user.Avatar,
CreatedAt = user.CreatedAt,
LastLoginAt = user.LastLoginAt
};
}
}
}