fix: persist image assets under /data
This commit is contained in:
1 parent
fb08a5806c
commit
7ce4f42927
5 files changed
+41
-16
No files matched your search
@@ -31,21 +31,27 @@
|
||||
|
||||
## 近期新增功能
|
||||
|
||||
- 🧠 任务中心增强:持续任务支持暂停、编辑、重新运行;任务列表区分执行中与历史任务;支持“仅清理历史任务”与“历史任务保留上限”自动清理
|
||||
- 💬 持续活跃任务升级:新增账号持续活跃(群组/频道)能力,支持多分类账号、随机文案、秒级发送间隔、持续运行配置
|
||||
- 🔄 同步体验优化:手动“立即同步”改为后台任务执行,可在任务中心跟踪进度;同步链路补充超时保护与状态持久化
|
||||
- 👤 账号列表增强:新增注册时间(估算,非百分百正确)展示,并可直接从账号操作中查看该账号已加入的频道和群组
|
||||
- 📺 频道管理升级:频道列表改为面向“已加入频道”视角,不再只显示系统创建频道;支持按账号、角色、分类筛选,支持查看频道内的本系统账号
|
||||
- 👥 群组管理补齐:新增群组创建页、群组分类、批量操作与列表能力补齐;群组列表同样支持按账号、角色、分类筛选,并支持查看群组内的本系统账号
|
||||
- 🔗 多账号关系可视化:同一频道/群组可绑定多个系统账号,频道/群组列表与详情页都可查看当前关联的系统账号、角色与状态
|
||||
- 🚪 真实退出/解散能力:频道与群组支持单个/批量退出;支持解散频道/群组,且解散前需后台密码确认
|
||||
- 🧹 数据准确性修正:修复频道列表混入群组的问题,并优化账号、频道、群组关系同步后的本地展示逻辑
|
||||
- ♻️ 同步残留自动清理:同步完成后自动清理失效关联、创建者残留和孤儿频道/群组记录,避免数据库持续臃肿
|
||||
- ⚡ 数据层优化:补充频道/群组查询与关系索引,提升几百账号、上千频道/群组场景下的筛选与加载性能
|
||||
- 🧠 AI 验证接入:持续活跃任务支持识别验证消息后自动点击按钮或文本作答
|
||||
- ⚙️ AI 设置增强:OpenAI 兼容端点、API Key、默认/预设模型、一键连通测试
|
||||
- 🔁 AI 稳定性:支持配置失败重试次数,AI 决策/作答/连通测试统一复用
|
||||
- 📚 数据字典能力:支持文本字典/图片字典与模板变量
|
||||
- 🕒 定时任务能力:新增定时频道/群组相关任务(创建/公开等)
|
||||
- 🧠 任务中心增强:持续任务支持暂停、编辑、重新运行;任务列表区分执行中与历史任务;支持自动清理
|
||||
- 💬 持续活跃任务升级:支持多分类账号、随机文案、秒级发送间隔、持续运行配置
|
||||
- 🔄 同步体验优化:手动“立即同步”改为后台任务执行,可在任务中心跟踪进度
|
||||
- 👤 账号列表增强:新增注册时间(估算)展示,并可查看账号已加入的频道/群组
|
||||
- 📺 频道管理升级:频道列表改为“已加入频道”视角,支持多条件筛选与关联账号展示
|
||||
- 👥 群组管理补齐:新增群组创建、分类、批量操作与列表能力
|
||||
- 🔗 多账号关系可视化:频道/群组可绑定多个系统账号,列表与详情可查看关联状态
|
||||
- 🚪 真实退出/解散能力:频道与群组支持单个/批量退出与解散
|
||||
- 🧹 数据准确性修正:修复频道列表混入群组的问题,优化关系同步后的展示
|
||||
- ♻️ 同步残留清理:同步完成后自动清理失效关联与孤儿记录
|
||||
- ⚡ 数据层优化:补充查询与关系索引,提升大量账号/频道/群组场景的筛选性能
|
||||
|
||||
## TODO(规划)
|
||||
|
||||
- [x] 一键退群/退订、订阅(频道/群组)
|
||||
- [x] 批量自动签到
|
||||
- [ ] 一键清空联系人
|
||||
- [ ] 批量手机号验证码重新登录(用于刷新会话 session)
|
||||
- [ ] 手机号注册:未注册号支持完整注册流程(姓名/可选邮箱/邮箱验证码等)
|
||||
|
||||
@@ -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)。
|
||||
|
||||
|
||||
@@ -15,6 +15,7 @@
|
||||
- `/data/sessions/`:账号 session 文件
|
||||
- `/data/appsettings.local.json`:UI 保存后的本地覆盖配置
|
||||
- `/data/admin_auth.json`:后台登录账号/密码(首次会用初始默认值生成)
|
||||
- `/data/uploads/`:图片资产(数据字典图片、头像素材等)
|
||||
|
||||
## 后台任务(刷新页面不影响)
|
||||
|
||||
|
||||
@@ -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();
|
||||
|
||||
|
||||
@@ -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<Stream> 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");
|
||||
|
||||
Reference in new issue
Block a user