diff --git a/client/docs/rss-mirror-feature.md b/client/docs/rss-mirror-feature.md index a537f4bc..9aca0ec5 100644 --- a/client/docs/rss-mirror-feature.md +++ b/client/docs/rss-mirror-feature.md @@ -20,7 +20,7 @@ ## 功能概述 -RSS镜像站点前端提供了两个主要页面,用于管理RSS源和查看RSS镜像条目。 +RSS镜像站点前端提供了三个主要页面,用于管理RSS源、查看RSS镜像条目和分词统计。 ### 页面列表 @@ -33,10 +33,15 @@ RSS镜像站点前端提供了两个主要页面,用于管理RSS源和查看RS 2. **RSS镜像条目** (`/download-subscription/rss-mirror-items`) - 查看所有RSS镜像条目 - 多条件筛选和搜索 - - 查看分词统计 - 下载到Aria2 - 批量删除 +3. **分词统计** (`/download-subscription/rss-word-segments`) + - 全部分词统计查看(支持分页) + - 分词详情列表查看 + - 按分词精确筛选 + - 直接下载到Aria2 + ### 核心特性 - ✅ 完全响应式设计,适配各种屏幕尺寸 @@ -88,17 +93,20 @@ DFApp.Vue/ ├── src/ │ ├── api/ │ │ ├── rssSource.ts # RSS源API封装 -│ │ └── rssMirror.ts # RSS镜像条目API封装 +│ │ ├── rssMirror.ts # RSS镜像条目API封装 +│ │ └── rssWordSegment.ts # 分词统计API封装 │ │ │ ├── types/ │ │ └── business.ts # 业务类型定义(包含RSS相关类型) │ │ │ ├── views/ -│ │ └── rss-mirror/ -│ │ ├── sources/ -│ │ │ └── index.vue # RSS源管理页面 -│ │ └── items/ -│ │ └── index.vue # RSS镜像条目页面 +│ │ ├── rss-mirror/ +│ │ │ ├── sources/ +│ │ │ │ └── index.vue # RSS源管理页面 +│ │ │ └── items/ +│ │ │ └── index.vue # RSS镜像条目页面 +│ │ └── rss/ +│ │ └── word-segments.vue # 分词统计页面 │ │ │ └── router/ │ └── modules/ @@ -126,23 +134,30 @@ DFApp.Vue/ - RSS镜像条目API封装 - 包含查询、删除、统计、下载等操作 +3. **`/src/api/rssWordSegment.ts`** + - 分词统计API封装 + - 包含分词列表查询、统计查询等操作 + #### 类型层 -3. **`/src/types/business.ts`** (扩展) +4. **`/src/types/business.ts`** (扩展) - 添加了RSS相关的TypeScript类型定义 #### 页面组件 -4. **`/src/views/rss-mirror/sources/index.vue`** +5. **`/src/views/rss-mirror/sources/index.vue`** - RSS源管理页面组件 -5. **`/src/views/rss-mirror/items/index.vue`** +6. **`/src/views/rss-mirror/items/index.vue`** - RSS镜像条目页面组件 +7. **`/src/views/rss/word-segments.vue`** + - 分词统计页面组件 + #### 路由配置 -6. **`/src/router/modules/download-subscription.ts`** (修改) - - 添加了两个新路由 +8. **`/src/router/modules/download-subscription.ts`** (修改) + - 添加了RSS镜像相关路由 --- @@ -1206,6 +1221,14 @@ export default { title: "RSS镜像条目" } }, + { + path: "/download-subscription/rss-word-segments", + name: "RssWordSegments", + component: () => import("@/views/rss/word-segments.vue"), + meta: { + title: "分词统计" + } + }, { path: "/download-subscription/filterKeyword", name: "FilterKeyword", @@ -1220,10 +1243,11 @@ export default { ### 路由说明 -**新增路由**: +**RSS镜像相关路由**: 1. `/download-subscription/rss-sources` - RSS源管理 2. `/download-subscription/rss-mirror-items` - RSS镜像条目 +3. `/download-subscription/rss-word-segments` - 分词统计 **路由元信息**: @@ -2108,7 +2132,6 @@ onMounted(() => { ### 项目文档 - [后端文档](/home/df/dfapp/DFApp/docs/rss-mirror-feature.md) -- [ABP Framework文档](https://docs.abp.io/) ### 工具推荐 @@ -2120,6 +2143,14 @@ onMounted(() => { ## 版本历史 +### v1.1.0 (2026-05-10) +**文档更新** + +- 新增分词统计页面文档 +- 更新路由配置(新增 rss-word-segments 路由) +- 更新项目结构(新增 word-segments.vue) +- 移除过时的 ABP Framework 引用 + ### v1.0.0 (2026-01-14) **初始版本** @@ -2149,7 +2180,7 @@ onMounted(() => { ## 作者信息 **开发日期**: 2026-01-14 -**版本**: 1.0.0 +**版本**: 1.1.0 **框架**: Vue 3 + TypeScript + Element Plus **AI助手**: Claude (Anthropic) diff --git a/client/src/api/rssSubscription.ts b/client/src/api/rssSubscription.ts deleted file mode 100644 index 0c5351f7..00000000 --- a/client/src/api/rssSubscription.ts +++ /dev/null @@ -1,62 +0,0 @@ -import { http } from "@/utils/http"; -import type { PagedResultDto } from "@/types/api"; -import type { - RssSubscriptionDto, - CreateUpdateRssSubscriptionDto, - GetRssSubscriptionsRequestDto -} from "@/types/business"; - -class RssSubscriptionApi { - private baseUrl = "/api/app/rss-subscription"; - - /** - * 获取订阅列表 - */ - async getList( - params?: GetRssSubscriptionsRequestDto - ): Promise> { - return http.get(this.baseUrl, { params }); - } - - /** - * 获取订阅详情 - */ - async get(id: number): Promise { - return http.get(`${this.baseUrl}/${id}`); - } - - /** - * 创建订阅 - */ - async create( - input: CreateUpdateRssSubscriptionDto - ): Promise { - return http.post(this.baseUrl, { data: input }); - } - - /** - * 更新订阅 - */ - async update( - id: number, - input: CreateUpdateRssSubscriptionDto - ): Promise { - return http.put(`${this.baseUrl}/${id}`, { data: input }); - } - - /** - * 删除订阅 - */ - async delete(id: number): Promise { - return http.request("delete", `${this.baseUrl}/${id}`); - } - - /** - * 启用/禁用订阅 - */ - async toggleEnable(id: number): Promise { - return http.post(`${this.baseUrl}/${id}/toggle-enable`); - } -} - -export const rssSubscriptionApi = new RssSubscriptionApi(); diff --git a/client/src/api/rssSubscriptionDownload.ts b/client/src/api/rssSubscriptionDownload.ts deleted file mode 100644 index 6c858370..00000000 --- a/client/src/api/rssSubscriptionDownload.ts +++ /dev/null @@ -1,56 +0,0 @@ -import { http } from "@/utils/http"; -import type { PagedResultDto } from "@/types/api"; -import type { - RssSubscriptionDownloadDto, - GetRssSubscriptionDownloadsRequestDto -} from "@/types/business"; - -class RssSubscriptionDownloadApi { - private baseUrl = "/api/app/rss-subscription-download"; - - /** - * 获取下载记录列表 - */ - async getList( - params?: GetRssSubscriptionDownloadsRequestDto - ): Promise> { - return http.get(this.baseUrl, { params }); - } - - /** - * 获取下载记录详情 - */ - async get(id: number): Promise { - return http.get(`${this.baseUrl}/${id}`); - } - - /** - * 删除下载记录 - */ - async delete(id: number): Promise { - return http.request("delete", `${this.baseUrl}/${id}`); - } - - /** - * 批量删除下载记录 - */ - async deleteMany(ids: number[]): Promise { - return http.request("delete", `${this.baseUrl}/many`, { data: ids }); - } - - /** - * 清空所有下载记录 - */ - async clearAll(): Promise { - return http.request("delete", `${this.baseUrl}/clear-all`); - } - - /** - * 重试下载 - */ - async retry(id: number): Promise { - return http.post(`${this.baseUrl}/${id}/retry`); - } -} - -export const rssSubscriptionDownloadApi = new RssSubscriptionDownloadApi(); diff --git a/client/src/router/modules/download-subscription.ts b/client/src/router/modules/download-subscription.ts index 95e8c0ca..8adb0bc5 100644 --- a/client/src/router/modules/download-subscription.ts +++ b/client/src/router/modules/download-subscription.ts @@ -63,22 +63,6 @@ export default { meta: { title: "关键词过滤管理" } - }, - { - path: "/download-subscription/rss-subscriptions", - name: "RssSubscriptions", - component: () => import("@/views/rss/subscription/index.vue"), - meta: { - title: "RSS订阅管理" - } - }, - { - path: "/download-subscription/rss-subscription-downloads", - name: "RssSubscriptionDownloads", - component: () => import("@/views/rss/subscription/downloads.vue"), - meta: { - title: "订阅下载记录" - } } ] } satisfies RouteConfigsTable; diff --git a/client/src/types/business.ts b/client/src/types/business.ts index 50ecbd24..9c92a9ff 100644 --- a/client/src/types/business.ts +++ b/client/src/types/business.ts @@ -793,90 +793,3 @@ export interface GetRssWordSegmentsRequestDto extends PagedRequestDto { word?: string; } -// RSS订阅模块类型 - -// RSS订阅DTO -export interface RssSubscriptionDto { - id: number; - name: string; - keywords: string; - isEnabled: boolean; - minSeeders?: number; - maxSeeders?: number; - minLeechers?: number; - maxLeechers?: number; - minDownloads?: number; - maxDownloads?: number; - qualityFilter?: string; - subtitleGroupFilter?: string; - autoDownload: boolean; - videoOnly: boolean; - enableKeywordFilter: boolean; - savePath?: string; - rssSourceId?: number; - rssSourceName?: string; - startDate?: string; - endDate?: string; - remark?: string; - creationTime: string; - lastModificationTime?: string; -} - -// 创建/更新RSS订阅DTO -export interface CreateUpdateRssSubscriptionDto { - name: string; - keywords: string; - isEnabled: boolean; - minSeeders?: number; - maxSeeders?: number; - minLeechers?: number; - maxLeechers?: number; - minDownloads?: number; - maxDownloads?: number; - qualityFilter?: string; - subtitleGroupFilter?: string; - autoDownload: boolean; - videoOnly: boolean; - enableKeywordFilter: boolean; - savePath?: string; - rssSourceId?: number; - startDate?: string; - endDate?: string; - remark?: string; -} - -// 获取RSS订阅请求DTO -export interface GetRssSubscriptionsRequestDto extends PagedRequestDto { - filter?: string; - isEnabled?: boolean; - rssSourceId?: number; -} - -// RSS订阅下载记录DTO -export interface RssSubscriptionDownloadDto { - id: number; - subscriptionId: number; - subscriptionName?: string; - rssMirrorItemId: number; - rssMirrorItemTitle?: string; - rssMirrorItemLink?: string; - rssSourceName?: string; - aria2Gid: string; - downloadStatus: number; - downloadStatusText?: string; - isPendingDueToLowDiskSpace: boolean; - errorMessage?: string; - downloadStartTime?: string; - downloadCompleteTime?: string; - creationTime: string; -} - -// 获取RSS订阅下载记录请求DTO -export interface GetRssSubscriptionDownloadsRequestDto extends PagedRequestDto { - subscriptionId?: number; - rssMirrorItemId?: number; - downloadStatus?: number; - filter?: string; - startTime?: string; - endTime?: string; -} diff --git a/client/src/views/rss/subscription/downloads.vue b/client/src/views/rss/subscription/downloads.vue deleted file mode 100644 index f50b6cca..00000000 --- a/client/src/views/rss/subscription/downloads.vue +++ /dev/null @@ -1,443 +0,0 @@ - - - - - diff --git a/client/src/views/rss/subscription/index.vue b/client/src/views/rss/subscription/index.vue deleted file mode 100644 index 042adc61..00000000 --- a/client/src/views/rss/subscription/index.vue +++ /dev/null @@ -1,599 +0,0 @@ - - - - - diff --git a/src/DFApp.Web/Background/Aria2BackgroundWorker.cs b/src/DFApp.Web/Background/Aria2BackgroundWorker.cs index b2e39845..f484c06e 100644 --- a/src/DFApp.Web/Background/Aria2BackgroundWorker.cs +++ b/src/DFApp.Web/Background/Aria2BackgroundWorker.cs @@ -1,6 +1,4 @@ using System; -using System.Collections.Generic; -using System.Linq; using System.Net.WebSockets; using System.Text; using System.Text.Json; @@ -8,12 +6,8 @@ using System.Threading.Tasks; using DFApp.Aria2; using DFApp.Aria2.Notifications; -using DFApp.Aria2.Request; -using DFApp.Aria2.Response.TellStatus; -using DFApp.Rss; using DFApp.Web.Data; using DFApp.Web.Data.Configuration; -using DFApp.Web.Queue; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Hosting; using Microsoft.Extensions.Logging; @@ -21,25 +15,21 @@ namespace DFApp.Web.Background; /// -/// Aria2 后台处理服务,通过 WebSocket 连接 Aria2 服务 -/// 接收下载通知、处理 RPC 响应、发送队列中的请求 +/// Aria2 后台处理服务,通过 WebSocket 连接 Aria2 服务接收下载通知 /// public class Aria2BackgroundWorker : BackgroundService { private readonly Aria2Manager _manager; private readonly IServiceProvider _serviceProvider; - private readonly IQueueManagement _queueManagement; private readonly ILogger _logger; public Aria2BackgroundWorker( Aria2Manager manager, IServiceProvider serviceProvider, - IQueueManagement queueManagement, ILogger logger) { _manager = manager; _serviceProvider = serviceProvider; - _queueManagement = queueManagement; _logger = logger; } @@ -69,10 +59,7 @@ protected override async Task ExecuteAsync(CancellationToken stoppingToken) await clientWebSocket.ConnectAsync(new Uri(aria2ws), stoppingToken); _logger.LogInformation("已连接到 Aria2 WebSocket: {Url}", aria2ws); - var receiveTask = ReceiveMessagesAsync(clientWebSocket, stoppingToken); - var sendTask = SendQueuedRequestsAsync(clientWebSocket, stoppingToken); - - await Task.WhenAll(receiveTask, sendTask); + await ReceiveMessagesAsync(clientWebSocket, stoppingToken); _logger.LogInformation("Aria2 WebSocket 连接已断开,5秒后重试"); } @@ -121,43 +108,7 @@ private async Task ReceiveMessagesAsync(ClientWebSocket ws, CancellationToken ct } /// - /// 从队列中获取请求并发送到 Aria2 - /// - private async Task SendQueuedRequestsAsync(ClientWebSocket ws, CancellationToken ct) - { - while (!ct.IsCancellationRequested && ws.State == WebSocketState.Open) - { - try - { - var items = _queueManagement.GetQueueValue("Aria2RequestQueue"); - if (items != null && items.Count > 0) - { - foreach (var item in items) - { - string json = JsonSerializer.Serialize(item); - var bytes = Encoding.UTF8.GetBytes(json); - if (ws.State != WebSocketState.Open) break; - await ws.SendAsync(new ArraySegment(bytes), WebSocketMessageType.Text, true, ct); - _logger.LogDebug("已发送 Aria2 请求: {Method}, Id: {Id}", item.Method, item.Id); - } - _queueManagement.ClearQueue("Aria2RequestQueue"); - } - - await Task.Delay(500, ct); - } - catch (OperationCanceledException) - { - break; - } - catch (Exception ex) - { - _logger.LogError(ex, "发送 Aria2 请求异常"); - } - } - } - - /// - /// 处理接收到的 WebSocket 消息 + /// 处理接收到的 WebSocket 通知消息 /// private async Task ProcessMessageAsync(string message) { @@ -171,44 +122,12 @@ private async Task ProcessMessageAsync(string message) _logger.LogDebug("Aria2 消息: {Message}", message); - ResponseBase? dto; - if (message.Contains("\"id\":") && !message.Contains("\"error\":")) - { - // 请求响应(如 TellStatus 响应) - dto = JsonSerializer.Deserialize(message); - } - else if (message.Contains("\"method\":")) + if (message.Contains("\"method\":")) { - // 通知事件 var notification = JsonSerializer.Deserialize(message); - dto = notification; - - // 下载完成时更新 RSS 订阅下载记录 - if (notification != null && notification.Method == Aria2Consts.OnDownloadComplete) - { - await UpdateDownloadRecordStatusAsync(notification); - } - } - else - { - return; - } - - if (dto != null) - { - var requests = await _manager.ProcessResponseAsync(dto); - if (requests != null && requests.Count > 0) + if (notification != null) { - // 将 Aria2Request 转换为 Aria2RequestDto 并加入发送队列 - var dtos = requests.Select(r => new Aria2RequestDto - { - JSONRPC = r.JSONRPC, - Method = r.Method, - Id = r.Id, - Params = new List(r.Params) - }).ToList(); - - _queueManagement.AddQueueValue("Aria2RequestQueue", dtos); + await _manager.ProcessResponseAsync(notification); } } } @@ -217,31 +136,4 @@ private async Task ProcessMessageAsync(string message) _logger.LogError(ex, "处理 Aria2 消息异常"); } } - - /// - /// 下载完成时更新 RSS 订阅下载记录状态 - /// - private async Task UpdateDownloadRecordStatusAsync(Aria2Notification notification) - { - if (notification.Params == null || notification.Params.Count == 0) return; - - string gid = notification.Params[0].GID; - if (string.IsNullOrEmpty(gid)) return; - - using var scope = _serviceProvider.CreateScope(); - var repository = scope.ServiceProvider.GetRequiredService>(); - - var downloads = await repository.GetListAsync(d => d.Aria2Gid == gid); - foreach (var download in downloads) - { - download.DownloadStatus = 2; - download.DownloadCompleteTime = DateTime.Now; - await repository.UpdateAsync(download); - } - - if (downloads.Count > 0) - { - _logger.LogInformation("更新订阅下载记录状态: {Gid} -> 完成,共 {Count} 条", gid, downloads.Count); - } - } } diff --git a/src/DFApp.Web/Background/DiskSpaceCheckJob.cs b/src/DFApp.Web/Background/DiskSpaceCheckJob.cs deleted file mode 100644 index 4710a411..00000000 --- a/src/DFApp.Web/Background/DiskSpaceCheckJob.cs +++ /dev/null @@ -1,42 +0,0 @@ -using DFApp.Rss; -using DFApp.Web.Services.Rss; -using Microsoft.Extensions.Logging; -using Quartz; -using System; -using System.Threading.Tasks; - -namespace DFApp.Web.Background -{ - /// - /// 磁盘空间检查定时任务,处理因空间不足而暂存的下载任务 - /// - public class DiskSpaceCheckJob : IJob - { - private readonly IRssSubscriptionService _rssSubscriptionService; - private readonly ILogger _logger; - - public DiskSpaceCheckJob( - IRssSubscriptionService rssSubscriptionService, - ILogger logger) - { - _rssSubscriptionService = rssSubscriptionService; - _logger = logger; - } - - public async Task Execute(IJobExecutionContext context) - { - _logger.LogInformation("开始执行磁盘空间检查任务"); - - try - { - await _rssSubscriptionService.ProcessPendingDownloadsAsync(); - - _logger.LogInformation("磁盘空间检查任务完成"); - } - catch (Exception ex) - { - _logger.LogError(ex, "磁盘空间检查任务执行失败"); - } - } - } -} diff --git a/src/DFApp.Web/Background/RssMirrorFetchJob.cs b/src/DFApp.Web/Background/RssMirrorFetchJob.cs index 9c575e8d..7f110a5a 100644 --- a/src/DFApp.Web/Background/RssMirrorFetchJob.cs +++ b/src/DFApp.Web/Background/RssMirrorFetchJob.cs @@ -16,17 +16,15 @@ namespace DFApp.Web.Background { /// - /// RSS镜像抓取定时任务,定期从启用的RSS源抓取数据并处理订阅匹配 + /// RSS镜像抓取定时任务,定期从启用的RSS源抓取数据 /// public class RssMirrorFetchJob : IJob { private readonly ISqlSugarRepository _rssSourceRepository; private readonly ISqlSugarRepository _rssMirrorItemRepository; private readonly ISqlSugarRepository _rssWordSegmentRepository; - private readonly ISqlSugarReadOnlyRepository _rssSubscriptionRepository; private readonly IWordSegmentService _wordSegmentService; private readonly ISqlSugarClient _db; - private readonly IRssSubscriptionService _rssSubscriptionService; private readonly ILogger _logger; /// @@ -41,19 +39,15 @@ public RssMirrorFetchJob( ISqlSugarRepository rssSourceRepository, ISqlSugarRepository rssMirrorItemRepository, ISqlSugarRepository rssWordSegmentRepository, - ISqlSugarReadOnlyRepository rssSubscriptionRepository, IWordSegmentService wordSegmentService, ISqlSugarClient db, - IRssSubscriptionService rssSubscriptionService, ILogger logger) { _rssSourceRepository = rssSourceRepository; _rssMirrorItemRepository = rssMirrorItemRepository; _rssWordSegmentRepository = rssWordSegmentRepository; - _rssSubscriptionRepository = rssSubscriptionRepository; _wordSegmentService = wordSegmentService; _db = db; - _rssSubscriptionService = rssSubscriptionService; _logger = logger; } @@ -195,9 +189,6 @@ private async Task FetchRssSource(RssSource source) _db.Ado.CommitTran(); _logger.LogInformation("RSS源 {Name} 抓取完成,新增 {Count} 条记录", source.Name, newItemCount); - - // 提交事务后处理订阅 - await ProcessSubscriptionsAsync(newItems); } catch { @@ -316,33 +307,5 @@ private List ParseRssXml(string xmlContent, RssSource source) return items; } - /// - /// 处理订阅匹配,对新增条目进行关键词匹配和自动下载 - /// - private async Task ProcessSubscriptionsAsync(List newItems) - { - var enabledSubscriptions = await _rssSubscriptionRepository.GetListAsync(s => s.IsEnabled); - - if (!enabledSubscriptions.Any()) - { - return; - } - - foreach (var item in newItems) - { - var matchResults = await _rssSubscriptionService.MatchSubscriptionsAsync(item); - - foreach (var matchResult in matchResults.Where(r => r.Matched)) - { - var subscription = enabledSubscriptions.FirstOrDefault(s => s.Id == matchResult.SubscriptionId); - if (subscription != null && subscription.AutoDownload) - { - await _rssSubscriptionService.CreateDownloadTaskAsync(matchResult.SubscriptionId, item.Id); - _logger.LogInformation("订阅 {SubscriptionName} 匹配并自动下载: {Title}", - subscription.Name, item.Title); - } - } - } - } } } diff --git a/src/DFApp.Web/Controllers/RssSubscriptionController.cs b/src/DFApp.Web/Controllers/RssSubscriptionController.cs deleted file mode 100644 index 8d064c9f..00000000 --- a/src/DFApp.Web/Controllers/RssSubscriptionController.cs +++ /dev/null @@ -1,109 +0,0 @@ -using System.Threading.Tasks; -using DFApp.Web.Data; -using DFApp.Web.DTOs.Rss; -using DFApp.Web.Infrastructure; -using DFApp.Web.Permissions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using RssSubscriptionAppService = DFApp.Web.Services.Rss.RssSubscriptionAppService; - -namespace DFApp.Web.Controllers; - -/// -/// RSS订阅管理控制器,提供RSS订阅的增删改查及启用/禁用切换功能 -/// -[ApiController] -[Route("api/app/rss-subscription")] -[Authorize] -public class RssSubscriptionController : DFAppControllerBase -{ - private readonly RssSubscriptionAppService _rssSubscriptionAppService; - - /// - /// 构造函数 - /// - /// RSS订阅管理服务 - /// 当前用户 - /// 权限检查器 - public RssSubscriptionController( - RssSubscriptionAppService rssSubscriptionAppService, - ICurrentUser currentUser, - IPermissionChecker permissionChecker) - : base(currentUser, permissionChecker) - { - _rssSubscriptionAppService = rssSubscriptionAppService; - } - - /// - /// 获取RSS订阅分页列表 - /// - /// 查询请求 - [HttpGet] - [Permission(DFAppPermissions.RssSubscription.Default)] - public async Task GetListAsync([FromQuery] GetRssSubscriptionsRequestDto input) - { - var result = await _rssSubscriptionAppService.GetListAsync(input); - return Success(result); - } - - /// - /// 根据ID获取RSS订阅详情 - /// - /// 订阅ID - [HttpGet("{id:long}")] - [Permission(DFAppPermissions.RssSubscription.Default)] - public async Task GetAsync([FromRoute] long id) - { - var result = await _rssSubscriptionAppService.GetAsync(id); - return Success(result); - } - - /// - /// 创建RSS订阅 - /// - /// 创建订阅请求 - [HttpPost] - [Permission(DFAppPermissions.RssSubscription.Create)] - public async Task CreateAsync([FromBody] CreateUpdateRssSubscriptionDto input) - { - var result = await _rssSubscriptionAppService.CreateAsync(input); - return Success(result); - } - - /// - /// 更新RSS订阅 - /// - /// 订阅ID - /// 更新订阅请求 - [HttpPut("{id:long}")] - [Permission(DFAppPermissions.RssSubscription.Update)] - public async Task UpdateAsync([FromRoute] long id, [FromBody] CreateUpdateRssSubscriptionDto input) - { - var result = await _rssSubscriptionAppService.UpdateAsync(id, input); - return Success(result); - } - - /// - /// 删除RSS订阅 - /// - /// 订阅ID - [HttpDelete("{id:long}")] - [Permission(DFAppPermissions.RssSubscription.Delete)] - public async Task DeleteAsync([FromRoute] long id) - { - await _rssSubscriptionAppService.DeleteAsync(id); - return Success(); - } - - /// - /// 切换订阅启用状态 - /// - /// 订阅ID - [HttpPost("{id:long}/toggle-enable")] - [Permission(DFAppPermissions.RssSubscription.Update)] - public async Task ToggleEnableAsync([FromRoute] long id) - { - await _rssSubscriptionAppService.ToggleEnableAsync(id); - return Success(); - } -} diff --git a/src/DFApp.Web/Controllers/RssSubscriptionDownloadController.cs b/src/DFApp.Web/Controllers/RssSubscriptionDownloadController.cs deleted file mode 100644 index dfb816ba..00000000 --- a/src/DFApp.Web/Controllers/RssSubscriptionDownloadController.cs +++ /dev/null @@ -1,108 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using DFApp.Web.Data; -using DFApp.Web.DTOs.Rss; -using DFApp.Web.Infrastructure; -using DFApp.Web.Permissions; -using Microsoft.AspNetCore.Authorization; -using Microsoft.AspNetCore.Mvc; -using RssSubscriptionDownloadAppService = DFApp.Web.Services.Rss.RssSubscriptionDownloadAppService; - -namespace DFApp.Web.Controllers; - -/// -/// RSS订阅下载记录管理控制器,提供下载记录的查询、删除、清空及重试功能 -/// -[ApiController] -[Route("api/app/rss-subscription-download")] -[Authorize] -public class RssSubscriptionDownloadController : DFAppControllerBase -{ - private readonly RssSubscriptionDownloadAppService _rssSubscriptionDownloadAppService; - - /// - /// 构造函数 - /// - /// RSS订阅下载记录管理服务 - /// 当前用户 - /// 权限检查器 - public RssSubscriptionDownloadController( - RssSubscriptionDownloadAppService rssSubscriptionDownloadAppService, - ICurrentUser currentUser, - IPermissionChecker permissionChecker) - : base(currentUser, permissionChecker) - { - _rssSubscriptionDownloadAppService = rssSubscriptionDownloadAppService; - } - - /// - /// 获取下载记录分页列表 - /// - /// 查询请求 - [HttpGet] - [Permission(DFAppPermissions.RssSubscription.Download)] - public async Task GetListAsync([FromQuery] GetRssSubscriptionDownloadsRequestDto input) - { - var result = await _rssSubscriptionDownloadAppService.GetListAsync(input); - return Success(result); - } - - /// - /// 根据ID获取下载记录详情 - /// - /// 下载记录ID - [HttpGet("{id:long}")] - [Permission(DFAppPermissions.RssSubscription.Download)] - public async Task GetAsync([FromRoute] long id) - { - var result = await _rssSubscriptionDownloadAppService.GetAsync(id); - return Success(result); - } - - /// - /// 删除下载记录 - /// - /// 下载记录ID - [HttpDelete("{id:long}")] - [Permission(DFAppPermissions.RssSubscription.Delete)] - public async Task DeleteAsync([FromRoute] long id) - { - await _rssSubscriptionDownloadAppService.DeleteAsync(id); - return Success(); - } - - /// - /// 批量删除下载记录 - /// - /// 下载记录ID列表 - [HttpDelete("many")] - [Permission(DFAppPermissions.RssSubscription.Delete)] - public async Task DeleteManyAsync([FromBody] List ids) - { - await _rssSubscriptionDownloadAppService.DeleteManyAsync(ids); - return Success(); - } - - /// - /// 清空所有下载记录 - /// - [HttpDelete("clear-all")] - [Permission(DFAppPermissions.RssSubscription.Delete)] - public async Task ClearAllAsync() - { - await _rssSubscriptionDownloadAppService.ClearAllAsync(); - return Success(); - } - - /// - /// 重试下载 - /// - /// 下载记录ID - [HttpPost("{id:long}/retry")] - [Permission(DFAppPermissions.RssSubscription.Download)] - public async Task RetryAsync([FromRoute] long id) - { - await _rssSubscriptionDownloadAppService.RetryAsync(id); - return Success(); - } -} diff --git a/src/DFApp.Web/DTOs/Aria2/Aria2RequestDto.cs b/src/DFApp.Web/DTOs/Aria2/Aria2RequestDto.cs deleted file mode 100644 index 6669cc78..00000000 --- a/src/DFApp.Web/DTOs/Aria2/Aria2RequestDto.cs +++ /dev/null @@ -1,24 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Text.Json.Serialization; -using System.Threading.Tasks; - -namespace DFApp.Web.DTOs.Aria2 -{ - public class Aria2RequestDto - { - [JsonPropertyName("jsonrpc")] - public string JSONRPC { get; set; } - - [JsonPropertyName("method")] - public string Method { get; set; } - - [JsonPropertyName("id")] - public string Id { get; set; } - - [JsonPropertyName("params")] - public IList Params { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/CreateUpdateRssSubscriptionDto.cs b/src/DFApp.Web/DTOs/Rss/CreateUpdateRssSubscriptionDto.cs deleted file mode 100644 index b4ea8dba..00000000 --- a/src/DFApp.Web/DTOs/Rss/CreateUpdateRssSubscriptionDto.cs +++ /dev/null @@ -1,105 +0,0 @@ -using System; - -namespace DFApp.Web.DTOs.Rss -{ - /// - /// 创建/更新RSS订阅DTO - /// - public class CreateUpdateRssSubscriptionDto - { - /// - /// 订阅名称 - /// - public string Name { get; set; } = string.Empty; - - /// - /// 关键词 - /// - public string Keywords { get; set; } = string.Empty; - - /// - /// 是否启用 - /// - public bool IsEnabled { get; set; } = true; - - /// - /// 最小做种者数量 - /// - public int? MinSeeders { get; set; } - - /// - /// 最大做种者数量 - /// - public int? MaxSeeders { get; set; } - - /// - /// 最小下载者数量 - /// - public int? MinLeechers { get; set; } - - /// - /// 最大下载者数量 - /// - public int? MaxLeechers { get; set; } - - /// - /// 最小完成下载数量 - /// - public int? MinDownloads { get; set; } - - /// - /// 最大完成下载数量 - /// - public int? MaxDownloads { get; set; } - - /// - /// 质量过滤器 - /// - public string? QualityFilter { get; set; } - - /// - /// 字幕组过滤器 - /// - public string? SubtitleGroupFilter { get; set; } - - /// - /// 是否自动下载 - /// - public bool AutoDownload { get; set; } = true; - - /// - /// 是否仅视频 - /// - public bool VideoOnly { get; set; } - - /// - /// 是否启用关键词过滤 - /// - public bool EnableKeywordFilter { get; set; } - - /// - /// 保存路径 - /// - public string? SavePath { get; set; } - - /// - /// RSS源ID - /// - public long? RssSourceId { get; set; } - - /// - /// 开始日期 - /// - public DateTime? StartDate { get; set; } - - /// - /// 结束日期 - /// - public DateTime? EndDate { get; set; } - - /// - /// 备注 - /// - public string? Remark { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionDownloadsRequestDto.cs b/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionDownloadsRequestDto.cs deleted file mode 100644 index 5b769d0b..00000000 --- a/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionDownloadsRequestDto.cs +++ /dev/null @@ -1,41 +0,0 @@ -using System; -using DFApp.Web.DTOs; - -namespace DFApp.Web.DTOs.Rss -{ - /// - /// 查询RSS订阅下载请求DTO - /// - public class GetRssSubscriptionDownloadsRequestDto : PagedAndSortedResultRequestDto - { - /// - /// 订阅ID - /// - public long? SubscriptionId { get; set; } - - /// - /// RSS镜像条目ID - /// - public long? RssMirrorItemId { get; set; } - - /// - /// 下载状态 - /// - public int? DownloadStatus { get; set; } - - /// - /// 关键词过滤 - /// - public string? Filter { get; set; } - - /// - /// 开始时间 - /// - public DateTime? StartTime { get; set; } - - /// - /// 结束时间 - /// - public DateTime? EndTime { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionsRequestDto.cs b/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionsRequestDto.cs deleted file mode 100644 index 07217327..00000000 --- a/src/DFApp.Web/DTOs/Rss/GetRssSubscriptionsRequestDto.cs +++ /dev/null @@ -1,25 +0,0 @@ -using DFApp.Web.DTOs; - -namespace DFApp.Web.DTOs.Rss -{ - /// - /// 查询RSS订阅请求DTO - /// - public class GetRssSubscriptionsRequestDto : PagedAndSortedResultRequestDto - { - /// - /// 关键词过滤 - /// - public string? Filter { get; set; } - - /// - /// 是否启用 - /// - public bool? IsEnabled { get; set; } - - /// - /// RSS源ID - /// - public long? RssSourceId { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/RssSubscriptionDownloadDto.cs b/src/DFApp.Web/DTOs/Rss/RssSubscriptionDownloadDto.cs deleted file mode 100644 index 5c8fc533..00000000 --- a/src/DFApp.Web/DTOs/Rss/RssSubscriptionDownloadDto.cs +++ /dev/null @@ -1,81 +0,0 @@ -using System; -using DFApp.Web.DTOs; - -namespace DFApp.Web.DTOs.Rss -{ - /// - /// RSS订阅下载DTO - /// - public class RssSubscriptionDownloadDto : EntityDto - { - /// - /// 订阅ID - /// - public long SubscriptionId { get; set; } - - /// - /// 订阅名称 - /// - public string? SubscriptionName { get; set; } - - /// - /// RSS镜像条目ID - /// - public long RssMirrorItemId { get; set; } - - /// - /// RSS镜像条目标题 - /// - public string? RssMirrorItemTitle { get; set; } - - /// - /// RSS镜像条目链接 - /// - public string? RssMirrorItemLink { get; set; } - - /// - /// RSS源名称 - /// - public string? RssSourceName { get; set; } - - /// - /// Aria2任务ID - /// - public string Aria2Gid { get; set; } = string.Empty; - - /// - /// 下载状态(0=未开始,1=下载中,2=已完成,3=失败) - /// - public int DownloadStatus { get; set; } - - /// - /// 下载状态文本 - /// - public string? DownloadStatusText { get; set; } - - /// - /// 是否因磁盘空间不足而暂存 - /// - public bool IsPendingDueToLowDiskSpace { get; set; } - - /// - /// 错误信息 - /// - public string? ErrorMessage { get; set; } - - /// - /// 下载开始时间 - /// - public DateTime? DownloadStartTime { get; set; } - - /// - /// 下载完成时间 - /// - public DateTime? DownloadCompleteTime { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreationTime { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/RssSubscriptionDto.cs b/src/DFApp.Web/DTOs/Rss/RssSubscriptionDto.cs deleted file mode 100644 index 53da8d56..00000000 --- a/src/DFApp.Web/DTOs/Rss/RssSubscriptionDto.cs +++ /dev/null @@ -1,121 +0,0 @@ -using System; -using DFApp.Web.DTOs; - -namespace DFApp.Web.DTOs.Rss -{ - /// - /// RSS订阅DTO - /// - public class RssSubscriptionDto : EntityDto - { - /// - /// 订阅名称 - /// - public string Name { get; set; } = string.Empty; - - /// - /// 关键词 - /// - public string Keywords { get; set; } = string.Empty; - - /// - /// 是否启用 - /// - public bool IsEnabled { get; set; } - - /// - /// 最小做种者数量 - /// - public int? MinSeeders { get; set; } - - /// - /// 最大做种者数量 - /// - public int? MaxSeeders { get; set; } - - /// - /// 最小下载者数量 - /// - public int? MinLeechers { get; set; } - - /// - /// 最大下载者数量 - /// - public int? MaxLeechers { get; set; } - - /// - /// 最小完成下载数量 - /// - public int? MinDownloads { get; set; } - - /// - /// 最大完成下载数量 - /// - public int? MaxDownloads { get; set; } - - /// - /// 质量过滤器 - /// - public string? QualityFilter { get; set; } - - /// - /// 字幕组过滤器 - /// - public string? SubtitleGroupFilter { get; set; } - - /// - /// 是否自动下载 - /// - public bool AutoDownload { get; set; } - - /// - /// 是否仅视频 - /// - public bool VideoOnly { get; set; } - - /// - /// 是否启用关键词过滤 - /// - public bool EnableKeywordFilter { get; set; } - - /// - /// 保存路径 - /// - public string? SavePath { get; set; } - - /// - /// RSS源ID - /// - public long? RssSourceId { get; set; } - - /// - /// RSS源名称 - /// - public string? RssSourceName { get; set; } - - /// - /// 开始日期 - /// - public DateTime? StartDate { get; set; } - - /// - /// 结束日期 - /// - public DateTime? EndDate { get; set; } - - /// - /// 备注 - /// - public string? Remark { get; set; } - - /// - /// 创建时间 - /// - public DateTime CreationTime { get; set; } - - /// - /// 最后修改时间 - /// - public DateTime? LastModificationTime { get; set; } - } -} diff --git a/src/DFApp.Web/DTOs/Rss/RssSubscriptionMatchResultDto.cs b/src/DFApp.Web/DTOs/Rss/RssSubscriptionMatchResultDto.cs deleted file mode 100644 index 8b4900ee..00000000 --- a/src/DFApp.Web/DTOs/Rss/RssSubscriptionMatchResultDto.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace DFApp.Web.DTOs.Rss -{ - /// - /// RSS订阅匹配结果DTO - /// - public class RssSubscriptionMatchResultDto - { - /// - /// 订阅ID - /// - public long SubscriptionId { get; set; } - - /// - /// 订阅名称 - /// - public string SubscriptionName { get; set; } = string.Empty; - - /// - /// 是否匹配 - /// - public bool Matched { get; set; } - - /// - /// 匹配原因 - /// - public string MatchReason { get; set; } = string.Empty; - } -} diff --git a/src/DFApp.Web/Domain/Aria2/Aria2Consts.cs b/src/DFApp.Web/Domain/Aria2/Aria2Consts.cs index 8611adad..afdf1540 100644 --- a/src/DFApp.Web/Domain/Aria2/Aria2Consts.cs +++ b/src/DFApp.Web/Domain/Aria2/Aria2Consts.cs @@ -218,21 +218,4 @@ public static class Aria2Consts /// 列出所有通知 /// public const string ListNotifications = "system.listNotifications"; - - private static string? _aria2RequestId; - - /// - /// Aria2 请求 ID - /// - public static string Aria2RequestId - { - get - { - if (string.IsNullOrWhiteSpace(_aria2RequestId)) - { - _aria2RequestId = Guid.NewGuid().ToString(); - } - return _aria2RequestId; - } - } } diff --git a/src/DFApp.Web/Domain/Aria2/Request/Aria2Request.cs b/src/DFApp.Web/Domain/Aria2/Request/Aria2Request.cs deleted file mode 100644 index 5365cf8b..00000000 --- a/src/DFApp.Web/Domain/Aria2/Request/Aria2Request.cs +++ /dev/null @@ -1,38 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace DFApp.Aria2.Request; - -/// -/// Aria2 RPC 请求 -/// -public class Aria2Request -{ - [JsonPropertyName("jsonrpc")] - public string JSONRPC { get; set; } = "2.0"; - - [JsonPropertyName("method")] - public string Method { get; set; } = string.Empty; - - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - [JsonPropertyName("params")] - public IList Params { get; set; } = new List(); - - /// - /// 构造函数,自动添加 token 到 Params[0] - /// - /// 请求 ID - /// RPC 密钥 - public Aria2Request(string id, string? secret = null) - { - Id = id; - JSONRPC = "2.0"; - Params = new List(); - if (!string.IsNullOrEmpty(secret)) - { - Params.Add($"token:{secret}"); - } - } -} diff --git a/src/DFApp.Web/Domain/Aria2/Request/Aria2RequestDto.cs b/src/DFApp.Web/Domain/Aria2/Request/Aria2RequestDto.cs deleted file mode 100644 index 2dead1e5..00000000 --- a/src/DFApp.Web/Domain/Aria2/Request/Aria2RequestDto.cs +++ /dev/null @@ -1,22 +0,0 @@ -using System.Collections.Generic; -using System.Text.Json.Serialization; - -namespace DFApp.Aria2.Request; - -/// -/// Aria2 RPC 请求 DTO(用于队列传递) -/// -public class Aria2RequestDto -{ - [JsonPropertyName("jsonrpc")] - public string JSONRPC { get; set; } = string.Empty; - - [JsonPropertyName("method")] - public string Method { get; set; } = string.Empty; - - [JsonPropertyName("id")] - public string Id { get; set; } = string.Empty; - - [JsonPropertyName("params")] - public IList Params { get; set; } = new List(); -} diff --git a/src/DFApp.Web/Domain/Rss/RssSubscription.cs b/src/DFApp.Web/Domain/Rss/RssSubscription.cs deleted file mode 100644 index 5d5c9c9b..00000000 --- a/src/DFApp.Web/Domain/Rss/RssSubscription.cs +++ /dev/null @@ -1,127 +0,0 @@ -using System; -using SqlSugar; -using DFApp.Web.Domain; - -namespace DFApp.Rss -{ - /// - /// RSS订阅配置 - /// - [SugarTable("AppRssSubscriptions")] - public class RssSubscription : AuditedEntity - { - /// - /// 订阅名称 - /// - [SugarColumn(ColumnName = "Name")] - public string Name { get; set; } = string.Empty; - - /// - /// 关键词 - /// - [SugarColumn(ColumnName = "Keywords")] - public string Keywords { get; set; } = string.Empty; - - /// - /// 是否启用 - /// - [SugarColumn(ColumnName = "IsEnabled")] - public bool IsEnabled { get; set; } - - /// - /// 最小做种者数量 - /// - [SugarColumn(ColumnName = "MinSeeders")] - public int? MinSeeders { get; set; } - - /// - /// 最大做种者数量 - /// - [SugarColumn(ColumnName = "MaxSeeders")] - public int? MaxSeeders { get; set; } - - /// - /// 最小下载者数量 - /// - [SugarColumn(ColumnName = "MinLeechers")] - public int? MinLeechers { get; set; } - - /// - /// 最大下载者数量 - /// - [SugarColumn(ColumnName = "MaxLeechers")] - public int? MaxLeechers { get; set; } - - /// - /// 最小完成下载数量 - /// - [SugarColumn(ColumnName = "MinDownloads")] - public int? MinDownloads { get; set; } - - /// - /// 最大完成下载数量 - /// - [SugarColumn(ColumnName = "MaxDownloads")] - public int? MaxDownloads { get; set; } - - /// - /// 质量过滤器 - /// - [SugarColumn(ColumnName = "QualityFilter")] - public string? QualityFilter { get; set; } - - /// - /// 字幕组过滤器 - /// - [SugarColumn(ColumnName = "SubtitleGroupFilter")] - public string? SubtitleGroupFilter { get; set; } - - /// - /// 是否自动下载 - /// - [SugarColumn(ColumnName = "AutoDownload")] - public bool AutoDownload { get; set; } - - /// - /// 是否仅视频 - /// - [SugarColumn(ColumnName = "VideoOnly")] - public bool VideoOnly { get; set; } - - /// - /// 是否启用关键词过滤 - /// - [SugarColumn(ColumnName = "EnableKeywordFilter")] - public bool EnableKeywordFilter { get; set; } - - /// - /// 保存路径 - /// - [SugarColumn(ColumnName = "SavePath")] - public string? SavePath { get; set; } - - /// - /// RSS源ID - /// - [SugarColumn(ColumnName = "RssSourceId")] - public long? RssSourceId { get; set; } - - /// - /// 开始日期 - /// - [SugarColumn(ColumnName = "StartDate")] - public DateTime? StartDate { get; set; } - - /// - /// 结束日期 - /// - [SugarColumn(ColumnName = "EndDate")] - public DateTime? EndDate { get; set; } - - /// - /// 备注 - /// - [SugarColumn(ColumnName = "Remark")] - public string? Remark { get; set; } - } -} diff --git a/src/DFApp.Web/Domain/Rss/RssSubscriptionDownload.cs b/src/DFApp.Web/Domain/Rss/RssSubscriptionDownload.cs deleted file mode 100644 index f18195fd..00000000 --- a/src/DFApp.Web/Domain/Rss/RssSubscriptionDownload.cs +++ /dev/null @@ -1,61 +0,0 @@ -using System; -using SqlSugar; -using DFApp.Web.Domain; - -namespace DFApp.Rss -{ - /// - /// RSS订阅下载记录 - /// - [SugarTable("AppRssSubscriptionDownloads")] - public class RssSubscriptionDownload : CreationAuditedEntity - { - /// - /// 订阅ID - /// - [SugarColumn(ColumnName = "SubscriptionId")] - public long SubscriptionId { get; set; } - - /// - /// RSS镜像条目ID - /// - [SugarColumn(ColumnName = "RssMirrorItemId")] - public long RssMirrorItemId { get; set; } - - /// - /// Aria2任务ID - /// - [SugarColumn(ColumnName = "Aria2Gid")] - public string Aria2Gid { get; set; } = string.Empty; - - /// - /// 下载状态(0=未开始,1=下载中,2=已完成,3=失败) - /// - [SugarColumn(ColumnName = "DownloadStatus")] - public int DownloadStatus { get; set; } - - /// - /// 错误信息 - /// - [SugarColumn(ColumnName = "ErrorMessage")] - public string? ErrorMessage { get; set; } - - /// - /// 下载开始时间 - /// - [SugarColumn(ColumnName = "DownloadStartTime")] - public DateTime? DownloadStartTime { get; set; } - - /// - /// 下载完成时间 - /// - [SugarColumn(ColumnName = "DownloadCompleteTime")] - public DateTime? DownloadCompleteTime { get; set; } - - /// - /// 是否因磁盘空间不足而等待 - /// - [SugarColumn(ColumnName = "IsPendingDueToLowDiskSpace")] - public bool IsPendingDueToLowDiskSpace { get; set; } - } -} diff --git a/src/DFApp.Web/Mapping/Aria2Mapper.cs b/src/DFApp.Web/Mapping/Aria2Mapper.cs index 78d4770d..2007f134 100644 --- a/src/DFApp.Web/Mapping/Aria2Mapper.cs +++ b/src/DFApp.Web/Mapping/Aria2Mapper.cs @@ -2,7 +2,6 @@ using System.Collections.Generic; using DFApp.Aria2; using DFApp.Aria2.Notifications; -using DFApp.Aria2.Request; using DFApp.Aria2.Response; using DFApp.Aria2.Response.TellStatus; using DFApp.Web.DTOs.Aria2; @@ -17,8 +16,6 @@ using Aria2NotificationDtoType = DFApp.Web.DTOs.Aria2.Aria2NotificationDto; using ParamsItemEntity = DFApp.Aria2.Notifications.ParamsItem; using ParamsItemDtoType = DFApp.Web.DTOs.Aria2.ParamsItemDto; -using Aria2RequestEntity = DFApp.Aria2.Request.Aria2Request; -using Aria2RequestDtoType = DFApp.Web.DTOs.Aria2.Aria2RequestDto; using Aria2ResponseEntity = DFApp.Aria2.Response.Aria2Response; using Aria2ResponseDtoType = DFApp.Web.DTOs.Aria2.Aria2ResponseDto; using ResponseBaseEntity = DFApp.Aria2.ResponseBase; @@ -89,11 +86,6 @@ public partial class Aria2Mapper /// public partial ParamsItemEntity MapToEntity(ParamsItemDtoType dto); - /// - /// Aria2Request → Aria2RequestDto - /// - public partial Aria2RequestDtoType MapToDto(Aria2RequestEntity entity); - /// /// Aria2Response → Aria2ResponseDto /// diff --git a/src/DFApp.Web/Mapping/RssMapper.cs b/src/DFApp.Web/Mapping/RssMapper.cs index 1b2505c0..0489d12a 100644 --- a/src/DFApp.Web/Mapping/RssMapper.cs +++ b/src/DFApp.Web/Mapping/RssMapper.cs @@ -6,13 +6,8 @@ using RssSourceEntity = DFApp.Rss.RssSource; using RssSourceDtoType = DFApp.Web.DTOs.Rss.RssSourceDto; using CreateUpdateRssSourceDtoType = DFApp.Web.DTOs.Rss.CreateUpdateRssSourceDto; -using RssSubscriptionEntity = DFApp.Rss.RssSubscription; -using RssSubscriptionDtoType = DFApp.Web.DTOs.Rss.RssSubscriptionDto; -using CreateUpdateRssSubscriptionDtoType = DFApp.Web.DTOs.Rss.CreateUpdateRssSubscriptionDto; using RssMirrorItemEntity = DFApp.Rss.RssMirrorItem; using RssMirrorItemDtoType = DFApp.Web.DTOs.Rss.RssMirrorItemDto; -using RssSubscriptionDownloadEntity = DFApp.Rss.RssSubscriptionDownload; -using RssSubscriptionDownloadDtoType = DFApp.Web.DTOs.Rss.RssSubscriptionDownloadDto; using RssWordSegmentEntity = DFApp.Rss.RssWordSegment; using RssWordSegmentDtoType = DFApp.Web.DTOs.Rss.RssWordSegmentDto; using RssWordSegmentWithItemDtoType = DFApp.Web.DTOs.Rss.RssWordSegmentWithItemDto; @@ -40,24 +35,6 @@ public partial class RssMapper [MapperIgnoreTarget(nameof(RssSourceEntity.ExtraProperties))] public partial RssSourceEntity MapToEntity(CreateUpdateRssSourceDtoType dto); - /// - /// RssSubscription → RssSubscriptionDto - /// 忽略 RssSourceName(由服务层填充) - /// - [MapperIgnoreTarget(nameof(RssSubscriptionDtoType.RssSourceName))] - public partial RssSubscriptionDtoType MapToDto(RssSubscriptionEntity entity); - - /// - /// CreateUpdateRssSubscriptionDto → RssSubscription - /// 忽略审计字段 - /// - [MapperIgnoreTarget(nameof(RssSubscriptionEntity.ConcurrencyStamp))] - [MapperIgnoreTarget(nameof(RssSubscriptionEntity.CreationTime))] - [MapperIgnoreTarget(nameof(RssSubscriptionEntity.CreatorId))] - [MapperIgnoreTarget(nameof(RssSubscriptionEntity.LastModificationTime))] - [MapperIgnoreTarget(nameof(RssSubscriptionEntity.LastModifierId))] - public partial RssSubscriptionEntity MapToEntity(CreateUpdateRssSubscriptionDtoType dto); - /// /// RssMirrorItem → RssMirrorItemDto /// 忽略 WordSegments(由服务层单独填充) @@ -66,17 +43,6 @@ public partial class RssMapper [MapperIgnoreTarget(nameof(RssMirrorItemDtoType.RssSourceName))] public partial RssMirrorItemDtoType MapToDto(RssMirrorItemEntity entity); - /// - /// RssSubscriptionDownload → RssSubscriptionDownloadDto - /// 忽略由服务层填充的显示字段 - /// - [MapperIgnoreTarget(nameof(RssSubscriptionDownloadDtoType.SubscriptionName))] - [MapperIgnoreTarget(nameof(RssSubscriptionDownloadDtoType.RssMirrorItemTitle))] - [MapperIgnoreTarget(nameof(RssSubscriptionDownloadDtoType.RssMirrorItemLink))] - [MapperIgnoreTarget(nameof(RssSubscriptionDownloadDtoType.RssSourceName))] - [MapperIgnoreTarget(nameof(RssSubscriptionDownloadDtoType.DownloadStatusText))] - public partial RssSubscriptionDownloadDtoType MapToDto(RssSubscriptionDownloadEntity entity); - /// /// RssWordSegment → RssWordSegmentDto /// diff --git a/src/DFApp.Web/Program.cs b/src/DFApp.Web/Program.cs index f01e171d..e713a72c 100644 --- a/src/DFApp.Web/Program.cs +++ b/src/DFApp.Web/Program.cs @@ -112,12 +112,9 @@ public async static Task Main(string[] args) builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); - builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); builder.Services.AddScoped(); @@ -241,13 +238,6 @@ public async static Task Main(string[] args) .WithIdentity("GasolinePriceRefreshJob-trigger") .WithCronSchedule("0 0 21 * * ?")); - // DiskSpaceCheckJob — 每10分钟执行 - q.ScheduleJob(trigger => trigger - .WithIdentity("DiskSpaceCheckJob-trigger") - .WithSimpleSchedule(x => x - .WithIntervalInMinutes(10) - .RepeatForever())); - // LotteryResultJob — 每晚23:00执行 q.ScheduleJob(trigger => trigger .WithIdentity("LotteryResultJob-trigger") diff --git a/src/DFApp.Web/Services/Aria2/Aria2Manager.cs b/src/DFApp.Web/Services/Aria2/Aria2Manager.cs index cd1b08c3..52c320ce 100644 --- a/src/DFApp.Web/Services/Aria2/Aria2Manager.cs +++ b/src/DFApp.Web/Services/Aria2/Aria2Manager.cs @@ -1,13 +1,8 @@ using System; using System.Collections.Generic; -using System.Linq; -using System.Text.Json; using System.Threading.Tasks; using DFApp.Aria2.Notifications; -using DFApp.Aria2.Request; -using DFApp.Aria2.Response.TellStatus; using DFApp.Web.Data; -using DFApp.Web.Data.Configuration; using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; @@ -19,14 +14,12 @@ namespace DFApp.Aria2; public class Aria2Manager { private readonly IServiceScopeFactory _scopeFactory; - private readonly List _requestsHistory; private readonly ILogger _logger; public Aria2Manager( IServiceScopeFactory scopeFactory, ILogger logger) { - _requestsHistory = new List(); _scopeFactory = scopeFactory; _logger = logger; } @@ -34,50 +27,27 @@ public Aria2Manager( /// /// 处理 RPC 响应,区分通知和请求响应 /// - /// RPC 响应 - /// 需要发送的请求列表 - public async Task> ProcessResponseAsync(ResponseBase? response) + public async Task ProcessResponseAsync(ResponseBase? response) { if (response == null) { - return new List(); + return; } if (string.IsNullOrEmpty(response.Id)) { - // 通知事件(没有 id 字段) - return await ProcessNotificationAsync(response as Aria2Notification); + await ProcessNotificationAsync(response as Aria2Notification); } - else - { - // 请求响应(有 id 字段) - var res = _requestsHistory.FirstOrDefault(x => x.Id == response.Id); - if (res != null) - { - switch (res.Method) - { - case Aria2Consts.TellStatus: - await SaveTellStatusResultAsync(response); - _requestsHistory.Remove(res); - break; - default: - break; - } - } - } - return new List(); } /// /// 处理 WebSocket 通知事件 /// - /// 通知对象 - /// 需要发送的请求列表 - public async Task> ProcessNotificationAsync(Aria2Notification? notification) + public async Task ProcessNotificationAsync(Aria2Notification? notification) { if (notification == null) { - return new List(); + return; } switch (notification.Method) @@ -96,189 +66,115 @@ public async Task> ProcessNotificationAsync(Aria2Notification break; case Aria2Consts.OnDownloadComplete: case Aria2Consts.OnBtDownloadComplete: - return await DownloadCompleteHandlerAsync(notification.Params); + await DownloadCompleteHandlerAsync(notification.Params); + break; default: _logger.LogInformation("Aria2 通知: 未知事件 {Method}", notification.Method); break; } - return new List(); } /// - /// 下载完成处理:构建 tellStatus 请求以获取完整状态存入数据库 + /// 下载完成处理:通过 HTTP RPC 调用 TellStatus 获取完整状态并存入数据库 /// /// 通知参数列表 - /// 需要发送的 tellStatus 请求列表 - public async Task> DownloadCompleteHandlerAsync(List paramsItems) + private async Task DownloadCompleteHandlerAsync(List paramsItems) { - List requests = new List(); - string aria2secret; - using (var scope = _scopeFactory.CreateScope()) - { - var configRepo = scope.ServiceProvider.GetRequiredService(); - aria2secret = await configRepo.GetConfigurationInfoValue("aria2secret", "DFApp.Aria2.Aria2Service"); - } foreach (var item in paramsItems) { - var request = new Aria2Request(Guid.NewGuid().ToString(), aria2secret); - request.Method = Aria2Consts.TellStatus; - if (!string.IsNullOrWhiteSpace(aria2secret)) + try + { + using var scope = _scopeFactory.CreateScope(); + var rpcClient = scope.ServiceProvider.GetRequiredService(); + + var taskDto = await rpcClient.TellStatusAsync(item.GID); + + _logger.LogInformation("=== 保存 TellStatus 结果到数据库 ==="); + _logger.LogInformation("GID: {Gid}", taskDto.Gid); + _logger.LogInformation("Dir: {Dir}", taskDto.Dir); + _logger.LogInformation("Status: {Status}", taskDto.Status); + _logger.LogInformation("TotalLength: {TotalLength}", taskDto.TotalLength); + _logger.LogInformation("CompletedLength: {CompletedLength}", taskDto.CompletedLength); + + await SaveTellStatusResultFromDtoAsync(scope, taskDto); + + _logger.LogInformation("=== TellStatus 结果保存完成 ==="); + } + catch (Exception ex) { - request.Params.Add($"token:{aria2secret}"); + _logger.LogError(ex, "处理下载完成通知失败,GID: {Gid}", item.GID); } - request.Params.Add(item.GID); - _requestsHistory.Add(request); - requests.Add(request); } - return requests; } /// - /// 从 RPC 响应中解析并保存 TellStatus 结果到数据库 + /// 从 Aria2TaskDto 保存 TellStatus 结果到数据库 /// - /// RPC 响应 - private async Task SaveTellStatusResultAsync(ResponseBase response) + /// 已创建的服务作用域 + /// 任务状态 DTO + private async Task SaveTellStatusResultFromDtoAsync(IServiceScope scope, Aria2TaskDto taskDto) { - try - { - // 从响应 JSON 中提取 result 数据 - var jsonElement = JsonSerializer.Deserialize(JsonSerializer.Serialize(response)); - if (!jsonElement.TryGetProperty("result", out var resultElement)) - { - _logger.LogWarning("TellStatus 响应中未找到 result 字段"); - return; - } + var resultRepository = scope.ServiceProvider.GetRequiredService>(); + var filesItemRepository = scope.ServiceProvider.GetRequiredService>(); + var urisItemRepository = scope.ServiceProvider.GetRequiredService>(); - // 解析 TellStatusResult - var tellStatusResult = new TellStatusResult + var tellStatusResult = new Response.TellStatus.TellStatusResult + { + GID = taskDto.Gid, + Status = taskDto.Status, + TotalLength = taskDto.TotalLength, + CompletedLength = taskDto.CompletedLength, + UploadLength = taskDto.UploadedLength, + DownloadSpeed = taskDto.DownloadSpeed, + UploadSpeed = taskDto.UploadSpeed, + Connections = taskDto.Connections, + Dir = taskDto.Dir, + ErrorCode = taskDto.ErrorCode, + ErrorMessage = taskDto.ErrorMessage + }; + + // 保存主记录 + tellStatusResult.Id = await resultRepository.InsertReturnIdAsync(tellStatusResult); + + // 解析并保存文件列表 + if (taskDto.Files != null) + { + int fileIndex = 0; + foreach (var fileDto in taskDto.Files) { - GID = resultElement.TryGetProperty("gid", out var gid) ? gid.GetString() : null, - Status = resultElement.TryGetProperty("status", out var status) ? status.GetString() : null, - TotalLength = resultElement.TryGetProperty("totalLength", out var totalLength) ? GetLongValue(totalLength) : null, - CompletedLength = resultElement.TryGetProperty("completedLength", out var completedLength) ? GetLongValue(completedLength) : null, - UploadLength = resultElement.TryGetProperty("uploadLength", out var uploadLength) ? GetLongValue(uploadLength) : null, - DownloadSpeed = resultElement.TryGetProperty("downloadSpeed", out var downloadSpeed) ? GetLongValue(downloadSpeed) : null, - UploadSpeed = resultElement.TryGetProperty("uploadSpeed", out var uploadSpeed) ? GetLongValue(uploadSpeed) : null, - Connections = resultElement.TryGetProperty("connections", out var connections) ? GetLongValue(connections) : null, - NumPieces = resultElement.TryGetProperty("numPieces", out var numPieces) ? GetLongValue(numPieces) : null, - PieceLength = resultElement.TryGetProperty("pieceLength", out var pieceLength) ? GetLongValue(pieceLength) : null, - Bitfield = resultElement.TryGetProperty("bitfield", out var bitfield) ? bitfield.GetString() : null, - Dir = resultElement.TryGetProperty("dir", out var dir) ? dir.GetString() : null, - ErrorCode = resultElement.TryGetProperty("errorCode", out var errorCode) ? errorCode.GetString() : null, - ErrorMessage = resultElement.TryGetProperty("errorMessage", out var errorMessage) ? errorMessage.GetString() : null - }; - - _logger.LogInformation("=== 保存 TellStatus 结果到数据库 ==="); - _logger.LogInformation("GID: {Gid}", tellStatusResult.GID); - _logger.LogInformation("Dir: {Dir}", tellStatusResult.Dir); - _logger.LogInformation("Status: {Status}", tellStatusResult.Status); - _logger.LogInformation("TotalLength: {TotalLength}", tellStatusResult.TotalLength); - _logger.LogInformation("CompletedLength: {CompletedLength}", tellStatusResult.CompletedLength); + var filesItem = new Response.TellStatus.FilesItem + { + ResultId = tellStatusResult.Id, + Index = long.TryParse(fileDto.Index, out var idx) ? idx : fileIndex, + Path = fileDto.Path, + Length = fileDto.Length, + CompletedLength = fileDto.CompletedLength, + Selected = fileDto.Selected + }; - // 在 scope 中执行数据库操作 - using (var scope = _scopeFactory.CreateScope()) - { - var resultRepository = scope.ServiceProvider.GetRequiredService>(); - var filesItemRepository = scope.ServiceProvider.GetRequiredService>(); - var urisItemRepository = scope.ServiceProvider.GetRequiredService>(); + filesItem.Id = (int)await filesItemRepository.InsertReturnIdAsync(filesItem); - // 保存主记录,使用 InsertReturnIdAsync 获取自增 Id - tellStatusResult.Id = await resultRepository.InsertReturnIdAsync(tellStatusResult); + _logger.LogInformation(" 文件[{Index}]: {Path}, 长度: {Length}, 已完成: {CompletedLength}", + filesItem.Index, filesItem.Path, filesItem.Length, filesItem.CompletedLength); - // 解析并保存文件列表 - if (resultElement.TryGetProperty("files", out var filesElement) && filesElement.ValueKind == JsonValueKind.Array) + // 解析并保存 URI 列表 + if (fileDto.Uris != null) { - int fileIndex = 0; - foreach (var fileElement in filesElement.EnumerateArray()) + foreach (var uriDto in fileDto.Uris) { - var filesItem = new FilesItem + var urisItem = new Response.TellStatus.UrisItem { - ResultId = tellStatusResult.Id, - Index = fileElement.TryGetProperty("index", out var index) ? GetLongValue(index) : fileIndex, - Path = fileElement.TryGetProperty("path", out var path) ? path.GetString() : null, - Length = fileElement.TryGetProperty("length", out var length) ? GetLongValue(length) : null, - CompletedLength = fileElement.TryGetProperty("completedLength", out var fileCompletedLength) ? GetLongValue(fileCompletedLength) : null, - Selected = fileElement.TryGetProperty("selected", out var selected) ? GetBoolValue(selected) : null + FilesItemId = filesItem.Id, + Uri = uriDto.Uri, + Status = uriDto.Status }; - // 使用 InsertReturnIdAsync 获取自增 Id,用于子表外键关联 - filesItem.Id = (int)await filesItemRepository.InsertReturnIdAsync(filesItem); - - _logger.LogInformation(" 文件[{Index}]: {Path}, 长度: {Length}, 已完成: {CompletedLength}", - filesItem.Index, filesItem.Path, filesItem.Length, filesItem.CompletedLength); - - // 解析并保存 URI 列表 - if (fileElement.TryGetProperty("uris", out var urisElement) && urisElement.ValueKind == JsonValueKind.Array) - { - foreach (var uriElement in urisElement.EnumerateArray()) - { - var urisItem = new UrisItem - { - FilesItemId = filesItem.Id, - Uri = uriElement.TryGetProperty("uri", out var uri) ? uri.GetString() : null, - Status = uriElement.TryGetProperty("status", out var uriStatus) ? uriStatus.GetString() : null - }; - - await urisItemRepository.InsertAsync(urisItem); - } - } - - fileIndex++; + await urisItemRepository.InsertAsync(urisItem); } } - } - _logger.LogInformation("=== TellStatus 结果保存完成 ==="); - } - catch (Exception ex) - { - _logger.LogError(ex, "保存 TellStatus 结果到数据库失败"); - } - } - - /// - /// 从 JsonElement 安全获取 long 值(支持字符串和数字类型) - /// - private long? GetLongValue(JsonElement element) - { - if (element.ValueKind == JsonValueKind.String) - { - var str = element.GetString(); - return long.TryParse(str, out var value) ? value : null; - } - if (element.ValueKind == JsonValueKind.Number) - { - return element.GetInt64(); - } - return null; - } - - /// - /// 从 JsonElement 安全获取 bool? 值 - /// - private bool? GetBoolValue(JsonElement element) - { - if (element.ValueKind == JsonValueKind.String) - { - var str = element.GetString(); - return bool.TryParse(str, out var value) ? value : null; - } - if (element.ValueKind == JsonValueKind.True || element.ValueKind == JsonValueKind.False) - { - return element.GetBoolean(); - } - return null; - } - - /// - /// 从 JsonElement 安全获取 string 值 - /// - private string? GetStringValue(JsonElement element) - { - if (element.ValueKind == JsonValueKind.String) - { - return element.GetString(); + fileIndex++; + } } - return element.ToString(); } } diff --git a/src/DFApp.Web/Services/Aria2/Aria2Service.cs b/src/DFApp.Web/Services/Aria2/Aria2Service.cs index 141283cc..c06053ec 100644 --- a/src/DFApp.Web/Services/Aria2/Aria2Service.cs +++ b/src/DFApp.Web/Services/Aria2/Aria2Service.cs @@ -8,14 +8,13 @@ using BencodeNET.Parsing; using BencodeNET.Torrents; using DFApp.Aria2; -using DFApp.Aria2.Request; using DFApp.Aria2.Response.TellStatus; using DFApp.FileFilter; using DFApp.Web.Data; using DFApp.Web.Data.Configuration; using DFApp.Web.Infrastructure; using DFApp.Web.Permissions; -using DFApp.Web.Queue; +using Microsoft.Extensions.DependencyInjection; using Microsoft.Extensions.Logging; namespace DFApp.Web.Services.Aria2; @@ -32,7 +31,7 @@ public class Aria2Service : CrudServiceBase< { private readonly ISqlSugarRepository _filesItemRepository; private readonly IConfigurationInfoRepository _configurationInfoRepository; - private readonly IQueueManagement _queueManagement; + private readonly IServiceScopeFactory _scopeFactory; private readonly IKeywordFilterRuleRepository _keywordFilterRuleRepository; private readonly ILogger _logger; @@ -44,7 +43,7 @@ public class Aria2Service : CrudServiceBase< /// TellStatusResult 仓储接口 /// FilesItem 仓储接口 /// 配置信息仓储接口 - /// 队列管理接口 + /// 服务作用域工厂,用于获取 Aria2RpcClient /// 关键词过滤规则仓储接口 /// 日志记录器 public Aria2Service( @@ -53,14 +52,14 @@ public Aria2Service( ISqlSugarRepository repository, ISqlSugarRepository filesItemRepository, IConfigurationInfoRepository configurationInfoRepository, - IQueueManagement queueManagement, + IServiceScopeFactory scopeFactory, IKeywordFilterRuleRepository keywordFilterRuleRepository, ILogger logger) : base(currentUser, permissionChecker, repository) { _filesItemRepository = filesItemRepository; _configurationInfoRepository = configurationInfoRepository; - _queueManagement = queueManagement; + _scopeFactory = scopeFactory; _keywordFilterRuleRepository = keywordFilterRuleRepository; _logger = logger; } @@ -318,7 +317,7 @@ public async Task ClearDownloadDirectoryAsync() } /// - /// 添加下载任务 + /// 添加下载任务(通过 HTTP RPC 直连 Aria2) /// /// 下载请求 DTO /// 下载响应 DTO @@ -329,11 +328,9 @@ public async Task AddDownloadAsync(AddDownloadRequestDto throw new BusinessException("URL列表不能为空"); } - // 从配置获取 aria2secret - string aria2secret = await _configurationInfoRepository.GetConfigurationInfoValue("aria2secret", "DFApp.Aria2.Aria2Service"); - - // 创建 Aria2Request - 构造函数会添加 token 到 Params[0] - var request = new Aria2Request(Guid.NewGuid().ToString(), aria2secret); + // 通过 scope 获取 Aria2RpcClient(因为它是通过 AddHttpClient 注册的) + using var scope = _scopeFactory.CreateScope(); + var rpcClient = scope.ServiceProvider.GetRequiredService(); // 判断是否包含 torrent 文件 URL 或磁力链接 bool hasTorrentFile = input.Urls.Any(url => url.EndsWith(".torrent", StringComparison.OrdinalIgnoreCase)); @@ -343,6 +340,8 @@ public async Task AddDownloadAsync(AddDownloadRequestDto // 处理过滤选项(VideoOnly 和关键词过滤) bool shouldFilterTorrent = (input.VideoOnly || input.EnableKeywordFilter) && hasTorrentFile; + string gid; + if (shouldFilterTorrent) { // 对于 .torrent 文件,使用 addTorrent 方法并设置 select-file 选项 @@ -354,17 +353,11 @@ public async Task AddDownloadAsync(AddDownloadRequestDto // 构建 select-file 字符串,例如"1,3,5" string selectFile = string.Join(",", filteredIndices); - // 下载 torrent 文件内容 + // 下载 torrent 文件内容并转为 Base64 using var httpClient = new HttpClient(); var torrentBytes = await httpClient.GetByteArrayAsync(torrentUrl); string torrentBase64 = Convert.ToBase64String(torrentBytes); - // 使用 addTorrent 方法 - request.Method = Aria2Consts.AddTorrent; - // 参数:token, torrent 内容 base64, urls 数组(空), options - request.Params.Insert(1, torrentBase64); - request.Params.Insert(2, new List()); - // 构建选项 var options = new Dictionary(); if (!string.IsNullOrWhiteSpace(input.SavePath)) @@ -382,21 +375,17 @@ public async Task AddDownloadAsync(AddDownloadRequestDto } } - if (options.Count > 0) - { - request.Params.Add(options); - } - string filterDescription = GetFilterDescription(input.VideoOnly, input.EnableKeywordFilter); _logger.LogInformation("{FilterDescription}过滤已应用,选择文件索引: {SelectFile}", filterDescription, selectFile); + + // 直接调用 RPC 添加种子下载 + gid = await rpcClient.AddTorrentAsync(torrentBase64, options); } else { _logger.LogWarning("过滤已启用,但未在 torrent 文件中找到符合条件的文件,将下载全部文件"); - // 继续使用 AddUri 方法 - request.Method = Aria2Consts.AddUri; - request.Params.Insert(1, input.Urls); - AddOptionsToRequest(request, input.SavePath, input.Options); + var options = BuildOptions(input.SavePath, input.Options); + gid = await rpcClient.AddUriAsync(input.Urls, options); } } else @@ -419,9 +408,6 @@ public async Task AddDownloadAsync(AddDownloadRequestDto } } - request.Method = Aria2Consts.AddUri; - request.Params.Insert(1, filteredUrls); - // 如果指定了 VideoOnly 但不是 .torrent 文件,记录警告 if (input.VideoOnly) { @@ -441,26 +427,13 @@ public async Task AddDownloadAsync(AddDownloadRequestDto _logger.LogInformation("关键词过滤已应用于URL列表"); } - // 添加选项 - AddOptionsToRequest(request, input.SavePath, input.Options); + // 直接调用 RPC 添加 URI 下载 + var options = BuildOptions(input.SavePath, input.Options); + gid = await rpcClient.AddUriAsync(filteredUrls, options); } - // 将请求转换为 DTO 并添加到队列 - // Aria2RequestDto 来自 DFApp.Aria2.Request 命名空间, - // 与 Mapperly 映射器的 DFApp.Web.DTOs.Aria2.Aria2RequestDto 类型不同, - // 因此保留手动映射 - var requestDto = new Aria2RequestDto - { - JSONRPC = request.JSONRPC, - Method = request.Method, - Id = request.Id, - Params = request.Params - }; - - // 添加到队列 - _queueManagement.AddQueueValue("Aria2RequestQueue", new List { requestDto }); - - return new AddDownloadResponseDto { Id = request.Id }; + _logger.LogInformation("已通过 RPC 直连添加下载任务,GID: {Gid}", gid); + return new AddDownloadResponseDto { Id = gid }; } /// @@ -655,12 +628,12 @@ private string ExtractFileNameFromUrl(string url) } /// - /// 添加选项到 Aria2 请求 + /// 构建 Aria2 下载选项 /// - /// Aria2 请求 /// 保存路径 /// 自定义选项 - private void AddOptionsToRequest(Aria2Request request, string? savePath, Dictionary? customOptions) + /// 选项字典(无选项时返回 null) + private Dictionary? BuildOptions(string? savePath, Dictionary? customOptions) { var options = new Dictionary(); @@ -677,10 +650,7 @@ private void AddOptionsToRequest(Aria2Request request, string? savePath, Diction } } - if (options.Count > 0) - { - request.Params.Add(options); - } + return options.Count > 0 ? options : null; } /// diff --git a/src/DFApp.Web/Services/Rss/IRssSubscriptionService.cs b/src/DFApp.Web/Services/Rss/IRssSubscriptionService.cs deleted file mode 100644 index f7d4eaf3..00000000 --- a/src/DFApp.Web/Services/Rss/IRssSubscriptionService.cs +++ /dev/null @@ -1,31 +0,0 @@ -using System.Collections.Generic; -using System.Threading.Tasks; -using DFApp.Rss; - -namespace DFApp.Web.Services.Rss -{ - /// - /// RSS订阅服务接口 - 负责订阅匹配、下载任务创建和暂存下载处理 - /// - public interface IRssSubscriptionService - { - /// - /// 匹配RSS镜像条目与所有启用的订阅规则 - /// - /// RSS镜像条目 - /// 每个订阅的匹配结果列表 - Task> MatchSubscriptionsAsync(RssMirrorItem item); - - /// - /// 根据订阅ID和镜像条目ID创建下载任务 - /// - /// 订阅ID - /// RSS镜像条目ID - Task CreateDownloadTaskAsync(long subscriptionId, long rssMirrorItemId); - - /// - /// 处理因磁盘空间不足而暂存的下载任务 - /// - Task ProcessPendingDownloadsAsync(); - } -} diff --git a/src/DFApp.Web/Services/Rss/RssSubscriptionAppService.cs b/src/DFApp.Web/Services/Rss/RssSubscriptionAppService.cs deleted file mode 100644 index 98b0a266..00000000 --- a/src/DFApp.Web/Services/Rss/RssSubscriptionAppService.cs +++ /dev/null @@ -1,320 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DFApp.Rss; -using DFApp.Web.Data; -using DFApp.Web.Infrastructure; -using DFApp.Web.Permissions; -using DFApp.Web.DTOs; -using DFApp.Web.DTOs.Rss; -using Microsoft.Extensions.Logging; -using SqlSugar; - -namespace DFApp.Web.Services.Rss; - -/// -/// RSS订阅管理服务 -/// -public class RssSubscriptionAppService : AppServiceBase -{ - private readonly ISqlSugarRepository _rssSubscriptionRepository; - private readonly ISqlSugarRepository _rssSourceRepository; - private readonly ILogger _logger; - - /// - /// 构造函数 - /// - /// 当前用户 - /// 权限检查器 - /// RSS订阅仓储 - /// RSS源仓储 - /// 日志记录器 - public RssSubscriptionAppService( - ICurrentUser currentUser, - IPermissionChecker permissionChecker, - ISqlSugarRepository rssSubscriptionRepository, - ISqlSugarRepository rssSourceRepository, - ILogger logger) - : base(currentUser, permissionChecker) - { - _rssSubscriptionRepository = rssSubscriptionRepository; - _rssSourceRepository = rssSourceRepository; - _logger = logger; - } - - /// - /// 获取RSS订阅分页列表 - /// - /// 查询请求DTO - /// 分页结果 - public async Task> GetListAsync(GetRssSubscriptionsRequestDto input) - { - var queryable = _rssSubscriptionRepository.GetQueryable(); - - if (input.IsEnabled.HasValue) - { - queryable = queryable.Where(x => x.IsEnabled == input.IsEnabled.Value); - } - - if (input.RssSourceId.HasValue) - { - queryable = queryable.Where(x => x.RssSourceId == input.RssSourceId.Value); - } - - if (!string.IsNullOrWhiteSpace(input.Filter)) - { - queryable = queryable.Where(x => x.Name.Contains(input.Filter) || - x.Keywords.Contains(input.Filter)); - } - - if (!string.IsNullOrWhiteSpace(input.Sorting)) - { - queryable = queryable.OrderBy(input.Sorting); - } - else - { - queryable = queryable.OrderByDescending(x => x.CreationTime); - } - - var totalCount = await queryable.CountAsync(); - var items = await queryable - .Skip(input.SkipCount) - .Take(input.MaxResultCount) - .ToListAsync(); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - var dtos = items.Select(MapToDto).ToList(); - - // 填充RSS源名称 - var sources = await _rssSourceRepository.GetListAsync(); - foreach (var dto in dtos) - { - dto.RssSourceName = sources.FirstOrDefault(s => s.Id == dto.RssSourceId)?.Name; - } - - return new PagedResultDto(totalCount, dtos); - } - - /// - /// 根据ID获取RSS订阅 - /// - /// 订阅ID - /// 订阅DTO - public async Task GetAsync(long id) - { - var subscription = await _rssSubscriptionRepository.GetByIdAsync(id); - EnsureEntityExists(subscription, id); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - var dto = MapToDto(subscription); - - // 填充RSS源名称 - if (subscription.RssSourceId.HasValue) - { - var source = await _rssSourceRepository.GetFirstOrDefaultAsync(s => s.Id == subscription.RssSourceId.Value); - dto.RssSourceName = source?.Name; - } - - return dto; - } - - /// - /// 创建RSS订阅 - /// - /// 创建/更新订阅DTO - /// 订阅DTO - public async Task CreateAsync(CreateUpdateRssSubscriptionDto input) - { - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - var subscription = new RssSubscription - { - Name = input.Name, - Keywords = input.Keywords, - IsEnabled = input.IsEnabled, - MinSeeders = input.MinSeeders, - MaxSeeders = input.MaxSeeders, - MinLeechers = input.MinLeechers, - MaxLeechers = input.MaxLeechers, - MinDownloads = input.MinDownloads, - MaxDownloads = input.MaxDownloads, - QualityFilter = input.QualityFilter, - SubtitleGroupFilter = input.SubtitleGroupFilter, - AutoDownload = input.AutoDownload, - VideoOnly = input.VideoOnly, - EnableKeywordFilter = input.EnableKeywordFilter, - SavePath = input.SavePath, - RssSourceId = input.RssSourceId, - StartDate = input.StartDate, - EndDate = input.EndDate, - Remark = input.Remark, - CreationTime = DateTime.Now - }; - - await _rssSubscriptionRepository.InsertAsync(subscription); - - _logger.LogInformation("创建RSS订阅: {Name}", input.Name); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - return MapToDto(subscription); - } - - /// - /// 更新RSS订阅 - /// - /// 订阅ID - /// 创建/更新订阅DTO - /// 订阅DTO - public async Task UpdateAsync(long id, CreateUpdateRssSubscriptionDto input) - { - _logger.LogInformation("开始更新RSS订阅,ID: {Id}", id); - - var subscription = await _rssSubscriptionRepository.GetByIdAsync(id); - EnsureEntityExists(subscription, id); - - _logger.LogInformation("获取到的实体,最后修改时间: {Time}", - subscription.LastModificationTime); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - MapToEntity(input, subscription); - subscription.LastModificationTime = DateTime.Now; - - try - { - await _rssSubscriptionRepository.UpdateAsync(subscription); - } - catch (SqlSugarException ex) - { - _logger.LogWarning("更新RSS订阅时发生并发冲突,重新获取实体后重试: {Name}, 异常: {Message}", - input.Name, ex.Message); - - // 等待一小段时间,确保获取到最新数据 - await Task.Delay(100); - - subscription = await _rssSubscriptionRepository.GetByIdAsync(id); - EnsureEntityExists(subscription, id); - _logger.LogInformation("重试获取到的实体,最后修改时间: {Time}", - subscription.LastModificationTime); - - MapToEntity(input, subscription); - subscription.LastModificationTime = DateTime.Now; - - await _rssSubscriptionRepository.UpdateAsync(subscription); - } - - _logger.LogInformation("更新RSS订阅成功: {Name}", input.Name); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - return MapToDto(subscription); - } - - /// - /// 删除RSS订阅 - /// - /// 订阅ID - public async Task DeleteAsync(long id) - { - await _rssSubscriptionRepository.DeleteAsync(id); - _logger.LogInformation("删除RSS订阅: {Id}", id); - } - - /// - /// 切换订阅启用状态 - /// - /// 订阅ID - public async Task ToggleEnableAsync(long id) - { - var subscription = await _rssSubscriptionRepository.GetByIdAsync(id); - EnsureEntityExists(subscription, id); - - subscription.IsEnabled = !subscription.IsEnabled; - subscription.LastModificationTime = DateTime.Now; - - try - { - await _rssSubscriptionRepository.UpdateAsync(subscription); - } - catch (SqlSugarException) - { - _logger.LogWarning("切换订阅状态时发生并发冲突,重新获取实体后重试: {Name}", subscription.Name); - - // 等待一小段时间,确保获取到最新数据 - await Task.Delay(100); - - subscription = await _rssSubscriptionRepository.GetByIdAsync(id); - EnsureEntityExists(subscription, id); - - subscription.IsEnabled = !subscription.IsEnabled; - subscription.LastModificationTime = DateTime.Now; - - await _rssSubscriptionRepository.UpdateAsync(subscription); - } - - _logger.LogInformation("{Action} RSS订阅: {Name}", - subscription.IsEnabled ? "启用" : "禁用", subscription.Name); - } - - /// - /// 将 RssSubscription 实体映射为 RssSubscriptionDto - /// - /// 订阅实体 - /// 订阅DTO - private static RssSubscriptionDto MapToDto(RssSubscription entity) - { - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - return new RssSubscriptionDto - { - Id = entity.Id, - Name = entity.Name, - Keywords = entity.Keywords, - IsEnabled = entity.IsEnabled, - MinSeeders = entity.MinSeeders, - MaxSeeders = entity.MaxSeeders, - MinLeechers = entity.MinLeechers, - MaxLeechers = entity.MaxLeechers, - MinDownloads = entity.MinDownloads, - MaxDownloads = entity.MaxDownloads, - QualityFilter = entity.QualityFilter, - SubtitleGroupFilter = entity.SubtitleGroupFilter, - AutoDownload = entity.AutoDownload, - VideoOnly = entity.VideoOnly, - EnableKeywordFilter = entity.EnableKeywordFilter, - SavePath = entity.SavePath, - RssSourceId = entity.RssSourceId, - StartDate = entity.StartDate, - EndDate = entity.EndDate, - Remark = entity.Remark, - CreationTime = entity.CreationTime, - LastModificationTime = entity.LastModificationTime - }; - } - - /// - /// 将 CreateUpdateRssSubscriptionDto 映射到现有 RssSubscription 实体 - /// - /// 输入DTO - /// 目标实体 - private static void MapToEntity(CreateUpdateRssSubscriptionDto input, RssSubscription entity) - { - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - entity.Name = input.Name; - entity.Keywords = input.Keywords; - entity.IsEnabled = input.IsEnabled; - entity.MinSeeders = input.MinSeeders; - entity.MaxSeeders = input.MaxSeeders; - entity.MinLeechers = input.MinLeechers; - entity.MaxLeechers = input.MaxLeechers; - entity.MinDownloads = input.MinDownloads; - entity.MaxDownloads = input.MaxDownloads; - entity.QualityFilter = input.QualityFilter; - entity.SubtitleGroupFilter = input.SubtitleGroupFilter; - entity.AutoDownload = input.AutoDownload; - entity.VideoOnly = input.VideoOnly; - entity.EnableKeywordFilter = input.EnableKeywordFilter; - entity.SavePath = input.SavePath; - entity.RssSourceId = input.RssSourceId; - entity.StartDate = input.StartDate; - entity.EndDate = input.EndDate; - entity.Remark = input.Remark; - } -} diff --git a/src/DFApp.Web/Services/Rss/RssSubscriptionDownloadAppService.cs b/src/DFApp.Web/Services/Rss/RssSubscriptionDownloadAppService.cs deleted file mode 100644 index 5439603a..00000000 --- a/src/DFApp.Web/Services/Rss/RssSubscriptionDownloadAppService.cs +++ /dev/null @@ -1,279 +0,0 @@ -using System; -using System.Collections.Generic; -using System.Linq; -using System.Threading.Tasks; -using DFApp.Rss; -using DFApp.Web.Data; -using DFApp.Web.Infrastructure; -using DFApp.Web.Permissions; -using DFApp.Web.DTOs; -using DFApp.Web.DTOs.Rss; -using Microsoft.Extensions.Logging; -using SqlSugar; - -namespace DFApp.Web.Services.Rss; - -/// -/// RSS订阅下载记录管理服务 -/// -public class RssSubscriptionDownloadAppService : AppServiceBase -{ - private readonly ISqlSugarRepository _rssSubscriptionDownloadRepository; - private readonly ISqlSugarRepository _rssSubscriptionRepository; - private readonly ISqlSugarRepository _rssMirrorItemRepository; - private readonly ISqlSugarRepository _rssSourceRepository; - private readonly ILogger _logger; - - // TODO: IRssSubscriptionService 未迁移,RetryAsync 方法中用伪代码替代 - // private readonly IRssSubscriptionService _rssSubscriptionService; - - /// - /// 构造函数 - /// - /// 当前用户 - /// 权限检查器 - /// RSS订阅下载记录仓储 - /// RSS订阅仓储 - /// RSS镜像条目仓储 - /// RSS源仓储 - /// 日志记录器 - public RssSubscriptionDownloadAppService( - ICurrentUser currentUser, - IPermissionChecker permissionChecker, - ISqlSugarRepository rssSubscriptionDownloadRepository, - ISqlSugarRepository rssSubscriptionRepository, - ISqlSugarRepository rssMirrorItemRepository, - ISqlSugarRepository rssSourceRepository, - ILogger logger) - : base(currentUser, permissionChecker) - { - _rssSubscriptionDownloadRepository = rssSubscriptionDownloadRepository; - _rssSubscriptionRepository = rssSubscriptionRepository; - _rssMirrorItemRepository = rssMirrorItemRepository; - _rssSourceRepository = rssSourceRepository; - _logger = logger; - } - - /// - /// 获取下载记录分页列表 - /// - /// 查询请求DTO - /// 分页结果 - public async Task> GetListAsync(GetRssSubscriptionDownloadsRequestDto input) - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Default); - - var queryable = _rssSubscriptionDownloadRepository.GetQueryable(); - - // 应用过滤条件 - if (input.SubscriptionId.HasValue) - { - queryable = queryable.Where(x => x.SubscriptionId == input.SubscriptionId.Value); - } - - if (input.RssMirrorItemId.HasValue) - { - queryable = queryable.Where(x => x.RssMirrorItemId == input.RssMirrorItemId.Value); - } - - if (input.DownloadStatus.HasValue) - { - queryable = queryable.Where(x => x.DownloadStatus == input.DownloadStatus.Value); - } - - if (input.StartTime.HasValue) - { - queryable = queryable.Where(x => x.CreationTime >= input.StartTime.Value); - } - - if (input.EndTime.HasValue) - { - queryable = queryable.Where(x => x.CreationTime <= input.EndTime.Value); - } - - // 排序 - if (!string.IsNullOrWhiteSpace(input.Sorting)) - { - queryable = queryable.OrderBy(input.Sorting); - } - else - { - queryable = queryable.OrderByDescending(x => x.CreationTime); - } - - // 分页 - var totalCount = await queryable.CountAsync(); - var items = await queryable - .Skip(input.SkipCount) - .Take(input.MaxResultCount) - .ToListAsync(); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - var dtos = items.Select(MapToDto).ToList(); - - // 加载关联数据(替代导航属性查询) - var subscriptionIds = dtos.Select(d => d.SubscriptionId).Distinct().ToList(); - var mirrorItemIds = dtos.Select(d => d.RssMirrorItemId).Distinct().ToList(); - - var subscriptions = await _rssSubscriptionRepository.GetListAsync(s => subscriptionIds.Contains(s.Id)); - var mirrorItems = await _rssMirrorItemRepository.GetListAsync(i => mirrorItemIds.Contains(i.Id)); - - var sourceIds = mirrorItems.Select(i => i.RssSourceId).Distinct().ToList(); - var sources = await _rssSourceRepository.GetListAsync(s => sourceIds.Contains(s.Id)); - - // 填充关联名称 - foreach (var dto in dtos) - { - dto.SubscriptionName = subscriptions.FirstOrDefault(s => s.Id == dto.SubscriptionId)?.Name; - - var item = mirrorItems.FirstOrDefault(i => i.Id == dto.RssMirrorItemId); - dto.RssMirrorItemTitle = item?.Title; - dto.RssMirrorItemLink = item?.Link; - dto.RssSourceName = sources.FirstOrDefault(s => s.Id == item?.RssSourceId)?.Name; - - dto.DownloadStatusText = GetDownloadStatusText(dto.DownloadStatus); - } - - return new PagedResultDto(totalCount, dtos); - } - - /// - /// 根据ID获取下载记录 - /// - /// 下载记录ID - /// 下载记录DTO - public async Task GetAsync(long id) - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Default); - - var download = await _rssSubscriptionDownloadRepository.GetByIdAsync(id); - EnsureEntityExists(download, id); - - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - var dto = MapToDto(download!); - - // 加载关联数据(替代导航属性查询) - var subscription = await _rssSubscriptionRepository.GetFirstOrDefaultAsync(s => s.Id == download!.SubscriptionId); - dto.SubscriptionName = subscription?.Name; - - var item = await _rssMirrorItemRepository.GetFirstOrDefaultAsync(i => i.Id == download!.RssMirrorItemId); - dto.RssMirrorItemTitle = item?.Title; - dto.RssMirrorItemLink = item?.Link; - - if (item != null) - { - var source = await _rssSourceRepository.GetFirstOrDefaultAsync(s => s.Id == item.RssSourceId); - dto.RssSourceName = source?.Name; - } - - dto.DownloadStatusText = GetDownloadStatusText(dto.DownloadStatus); - - return dto; - } - - /// - /// 删除下载记录 - /// - /// 下载记录ID - public async Task DeleteAsync(long id) - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Delete); - - await _rssSubscriptionDownloadRepository.DeleteAsync(id); - _logger.LogInformation("删除订阅下载记录: {Id}", id); - } - - /// - /// 批量删除下载记录 - /// - /// 下载记录ID列表 - public async Task DeleteManyAsync(List ids) - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Delete); - - foreach (var id in ids) - { - await _rssSubscriptionDownloadRepository.DeleteAsync(id); - } - - _logger.LogInformation("批量删除订阅下载记录: {Count} 条", ids.Count); - } - - /// - /// 清空所有下载记录 - /// - public async Task ClearAllAsync() - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Delete); - - await _rssSubscriptionDownloadRepository.DeleteAsync(x => true); - _logger.LogInformation("清空所有订阅下载记录"); - } - - /// - /// 重试下载 - /// - /// 下载记录ID - public async Task RetryAsync(long id) - { - await CheckPermissionAsync(DFAppPermissions.RssSubscription.Default); - - var download = await _rssSubscriptionDownloadRepository.GetByIdAsync(id); - EnsureEntityExists(download, id); - - if (download!.DownloadStatus != 3) - { - throw new BusinessException("只能重试失败的下载任务"); - } - - // 先删除旧的下载记录,避免与后续创建的记录产生冲突 - await _rssSubscriptionDownloadRepository.DeleteAsync(id); - - // TODO: IRssSubscriptionService 未迁移,以下为伪代码 - // await _rssSubscriptionService.CreateDownloadTaskAsync(download.SubscriptionId, download.RssMirrorItemId); - _logger.LogWarning("IRssSubscriptionService 未迁移,重试下载功能暂不可用。SubscriptionId: {SubscriptionId}, RssMirrorItemId: {RssMirrorItemId}", - download.SubscriptionId, download.RssMirrorItemId); - - _logger.LogInformation("重试订阅下载: {Id}", id); - } - - /// - /// 获取下载状态文本 - /// - /// 状态码 - /// 状态文本 - private static string GetDownloadStatusText(int status) - { - return status switch - { - 0 => "待下载", - 1 => "下载中", - 2 => "下载完成", - 3 => "下载失败", - _ => "未知状态" - }; - } - - /// - /// 将 RssSubscriptionDownload 实体映射为 RssSubscriptionDownloadDto - /// - /// 下载记录实体 - /// 下载记录DTO - private static RssSubscriptionDownloadDto MapToDto(RssSubscriptionDownload entity) - { - // TODO: 使用 Mapperly 映射(命名空间冲突,暂保留手动映射) - return new RssSubscriptionDownloadDto - { - Id = entity.Id, - SubscriptionId = entity.SubscriptionId, - RssMirrorItemId = entity.RssMirrorItemId, - Aria2Gid = entity.Aria2Gid, - DownloadStatus = entity.DownloadStatus, - IsPendingDueToLowDiskSpace = entity.IsPendingDueToLowDiskSpace, - ErrorMessage = entity.ErrorMessage, - DownloadStartTime = entity.DownloadStartTime, - DownloadCompleteTime = entity.DownloadCompleteTime, - CreationTime = entity.CreationTime - }; - } -} diff --git a/src/DFApp.Web/Services/Rss/RssSubscriptionMatchResult.cs b/src/DFApp.Web/Services/Rss/RssSubscriptionMatchResult.cs deleted file mode 100644 index 688c7ce2..00000000 --- a/src/DFApp.Web/Services/Rss/RssSubscriptionMatchResult.cs +++ /dev/null @@ -1,28 +0,0 @@ -namespace DFApp.Web.Services.Rss -{ - /// - /// RSS订阅匹配结果 - /// - public class RssSubscriptionMatchResult - { - /// - /// 订阅ID - /// - public long SubscriptionId { get; set; } - - /// - /// 订阅名称 - /// - public string SubscriptionName { get; set; } = string.Empty; - - /// - /// 是否匹配 - /// - public bool Matched { get; set; } - - /// - /// 匹配原因 - /// - public string MatchReason { get; set; } = string.Empty; - } -} diff --git a/src/DFApp.Web/Services/Rss/RssSubscriptionService.cs b/src/DFApp.Web/Services/Rss/RssSubscriptionService.cs deleted file mode 100644 index 837f1f8c..00000000 --- a/src/DFApp.Web/Services/Rss/RssSubscriptionService.cs +++ /dev/null @@ -1,344 +0,0 @@ -using System; -using System.Collections.Generic; -using System.IO; -using System.Linq; -using System.Threading.Tasks; -using DFApp.Rss; -using DFApp.Web.Data; -using Microsoft.Extensions.Logging; - -namespace DFApp.Web.Services.Rss; - -/// -/// RSS订阅服务 - 负责订阅匹配、下载任务创建和暂存下载处理 -/// -public class RssSubscriptionService : IRssSubscriptionService -{ - private readonly ILogger _logger; - private readonly ISqlSugarRepository _rssSubscriptionRepository; - private readonly ISqlSugarRepository _rssMirrorItemRepository; - private readonly ISqlSugarRepository _rssSubscriptionDownloadRepository; - - // TODO: IAria2Service 未迁移,暂时使用 object? 替代 - private readonly object? _aria2Service; - - /// - /// 最小磁盘空间(GB) - /// - private const long MinDiskSpaceGB = 2; - - /// - /// 最小磁盘空间(字节) - /// - private const long MinDiskSpaceBytes = MinDiskSpaceGB * 1024 * 1024 * 1024; - - public RssSubscriptionService( - ILogger logger, - ISqlSugarRepository rssSubscriptionRepository, - ISqlSugarRepository rssMirrorItemRepository, - ISqlSugarRepository rssSubscriptionDownloadRepository, - object? aria2Service = null) - { - _logger = logger; - _rssSubscriptionRepository = rssSubscriptionRepository; - _rssMirrorItemRepository = rssMirrorItemRepository; - _rssSubscriptionDownloadRepository = rssSubscriptionDownloadRepository; - _aria2Service = aria2Service; - } - - /// - /// 匹配RSS镜像条目与所有启用的订阅规则 - /// - /// RSS镜像条目 - /// 每个订阅的匹配结果列表 - public async Task> MatchSubscriptionsAsync(RssMirrorItem item) - { - var results = new List(); - - var enabledSubscriptions = await _rssSubscriptionRepository.GetListAsync(s => s.IsEnabled); - - foreach (var subscription in enabledSubscriptions) - { - var result = new RssSubscriptionMatchResult - { - SubscriptionId = subscription.Id, - SubscriptionName = subscription.Name - }; - - // 检查RSS源是否匹配 - if (subscription.RssSourceId.HasValue && subscription.RssSourceId != item.RssSourceId) - { - result.Matched = false; - result.MatchReason = "RSS源不匹配"; - results.Add(result); - continue; - } - - // 检查发布日期是否在订阅日期范围内 - if (subscription.StartDate.HasValue && item.PublishDate < subscription.StartDate) - { - result.Matched = false; - result.MatchReason = "早于开始日期"; - results.Add(result); - continue; - } - - if (subscription.EndDate.HasValue && item.PublishDate > subscription.EndDate) - { - result.Matched = false; - result.MatchReason = "晚于结束日期"; - results.Add(result); - continue; - } - - // 检查关键词匹配 - var keywords = subscription.Keywords.Split(',', StringSplitOptions.RemoveEmptyEntries); - bool keywordMatched = keywords.Any(k => - item.Title.Contains(k.Trim(), StringComparison.OrdinalIgnoreCase)); - - if (!keywordMatched) - { - result.Matched = false; - result.MatchReason = "关键词不匹配"; - results.Add(result); - continue; - } - - // 检查质量过滤 - if (!string.IsNullOrEmpty(subscription.QualityFilter) && - !item.Title.Contains(subscription.QualityFilter, StringComparison.OrdinalIgnoreCase)) - { - result.Matched = false; - result.MatchReason = "质量过滤不匹配"; - results.Add(result); - continue; - } - - // 检查字幕组过滤 - if (!string.IsNullOrEmpty(subscription.SubtitleGroupFilter)) - { - var groups = subscription.SubtitleGroupFilter.Split(',', StringSplitOptions.RemoveEmptyEntries); - bool groupMatched = groups.Any(g => - item.Title.Contains(g.Trim(), StringComparison.OrdinalIgnoreCase)); - if (!groupMatched) - { - result.Matched = false; - result.MatchReason = "字幕组不匹配"; - results.Add(result); - continue; - } - } - - // 检查做种者数量范围 - if (subscription.MinSeeders.HasValue && (!item.Seeders.HasValue || item.Seeders < subscription.MinSeeders)) - { - result.Matched = false; - result.MatchReason = "做种者数量不足"; - results.Add(result); - continue; - } - - if (subscription.MaxSeeders.HasValue && (!item.Seeders.HasValue || item.Seeders > subscription.MaxSeeders)) - { - result.Matched = false; - result.MatchReason = "做种者数量过多"; - results.Add(result); - continue; - } - - // 检查下载者数量范围 - if (subscription.MinLeechers.HasValue && (!item.Leechers.HasValue || item.Leechers < subscription.MinLeechers)) - { - result.Matched = false; - result.MatchReason = "下载者数量不足"; - results.Add(result); - continue; - } - - if (subscription.MaxLeechers.HasValue && (!item.Leechers.HasValue || item.Leechers > subscription.MaxLeechers)) - { - result.Matched = false; - result.MatchReason = "下载者数量过多"; - results.Add(result); - continue; - } - - // 检查完成下载数量范围 - if (subscription.MinDownloads.HasValue && (!item.Downloads.HasValue || item.Downloads < subscription.MinDownloads)) - { - result.Matched = false; - result.MatchReason = "完成下载数量不足"; - results.Add(result); - continue; - } - - if (subscription.MaxDownloads.HasValue && (!item.Downloads.HasValue || item.Downloads > subscription.MaxDownloads)) - { - result.Matched = false; - result.MatchReason = "完成下载数量过多"; - results.Add(result); - continue; - } - - result.Matched = true; - result.MatchReason = "匹配成功"; - results.Add(result); - } - - return results; - } - - /// - /// 根据订阅ID和镜像条目ID创建下载任务 - /// - /// 订阅ID - /// RSS镜像条目ID - public async Task CreateDownloadTaskAsync(long subscriptionId, long rssMirrorItemId) - { - var subscription = await _rssSubscriptionRepository.GetByIdAsync(subscriptionId) - ?? throw new InvalidOperationException($"订阅 {subscriptionId} 不存在"); - var item = await _rssMirrorItemRepository.GetByIdAsync(rssMirrorItemId) - ?? throw new InvalidOperationException($"RSS镜像条目 {rssMirrorItemId} 不存在"); - - // 检查是否已存在下载记录 - var existingDownload = await _rssSubscriptionDownloadRepository.GetFirstOrDefaultAsync( - d => d.SubscriptionId == subscriptionId && d.RssMirrorItemId == rssMirrorItemId); - - if (existingDownload != null) - { - _logger.LogInformation("订阅 {SubscriptionName} 的下载任务已存在: {Title}", - subscription.Name, item.Title); - return; - } - - var availableSpace = GetAvailableDiskSpace(); - - // 磁盘空间不足时暂存下载记录 - if (availableSpace < MinDiskSpaceBytes) - { - _logger.LogWarning("磁盘空间不足 {MinGB} GB,暂存订阅 {SubscriptionName} 的下载: {Title}", - MinDiskSpaceGB, subscription.Name, item.Title); - - var pendingRecord = new RssSubscriptionDownload - { - SubscriptionId = subscriptionId, - RssMirrorItemId = rssMirrorItemId, - Aria2Gid = string.Empty, - DownloadStatus = 0, - IsPendingDueToLowDiskSpace = true, - CreationTime = DateTime.Now - }; - - await _rssSubscriptionDownloadRepository.InsertAsync(pendingRecord); - return; - } - - // TODO: IAria2Service 未迁移,以下为伪代码 - // var downloadRequest = new AddDownloadRequestDto - // { - // Urls = new List { item.Link }, - // VideoOnly = subscription.VideoOnly, - // EnableKeywordFilter = subscription.EnableKeywordFilter, - // SavePath = subscription.SavePath - // }; - // var result = await _aria2Service.AddDownloadAsync(downloadRequest); - - var downloadRecord = new RssSubscriptionDownload - { - SubscriptionId = subscriptionId, - RssMirrorItemId = rssMirrorItemId, - Aria2Gid = string.Empty, // TODO: 替换为 result.Id - DownloadStatus = 1, - DownloadStartTime = DateTime.Now, - CreationTime = DateTime.Now - }; - - await _rssSubscriptionDownloadRepository.InsertAsync(downloadRecord); - - _logger.LogInformation("订阅 {SubscriptionName} 自动下载: {Title}", - subscription.Name, item.Title); - } - - /// - /// 处理因磁盘空间不足而暂存的下载任务 - /// - public async Task ProcessPendingDownloadsAsync() - { - var availableSpace = GetAvailableDiskSpace(); - - if (availableSpace < MinDiskSpaceBytes) - { - _logger.LogInformation("磁盘空间不足 {MinGB} GB,跳过暂存下载处理", MinDiskSpaceGB); - return; - } - - var pendingDownloads = await _rssSubscriptionDownloadRepository.GetListAsync( - d => d.IsPendingDueToLowDiskSpace && d.DownloadStatus == 0); - - if (!pendingDownloads.Any()) - { - return; - } - - _logger.LogInformation("找到 {Count} 个暂存的下载任务", pendingDownloads.Count); - - foreach (var download in pendingDownloads) - { - try - { - var subscription = await _rssSubscriptionRepository.GetByIdAsync(download.SubscriptionId); - var item = await _rssMirrorItemRepository.GetByIdAsync(download.RssMirrorItemId); - - if (subscription == null || item == null) - { - _logger.LogWarning("暂存下载 {Id} 关联的订阅或镜像条目不存在,跳过", download.Id); - continue; - } - - // TODO: IAria2Service 未迁移,以下为伪代码 - // var downloadRequest = new AddDownloadRequestDto - // { - // Urls = new List { item.Link }, - // VideoOnly = subscription.VideoOnly, - // EnableKeywordFilter = subscription.EnableKeywordFilter, - // SavePath = subscription.SavePath - // }; - // var result = await _aria2Service.AddDownloadAsync(downloadRequest); - - download.Aria2Gid = string.Empty; // TODO: 替换为 result.Id - download.DownloadStatus = 1; - download.DownloadStartTime = DateTime.Now; - download.IsPendingDueToLowDiskSpace = false; - - await _rssSubscriptionDownloadRepository.UpdateAsync(download); - - _logger.LogInformation("已处理暂存下载: {Id}", download.Id); - } - catch (Exception ex) - { - _logger.LogError(ex, "处理暂存下载失败: {Id}", download.Id); - } - } - - _logger.LogInformation("已处理 {Count} 个暂存下载任务", pendingDownloads.Count); - } - - /// - /// 获取当前磁盘可用空间 - /// - /// 可用空间字节数 - private long GetAvailableDiskSpace() - { - try - { - var currentDirectory = Directory.GetCurrentDirectory(); - var driveInfo = new DriveInfo(Path.GetPathRoot(currentDirectory)!); - return driveInfo.AvailableFreeSpace; - } - catch (Exception ex) - { - _logger.LogError(ex, "获取磁盘空间失败"); - return 0; - } - } -}