From 2b31a355aefb01768fee9db9eef2730a1f6a86cc Mon Sep 17 00:00:00 2001
From: CJK_mkp <113243675+CJKmkp@users.noreply.github.com>
Date: Sun, 2 Nov 2025 10:30:36 +0800
Subject: [PATCH] =?UTF-8?q?add:Dlass=E8=81=94=E5=8A=A8?=
MIME-Version: 1.0
Content-Type: text/plain; charset=UTF-8
Content-Transfer-Encoding: 8bit
---
Ink Canvas/Helpers/DlassApiClient.cs | 2 +-
Ink Canvas/Helpers/DlassNoteUploader.cs | 301 +++++++++++++++++++-----
2 files changed, 239 insertions(+), 64 deletions(-)
diff --git a/Ink Canvas/Helpers/DlassApiClient.cs b/Ink Canvas/Helpers/DlassApiClient.cs
index 61a04f42..d77bed80 100644
--- a/Ink Canvas/Helpers/DlassApiClient.cs
+++ b/Ink Canvas/Helpers/DlassApiClient.cs
@@ -12,7 +12,7 @@ namespace Ink_Canvas.Helpers
///
/// Dlass API 客户端,用于与服务端通信
///
- public class DlassApiClient
+ public class DlassApiClient : IDisposable
{
private const string DEFAULT_BASE_URL = "https://dlass.tech";
private readonly string _appId;
diff --git a/Ink Canvas/Helpers/DlassNoteUploader.cs b/Ink Canvas/Helpers/DlassNoteUploader.cs
index 30da0f51..0d67e3a2 100644
--- a/Ink Canvas/Helpers/DlassNoteUploader.cs
+++ b/Ink Canvas/Helpers/DlassNoteUploader.cs
@@ -1,9 +1,11 @@
using Ink_Canvas.Helpers;
using Newtonsoft.Json;
using System;
+using System.Collections.Concurrent;
using System.Collections.Generic;
using System.IO;
using System.Linq;
+using System.Threading;
using System.Threading.Tasks;
namespace Ink_Canvas.Helpers
@@ -15,6 +17,17 @@ namespace Ink_Canvas.Helpers
{
private const string APP_ID = "app_WkjocWqsrVY7T6zQV2CfiA";
private const string APP_SECRET = "o7dx5b5ASGUMcM72PCpmRQYAhSijqaOVHoGyBK0IxbA";
+ private const int BATCH_SIZE = 10; // 批量上传大小
+
+ ///
+ /// 上传队列(线程安全)
+ ///
+ private static readonly ConcurrentQueue _uploadQueue = new ConcurrentQueue();
+
+ ///
+ /// 队列处理锁,防止并发处理
+ ///
+ private static readonly SemaphoreSlim _queueProcessingLock = new SemaphoreSlim(1, 1);
///
/// 上传笔记响应模型
@@ -80,18 +93,8 @@ namespace Ink_Canvas.Helpers
/// 异步上传笔记文件到Dlass(支持PNG和ICSTK格式)
///
/// 文件路径(支持PNG和ICSTK)
- /// 是否上传成功
+ /// 是否成功加入队列(不等待实际上传完成)
public static async Task UploadNoteFileAsync(string filePath)
- {
- return await UploadPngNoteAsync(filePath);
- }
-
- ///
- /// 异步上传PNG文件到Dlass
- ///
- /// PNG文件路径
- /// 是否上传成功
- public static async Task UploadPngNoteAsync(string pngFilePath)
{
try
{
@@ -101,91 +104,267 @@ namespace Ink_Canvas.Helpers
return false;
}
- // 检查文件是否存在
- if (!File.Exists(pngFilePath))
+ // 基本验证
+ if (!File.Exists(filePath))
{
- LogHelper.WriteLogToFile($"上传失败:文件不存在 - {pngFilePath}", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile($"上传失败:文件不存在 - {filePath}", LogHelper.LogType.Error);
return false;
}
- // 检查文件扩展名
- var fileExtension = Path.GetExtension(pngFilePath).ToLower();
+ var fileExtension = Path.GetExtension(filePath).ToLower();
if (fileExtension != ".png" && fileExtension != ".icstk")
{
- LogHelper.WriteLogToFile($"上传失败:不支持的文件格式 - {fileExtension},仅支持PNG和ICSTK", LogHelper.LogType.Error);
return false;
}
- // 检查文件大小(最大10MB)
- var fileInfo = new FileInfo(pngFilePath);
+ var fileInfo = new FileInfo(filePath);
if (fileInfo.Length > 10 * 1024 * 1024)
{
LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过10MB限制", LogHelper.LogType.Error);
return false;
}
- // 获取设置的班级名称
- var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
- if (string.IsNullOrEmpty(selectedClassName))
+ // 获取上传延迟时间(分钟)
+ var delayMinutes = MainWindow.Settings?.Dlass?.AutoUploadDelayMinutes ?? 0;
+
+ // 如果设置了延迟时间,在后台任务中等待后再加入队列
+ if (delayMinutes > 0)
{
- LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
- return false;
+ _ = Task.Run(async () =>
+ {
+ await Task.Delay(TimeSpan.FromMinutes(delayMinutes));
+ EnqueueFile(filePath);
+ });
+ }
+ else
+ {
+ EnqueueFile(filePath);
}
- // 获取用户Token
- var userToken = MainWindow.Settings?.Dlass?.UserToken;
- if (string.IsNullOrEmpty(userToken))
+ return true;
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"加入上传队列时出错: {ex.Message}", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+
+ ///
+ /// 将文件加入上传队列
+ ///
+ private static void EnqueueFile(string filePath)
+ {
+ _uploadQueue.Enqueue(filePath);
+
+ // 如果队列达到批量大小,触发批量上传
+ if (_uploadQueue.Count >= BATCH_SIZE)
+ {
+ _ = ProcessUploadQueueAsync();
+ }
+ }
+
+ ///
+ /// 处理上传队列,批量上传文件
+ ///
+ private static async Task ProcessUploadQueueAsync()
+ {
+ // 使用信号量防止并发处理
+ if (!await _queueProcessingLock.WaitAsync(0))
+ {
+ return; // 已有处理任务在运行
+ }
+
+ try
+ {
+ var filesToUpload = new List();
+
+ // 从队列中取出最多BATCH_SIZE个文件
+ while (filesToUpload.Count < BATCH_SIZE && _uploadQueue.TryDequeue(out string filePath))
{
- LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
- return false;
+ // 再次检查文件是否存在(可能在队列中时被删除)
+ if (File.Exists(filePath))
+ {
+ filesToUpload.Add(filePath);
+ }
}
- // 获取API基础URL
- var apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
+ if (filesToUpload.Count == 0)
+ {
+ return;
+ }
- // 创建API客户端并获取白板信息
- DlassApiClient apiClient = null;
+ // 获取共享的白板信息(同一批次的所有文件共享认证信息)
+ WhiteboardInfo sharedWhiteboard = null;
+ string apiBaseUrl = null;
+ string userToken = null;
+
try
{
- apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken);
-
- // 调用认证接口获取白板列表
- var authData = new
+ var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
+ if (string.IsNullOrEmpty(selectedClassName))
{
- app_id = APP_ID,
- app_secret = APP_SECRET,
- user_token = userToken
- };
+ LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
+ return;
+ }
- var authResult = await apiClient.PostAsync("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
-
- if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
+ userToken = MainWindow.Settings?.Dlass?.UserToken;
+ if (string.IsNullOrEmpty(userToken))
{
- LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
+ return;
+ }
+
+ apiBaseUrl = MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
+
+ // 获取白板信息(只获取一次,所有文件共享)
+ using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
+ {
+ var authData = new
+ {
+ app_id = APP_ID,
+ app_secret = APP_SECRET,
+ user_token = userToken
+ };
+
+ var authResult = await apiClient.PostAsync("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
+
+ if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
+ {
+ LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
+ return;
+ }
+
+ sharedWhiteboard = authResult.Whiteboards
+ .FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
+
+ if (sharedWhiteboard == null || string.IsNullOrEmpty(sharedWhiteboard.BoardId) || string.IsNullOrEmpty(sharedWhiteboard.SecretKey))
+ {
+ LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
+ return;
+ }
+ }
+ }
+ catch (Exception ex)
+ {
+ LogHelper.WriteLogToFile($"批量上传获取白板信息时出错: {ex.Message}", LogHelper.LogType.Error);
+ return;
+ }
+
+ // 并发上传所有文件(共享白板信息)
+ var uploadTasks = filesToUpload.Select(filePath => UploadFileInternalAsync(filePath, sharedWhiteboard, apiBaseUrl, userToken));
+ await Task.WhenAll(uploadTasks);
+
+ // 如果队列中还有文件,继续处理
+ if (_uploadQueue.Count >= BATCH_SIZE)
+ {
+ _ = ProcessUploadQueueAsync();
+ }
+ }
+ finally
+ {
+ _queueProcessingLock.Release();
+ }
+ }
+
+ ///
+ /// 内部上传方法,执行实际上传操作
+ ///
+ /// 文件路径
+ /// 白板信息(如果为null则重新获取)
+ /// API基础URL(如果为null则从设置获取)
+ /// 用户Token(如果为null则从设置获取)
+ private static async Task UploadFileInternalAsync(string filePath, WhiteboardInfo whiteboard = null, string apiBaseUrl = null, string userToken = null)
+ {
+ try
+ {
+ // 再次检查文件是否存在(可能在队列等待时被删除)
+ if (!File.Exists(filePath))
+ {
+ return false;
+ }
+
+ // 检查文件扩展名
+ var fileExtension = Path.GetExtension(filePath).ToLower();
+ if (fileExtension != ".png" && fileExtension != ".icstk")
+ {
+ return false;
+ }
+
+ // 检查文件大小(最大10MB)
+ var fileInfo = new FileInfo(filePath);
+ if (fileInfo.Length > 10 * 1024 * 1024)
+ {
+ LogHelper.WriteLogToFile($"上传失败:文件过大({fileInfo.Length / 1024 / 1024}MB),超过10MB限制", LogHelper.LogType.Error);
+ return false;
+ }
+
+ // 如果白板信息未提供,则重新获取
+ if (whiteboard == null)
+ {
+ var selectedClassName = MainWindow.Settings?.Dlass?.SelectedClassName;
+ if (string.IsNullOrEmpty(selectedClassName))
+ {
+ LogHelper.WriteLogToFile("上传失败:未选择班级", LogHelper.LogType.Error);
return false;
}
- // 查找匹配班级的白板
- var whiteboard = authResult.Whiteboards
- .FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
-
- if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
+ userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
+ if (string.IsNullOrEmpty(userToken))
{
- LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
+ LogHelper.WriteLogToFile("上传失败:未设置用户Token", LogHelper.LogType.Error);
return false;
}
- // 准备上传参数
- var fileName = Path.GetFileNameWithoutExtension(pngFilePath);
- var title = fileName;
- var fileType = fileExtension == ".icstk" ? "墨迹文件" : "笔记";
- var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
- var tags = fileExtension == ".icstk" ? "自动上传,墨迹,icstk" : "自动上传,笔记,png";
+ apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
- // 上传文件
+ // 创建API客户端并获取白板信息
+ using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
+ {
+ var authData = new
+ {
+ app_id = APP_ID,
+ app_secret = APP_SECRET,
+ user_token = userToken
+ };
+
+ var authResult = await apiClient.PostAsync("/api/whiteboard/framework/auth-with-token", authData, requireAuth: false);
+
+ if (authResult == null || !authResult.Success || authResult.Whiteboards == null)
+ {
+ LogHelper.WriteLogToFile("上传失败:无法获取白板信息", LogHelper.LogType.Error);
+ return false;
+ }
+
+ // 查找匹配班级的白板
+ whiteboard = authResult.Whiteboards
+ .FirstOrDefault(w => !string.IsNullOrEmpty(w.ClassName) && w.ClassName == selectedClassName);
+
+ if (whiteboard == null || string.IsNullOrEmpty(whiteboard.BoardId) || string.IsNullOrEmpty(whiteboard.SecretKey))
+ {
+ LogHelper.WriteLogToFile($"上传失败:未找到班级'{selectedClassName}'对应的白板", LogHelper.LogType.Error);
+ return false;
+ }
+ }
+ }
+
+ // 获取API基础URL和用户Token(如果未提供)
+ apiBaseUrl = apiBaseUrl ?? MainWindow.Settings?.Dlass?.ApiBaseUrl ?? "https://dlass.tech";
+ userToken = userToken ?? MainWindow.Settings?.Dlass?.UserToken;
+
+ // 准备上传参数
+ var fileName = Path.GetFileNameWithoutExtension(filePath);
+ var title = fileName;
+ var fileType = fileExtension == ".icstk" ? "墨迹文件" : "笔记";
+ var description = $"自动上传的{fileType} - {DateTime.Now:yyyy-MM-dd HH:mm:ss}";
+ var tags = fileExtension == ".icstk" ? "自动上传,墨迹,icstk" : "自动上传,笔记,png";
+
+ // 创建API客户端并上传文件
+ using (var apiClient = new DlassApiClient(APP_ID, APP_SECRET, apiBaseUrl, userToken))
+ {
var uploadResult = await apiClient.UploadNoteAsync(
"/api/whiteboard/upload_note",
- pngFilePath,
+ filePath,
whiteboard.BoardId,
whiteboard.SecretKey,
title,
@@ -203,10 +382,6 @@ namespace Ink_Canvas.Helpers
return false;
}
}
- finally
- {
- apiClient?.Dispose();
- }
}
catch (Exception ex)
{