初始化
This commit is contained in:
307
FutureMailAPI/Services/AIAssistantService.cs
Normal file
307
FutureMailAPI/Services/AIAssistantService.cs
Normal 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> { "保持积极态度" }
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
168
FutureMailAPI/Services/AuthService.cs
Normal file
168
FutureMailAPI/Services/AuthService.cs
Normal 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, "令牌刷新成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
319
FutureMailAPI/Services/FileUploadService.cs
Normal file
319
FutureMailAPI/Services/FileUploadService.cs
Normal 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
11
FutureMailAPI/Services/IAIAssistantService.cs
Normal file
11
FutureMailAPI/Services/IAIAssistantService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
FutureMailAPI/Services/IFileUploadService.cs
Normal file
12
FutureMailAPI/Services/IFileUploadService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
21
FutureMailAPI/Services/IMailService.cs
Normal file
21
FutureMailAPI/Services/IMailService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
16
FutureMailAPI/Services/INotificationService.cs
Normal file
16
FutureMailAPI/Services/INotificationService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
19
FutureMailAPI/Services/IOAuthService.cs
Normal file
19
FutureMailAPI/Services/IOAuthService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
12
FutureMailAPI/Services/IPersonalSpaceService.cs
Normal file
12
FutureMailAPI/Services/IPersonalSpaceService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
17
FutureMailAPI/Services/ITimeCapsuleService.cs
Normal file
17
FutureMailAPI/Services/ITimeCapsuleService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
69
FutureMailAPI/Services/InitializationService.cs
Normal file
69
FutureMailAPI/Services/InitializationService.cs
Normal 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客户端创建成功");
|
||||
}
|
||||
}
|
||||
}
|
||||
475
FutureMailAPI/Services/MailService.cs
Normal file
475
FutureMailAPI/Services/MailService.cs
Normal 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 => "事件",
|
||||
_ => "未知"
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
104
FutureMailAPI/Services/NotificationService.cs
Normal file
104
FutureMailAPI/Services/NotificationService.cs
Normal 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));
|
||||
}
|
||||
}
|
||||
}
|
||||
416
FutureMailAPI/Services/OAuthService.cs
Normal file
416
FutureMailAPI/Services/OAuthService.cs
Normal 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
419
FutureMailAPI/Services/PersonalSpaceService.cs
Normal file
419
FutureMailAPI/Services/PersonalSpaceService.cs
Normal 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();
|
||||
}
|
||||
}
|
||||
}
|
||||
436
FutureMailAPI/Services/TimeCapsuleService.cs
Normal file
436
FutureMailAPI/Services/TimeCapsuleService.cs
Normal 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); // 以月为单位
|
||||
}
|
||||
}
|
||||
}
|
||||
298
FutureMailAPI/Services/UserService.cs
Normal file
298
FutureMailAPI/Services/UserService.cs
Normal 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
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user