diff --git a/README.md b/README.md index 6c5a98e..cfb09d9 100644 --- a/README.md +++ b/README.md @@ -31,21 +31,27 @@ ## 近期新增功能 -- 🧠 任务中心增强:持续任务支持暂停、编辑、重新运行;任务列表区分执行中与历史任务;支持“仅清理历史任务”与“历史任务保留上限”自动清理 -- 💬 持续活跃任务升级:新增账号持续活跃(群组/频道)能力,支持多分类账号、随机文案、秒级发送间隔、持续运行配置 -- 🔄 同步体验优化:手动“立即同步”改为后台任务执行,可在任务中心跟踪进度;同步链路补充超时保护与状态持久化 -- 👤 账号列表增强:新增注册时间(估算,非百分百正确)展示,并可直接从账号操作中查看该账号已加入的频道和群组 -- 📺 频道管理升级:频道列表改为面向“已加入频道”视角,不再只显示系统创建频道;支持按账号、角色、分类筛选,支持查看频道内的本系统账号 -- 👥 群组管理补齐:新增群组创建页、群组分类、批量操作与列表能力补齐;群组列表同样支持按账号、角色、分类筛选,并支持查看群组内的本系统账号 -- 🔗 多账号关系可视化:同一频道/群组可绑定多个系统账号,频道/群组列表与详情页都可查看当前关联的系统账号、角色与状态 -- 🚪 真实退出/解散能力:频道与群组支持单个/批量退出;支持解散频道/群组,且解散前需后台密码确认 -- 🧹 数据准确性修正:修复频道列表混入群组的问题,并优化账号、频道、群组关系同步后的本地展示逻辑 -- ♻️ 同步残留自动清理:同步完成后自动清理失效关联、创建者残留和孤儿频道/群组记录,避免数据库持续臃肿 -- ⚡ 数据层优化:补充频道/群组查询与关系索引,提升几百账号、上千频道/群组场景下的筛选与加载性能 +- 🧠 AI 验证接入:持续活跃任务支持识别验证消息后自动点击按钮或文本作答 +- ⚙️ AI 设置增强:OpenAI 兼容端点、API Key、默认/预设模型、一键连通测试 +- 🔁 AI 稳定性:支持配置失败重试次数,AI 决策/作答/连通测试统一复用 +- 📚 数据字典能力:支持文本字典/图片字典与模板变量 +- 🕒 定时任务能力:新增定时频道/群组相关任务(创建/公开等) +- 🧠 任务中心增强:持续任务支持暂停、编辑、重新运行;任务列表区分执行中与历史任务;支持自动清理 +- 💬 持续活跃任务升级:支持多分类账号、随机文案、秒级发送间隔、持续运行配置 +- 🔄 同步体验优化:手动“立即同步”改为后台任务执行,可在任务中心跟踪进度 +- 👤 账号列表增强:新增注册时间(估算)展示,并可查看账号已加入的频道/群组 +- 📺 频道管理升级:频道列表改为“已加入频道”视角,支持多条件筛选与关联账号展示 +- 👥 群组管理补齐:新增群组创建、分类、批量操作与列表能力 +- 🔗 多账号关系可视化:频道/群组可绑定多个系统账号,列表与详情可查看关联状态 +- 🚪 真实退出/解散能力:频道与群组支持单个/批量退出与解散 +- 🧹 数据准确性修正:修复频道列表混入群组的问题,优化关系同步后的展示 +- ♻️ 同步残留清理:同步完成后自动清理失效关联与孤儿记录 +- ⚡ 数据层优化:补充查询与关系索引,提升大量账号/频道/群组场景的筛选性能 ## TODO(规划) - [x] 一键退群/退订、订阅(频道/群组) +- [x] 批量自动签到 - [ ] 一键清空联系人 - [ ] 批量手机号验证码重新登录(用于刷新会话 session) - [ ] 手机号注册:未注册号支持完整注册流程(姓名/可选邮箱/邮箱验证码等) diff --git a/docs/getting-started/installation.md b/docs/getting-started/installation.md index abe960b..5e94380 100644 --- a/docs/getting-started/installation.md +++ b/docs/getting-started/installation.md @@ -61,6 +61,7 @@ docker compose up -d - Sessions:`./docker-data/sessions/` - 系统设置本地覆盖:`./docker-data/appsettings.local.json` - 后台登录凭据文件:`./docker-data/admin_auth.json` +- 图片资产:`./docker-data/uploads/` 更多说明见:[配置与数据目录](../reference/configuration.md)。 diff --git a/docs/reference/configuration.md b/docs/reference/configuration.md index 02534b0..f07243b 100644 --- a/docs/reference/configuration.md +++ b/docs/reference/configuration.md @@ -15,6 +15,7 @@ - `/data/sessions/`:账号 session 文件 - `/data/appsettings.local.json`:UI 保存后的本地覆盖配置 - `/data/admin_auth.json`:后台登录账号/密码(首次会用初始默认值生成) +- `/data/uploads/`:图片资产(数据字典图片、头像素材等) ## 后台任务(刷新页面不影响) diff --git a/src/TelegramPanel.Web/Program.cs b/src/TelegramPanel.Web/Program.cs index 85345c6..3339a56 100644 --- a/src/TelegramPanel.Web/Program.cs +++ b/src/TelegramPanel.Web/Program.cs @@ -16,6 +16,7 @@ using TelegramPanel.Web.Services; using Microsoft.Extensions.Logging; using Microsoft.AspNetCore.DataProtection; using Microsoft.Data.Sqlite; +using Microsoft.Extensions.FileProviders; using System.Diagnostics; // 诊断:对某个目录下的 *.json/*.session 做一次“可转换/可校验”检查(不写数据库) @@ -726,6 +727,16 @@ if (hasHttpsEndpoint) // 静态文件(包括 MudBlazor 等 NuGet 包的静态 Web 资源) app.UseStaticFiles(); +if (Directory.Exists("/data")) +{ + var uploadsRoot = "/data/uploads"; + Directory.CreateDirectory(uploadsRoot); + app.UseStaticFiles(new StaticFileOptions + { + FileProvider = new PhysicalFileProvider(uploadsRoot), + RequestPath = "/uploads" + }); +} app.UseAntiforgery(); diff --git a/src/TelegramPanel.Web/Services/ImageAssetStorageService.cs b/src/TelegramPanel.Web/Services/ImageAssetStorageService.cs index f5705a0..9a2e8d4 100644 --- a/src/TelegramPanel.Web/Services/ImageAssetStorageService.cs +++ b/src/TelegramPanel.Web/Services/ImageAssetStorageService.cs @@ -50,7 +50,7 @@ public sealed class ImageAssetStorageService var extension = ".jpg"; var relativeDir = Path.Combine("uploads", scope).Replace('\\', '/'); - var root = GetWebRootPath(); + var root = GetStorageRootPath(); var fullDir = Path.Combine(root, "uploads", scope.Replace('/', Path.DirectorySeparatorChar)); Directory.CreateDirectory(fullDir); @@ -84,7 +84,7 @@ public sealed class ImageAssetStorageService public Task OpenReadAsync(string assetPath, CancellationToken cancellationToken = default) { assetPath = NormalizeAssetPath(assetPath); - var fullPath = Path.Combine(GetWebRootPath(), assetPath.Replace('/', Path.DirectorySeparatorChar)); + var fullPath = Path.Combine(GetStorageRootPath(), assetPath.Replace('/', Path.DirectorySeparatorChar)); if (!File.Exists(fullPath)) throw new FileNotFoundException("图片资产不存在", fullPath); @@ -98,7 +98,7 @@ public sealed class ImageAssetStorageService if (assetPath.Length == 0) return Task.CompletedTask; - var fullPath = Path.Combine(GetWebRootPath(), assetPath.Replace('/', Path.DirectorySeparatorChar)); + var fullPath = Path.Combine(GetStorageRootPath(), assetPath.Replace('/', Path.DirectorySeparatorChar)); try { if (File.Exists(fullPath)) @@ -118,7 +118,7 @@ public sealed class ImageAssetStorageService if (scope.Length == 0) return Task.CompletedTask; - var fullDir = Path.Combine(GetWebRootPath(), "uploads", scope.Replace('/', Path.DirectorySeparatorChar)); + var fullDir = Path.Combine(GetStorageRootPath(), "uploads", scope.Replace('/', Path.DirectorySeparatorChar)); try { if (Directory.Exists(fullDir)) @@ -132,8 +132,14 @@ public sealed class ImageAssetStorageService return Task.CompletedTask; } - private string GetWebRootPath() + private string GetStorageRootPath() { + if (Directory.Exists("/data")) + { + Directory.CreateDirectory("/data/uploads"); + return "/data"; + } + var path = _environment.WebRootPath; if (string.IsNullOrWhiteSpace(path)) path = Path.Combine(AppContext.BaseDirectory, "wwwroot");