From 964f97c115bb2cd97e434d4cc4a764892ef6cd8c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=B0=8F=E8=82=A5=E7=BE=8A?= <1048382248@qq.com> Date: Wed, 29 Oct 2025 18:11:21 +0800 Subject: [PATCH] =?UTF-8?q?=E4=BC=98=E5=8C=96=20=E4=BB=BB=E5=8A=A1?= =?UTF-8?q?=E6=97=A5=E5=BF=97=E6=89=93=E5=8D=B0=E5=8A=9F=E8=83=BD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Pages/EvaluationProject.razor.cs | 4 +- .../Pages/NodeSubscriptionPage.razor.cs | 4 +- .../Components/Pages/VideoTaskPage.razor.cs | 6 +- .../AICore/FFMPGE/FFMPGEHandle.cs | 6 +- VideoAnalysisCore/AICore/GPT/BserGPT.cs | 50 +- .../AICore/GPT/ChatGPT/ChatGPTClient.cs | 90 +--- .../AICore/GPT/ChatGPT/Chat_GPT.cs | 436 ------------------ .../AICore/GPT/DeepSeek/DeepSeekClient.cs | 60 +-- .../AICore/GPT/DeepSeek/DeepSeek_GPT.cs | 21 +- .../AICore/SherpaOnnx/SenseVoice.cs | 2 +- VideoAnalysisCore/Common/AppCommon.cs | 9 +- VideoAnalysisCore/Common/RedisExpand.cs | 89 +++- VideoAnalysisCore/Job/TaskFileClearJob.cs | 8 +- VideoAnalysisCore/Model/TaskLog.cs | 39 ++ 14 files changed, 202 insertions(+), 622 deletions(-) delete mode 100644 VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs create mode 100644 VideoAnalysisCore/Model/TaskLog.cs diff --git a/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs b/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs index 2aef2b7..0eefe33 100644 --- a/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs +++ b/VideoAnalysis/Components/Pages/EvaluationProject.razor.cs @@ -43,7 +43,7 @@ namespace Learn.VideoAnalysis.Components.Pages CourseGradingCriteria rowData; SubjectEnum editSubject; - async void SubjectEnumSelect() + async Task SubjectEnumSelect() { _editSource = await criteria.GetListAsync(x => x.Subject.Value == editSubject); await this.InvokeAsync(StateHasChanged); @@ -110,7 +110,7 @@ namespace Learn.VideoAnalysis.Components.Pages /// 分页 查询 筛选 时 /// /// - async void OnChange(QueryModel query) + async Task OnChange(QueryModel query) { tableLoading = true; List where = default!; diff --git a/VideoAnalysis/Components/Pages/NodeSubscriptionPage.razor.cs b/VideoAnalysis/Components/Pages/NodeSubscriptionPage.razor.cs index 302b79d..02a35dd 100644 --- a/VideoAnalysis/Components/Pages/NodeSubscriptionPage.razor.cs +++ b/VideoAnalysis/Components/Pages/NodeSubscriptionPage.razor.cs @@ -43,7 +43,7 @@ namespace Learn.VideoAnalysis.Components.Pages NodeSubscription rowData; SubjectEnum editSubject; - async void SubjectEnumSelect() + async Task SubjectEnumSelect() { _editSource = await criteria.GetListAsync(x => x.Subject == editSubject); await this.InvokeAsync(StateHasChanged); @@ -87,7 +87,7 @@ namespace Learn.VideoAnalysis.Components.Pages /// 分页 查询 筛选 时 /// /// - async void OnChange(QueryModel query) + async Task OnChange(QueryModel query) { tableLoading = true; List where = default!; diff --git a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs index a470b7a..de28a9c 100644 --- a/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs +++ b/VideoAnalysis/Components/Pages/VideoTaskPage.razor.cs @@ -53,7 +53,7 @@ namespace Learn.VideoAnalysis.Components.Pages /// 点击重试 /// /// - async void ReStartClick(VideoTaskDto query) + async Task ReStartClick(VideoTaskDto query) { selectDefaultValue = (await redisManager.Redis.HMGetAsync(RedisExpandKey.Task(query.Id), "LastEnum")).FirstOrDefault(); @@ -83,7 +83,7 @@ namespace Learn.VideoAnalysis.Components.Pages /// /// /// - async void ShowErrorTask(MouseEventArgs e) + async Task ShowErrorTask(MouseEventArgs e) { _dataSource = await taskDB.AsQueryable() .Where(s => s.ErrorMessage != null && s.ErrorMessage != string.Empty) @@ -98,7 +98,7 @@ namespace Learn.VideoAnalysis.Components.Pages /// 分页 查询 筛选 时 /// /// - async void OnChange(QueryModel query) + async Task OnChange(QueryModel query) { lastQuery = query; tableLoading = true; diff --git a/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs b/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs index fdc4445..2af657e 100644 --- a/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs +++ b/VideoAnalysisCore/AICore/FFMPGE/FFMPGEHandle.cs @@ -69,7 +69,7 @@ namespace VideoAnalysisCore.AICore.FFMPGE var filePath = Path.Combine(localPath, "ppt.mp4"); if (!File.Exists(filePath)) { - Console.WriteLine("存在PPT Code但未能找到对应资源文件"); + redisManager.AddTaskLog(task,"存在PPT Code但未能找到对应资源文件"); return; } var ffmpeg = new Engine(FFmpegPath); @@ -105,9 +105,9 @@ namespace VideoAnalysisCore.AICore.FFMPGE keyFrames.Add((int)timestamp); //string outputPath = Path.Combine(outputDir, $"change_{timestamp:0000}.jpg"); //currFrame.Save(outputPath); - //Console.WriteLine($"变化帧: {timestamp}秒,差异值: {ssim:F2}"); + //await redisManager.AddTaskLog(chatReq.taskId, $"变化帧: {timestamp}秒,差异值: {ssim:F2}"); } - //Console.WriteLine($"帧: {timestamp}秒,SSIM{ssim:F2} 差异值: {ssim:F2} "); + //await redisManager.AddTaskLog(chatReq.taskId, $"帧: {timestamp}秒,SSIM{ssim:F2} 差异值: {ssim:F2} "); } prevFrame?.Dispose(); diff --git a/VideoAnalysisCore/AICore/GPT/BserGPT.cs b/VideoAnalysisCore/AICore/GPT/BserGPT.cs index 297dff1..58d2902 100644 --- a/VideoAnalysisCore/AICore/GPT/BserGPT.cs +++ b/VideoAnalysisCore/AICore/GPT/BserGPT.cs @@ -1,4 +1,9 @@ -using VideoAnalysisCore.AICore.GPT.Dto; +using Microsoft.Extensions.Hosting; +using System.Net.Http.Headers; +using System.Net.Http; +using System.Net; +using System.Text; +using VideoAnalysisCore.AICore.GPT.Dto; using VideoAnalysisCore.AICore.SherpaOnnx; using VideoAnalysisCore.Common; using Whisper.net; @@ -168,4 +173,47 @@ namespace VideoAnalysisCore.AICore.GPT public string reasoning_content { get; set; } public string refusal { get; set; } } + public static class GPTHttpClientExp + { + public static async Task PostJsonStreamAsync( + this IHttpClientFactory _httpClientFactory, + string path, string json,string apiKey, bool readAll = false) + { + var uriBuilder = new UriBuilder(path); + var maxRestart = 4; + var errorMSG = new Exception[maxRestart]; + for (int i = 0; i < maxRestart; i++) + { + try + { + var client = _httpClientFactory.CreateClient(); + client.DefaultRequestHeaders.Authorization = + new AuthenticationHeaderValue("Bearer", apiKey); + client.Timeout = TimeSpan.FromSeconds(60 * 20);//超时时间20分钟 + client.DefaultRequestVersion = HttpVersion.Version20; + client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; + client.DefaultRequestHeaders.ConnectionClose = true; + + var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); + request.Content = new StringContent(json, Encoding.UTF8, "application/json"); + if (readAll) + return await client.SendAsync(request); + return await client.SendAsync(request, HttpCompletionOption.ResponseHeadersRead); + } + catch (Exception e) + { + errorMSG[i] = e; + Console.WriteLine("====================[请求异常,重试]===================="); + Console.WriteLine(uriBuilder.Uri); + Console.WriteLine(e.Message); + Console.WriteLine(e.StackTrace); + Console.WriteLine("=============================================="); + Thread.Sleep(1000); + } + } + throw errorMSG.Last(s => s != null); + } + + + } } diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs index 06f7b6a..6cfc429 100644 --- a/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs +++ b/VideoAnalysisCore/AICore/GPT/ChatGPT/ChatGPTClient.cs @@ -8,6 +8,7 @@ using Newtonsoft.Json; using System.Net.Http.Json; using System.Net; using VideoAnalysisCore.AICore.GPT.DeepSeek; +using VideoAnalysisCore.AICore.GPT; using System.Text.Json; @@ -28,41 +29,6 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT this.redisManager = redisManager; } - private HttpResponseMessage PostJsonStream(string path, string json) - { - var uriBuilder = new UriBuilder(Host+ path); - var maxRestart = 4; - var errorMSG = new Exception[maxRestart]; - for (int i = 0; i < maxRestart; i++) - { - try - { - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", ApiKey); - client.Timeout = TimeSpan.FromSeconds(60 * 20);//超时时间20分钟 - client.DefaultRequestVersion = HttpVersion.Version20; - client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; - client.DefaultRequestHeaders.ConnectionClose = true; - - var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - - return client.Send(request, HttpCompletionOption.ResponseHeadersRead); - } - catch (Exception e) - { - errorMSG[i] = e; - Console.WriteLine("====================[请求异常,重试]===================="); - Console.WriteLine(uriBuilder.Uri); - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); - Console.WriteLine("=============================================="); - Thread.Sleep(1000); - } - } - throw errorMSG.Last(s => s != null); - } /// /// ChatSSE[流式传输 更稳定] @@ -73,11 +39,10 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT { var requestBody = chatReq.ToJson(); PostJsonStream: - var chatResp = PostJsonStream("v1/chat/completions", requestBody); + var chatResp = await _httpClientFactory.PostJsonStreamAsync(Host+"v1/chat/completions", requestBody, ApiKey); if (!chatResp.IsSuccessStatusCode) { - Console.WriteLine(DateTime.Now + "=>请求GPT服务器异常 " + chatResp?.StatusCode); - Console.WriteLine(await chatResp.Content.ReadAsStringAsync()); + await redisManager.AddTaskLog(chatReq.taskId, "请求GPT服务器异常 " + chatResp?.StatusCode + await chatResp.Content.ReadAsStringAsync()); goto PostJsonStream; } using var stream = chatResp.Content.ReadAsStream(); @@ -130,14 +95,11 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT } catch (Exception e) { - Console.WriteLine("异常 ChatSSE=>"); - Console.WriteLine(line); - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); + await redisManager.AddTaskLog(chatReq.taskId, "异常 ChatSSE=>" + line + "\r\n" + e.Message + "\r\n" + e.StackTrace); } } } - Console.WriteLine(DateTime.Now + "=>AI请求超时 " + chatReq.taskId); + await redisManager.AddTaskLog(chatReq.taskId, DateTime.Now + "=>AI请求超时 " + chatReq.taskId); return null; } @@ -216,8 +178,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT } catch (Exception ex) { - Console.WriteLine(ex.Message); - Console.WriteLine(DateTime.Now + $"=>ChatGPT结果解析错误 重试剩余{tryCount}"); + await redisManager.AddTaskLog(task, $"ChatGPT结果解析错误 重试剩余{tryCount}" + ex.Message); } } throw new Exception(DateTime.Now + "=>ChatGPT请求失败次数过多!!!"); @@ -237,7 +198,7 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT if (chatReq.stream) return await ChatSSE(chatReq); var requestBody = chatReq.ToJson(); - var chatResp = await PostJsonStreamAsync("v1/chat/completions", requestBody); + var chatResp = await _httpClientFactory.PostJsonStreamAsync(Host+"v1/chat/completions", requestBody,ApiKey,true); var res = await chatResp.Content.ReadFromJsonAsync(); var chatResContent = res?.choices.FirstOrDefault()?.message.content.Trim(); @@ -249,42 +210,5 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT return null; return (res.usage, chatResContent); } - - - private async Task PostJsonStreamAsync(string path, string json) - { - var uriBuilder = new UriBuilder(Host + path); - var maxRestart = 4; - var errorMSG = new Exception[maxRestart]; - for (int i = 0; i < maxRestart; i++) - { - try - { - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", AppCommon.Config.ChatGpt.ChatGpt.ApiKey); - client.Timeout = TimeSpan.FromSeconds(60 *20);//超时时间20分钟 - client.DefaultRequestVersion = HttpVersion.Version20; - client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; - client.DefaultRequestHeaders.ConnectionClose = true; - - var content = new StringContent(json, Encoding.UTF8, "application/json"); - return await client.PostAsync(uriBuilder.Uri, content); - } - catch (Exception e) - { - errorMSG[i] = e; - Console.WriteLine("====================[请求异常,重试]===================="); - Console.WriteLine(uriBuilder.Uri); - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); - Console.WriteLine("=============================================="); - - } - Thread.Sleep(1000); - } - throw errorMSG.Last(s => s != null); - } - } } \ No newline at end of file diff --git a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs b/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs deleted file mode 100644 index 72dad2e..0000000 --- a/VideoAnalysisCore/AICore/GPT/ChatGPT/Chat_GPT.cs +++ /dev/null @@ -1,436 +0,0 @@ -using VideoAnalysisCore.Common; -using System.Text.Json; -using VideoAnalysisCore.Model; -using System.Text; -using System.ComponentModel.DataAnnotations; -using System.Reflection; -using VideoAnalysisCore.Model.Dto; -using VideoAnalysisCore.AICore.GPT.Dto; -using VideoAnalysisCore.AICore.GPT; -using System.Threading.Tasks; -using VideoAnalysisCore.AICore.SherpaOnnx; -using VideoAnalysisCore.Model.蓝鲸智库; -using VideoAnalysisCore.Model.Enum; - -namespace VideoAnalysisCore.AICore.GPT.ChatGPT -{ - /// - /// chatgpt 文本模型 - /// - public class Chat_GPT : IBserGPT - { - private readonly ChatGPTClient chatClient; - private readonly Repository criteriaDB; - private readonly Repository videoTaskDB; - private readonly Repository knowledgeInfoDB; - private readonly RedisManager redisManager; - /// - /// 初始化 - /// - /// - /// - public Chat_GPT(ChatGPTClient moonshotClient, Repository criteria, Repository videoTaskDB, Repository knowledgeInfoDB, RedisManager redisManager) - { - this.chatClient = moonshotClient; - criteriaDB = criteria; - this.videoTaskDB = videoTaskDB; - this.knowledgeInfoDB = knowledgeInfoDB; - this.redisManager = redisManager; - } - - private static List MergeRes(VideoKnowRes[] timeBases) - { - if (timeBases == null || timeBases.Count() == 0) - { - return new List(); - } - var mergedList = new List(); - // 初始化合并段 - var current = timeBases.First(); - foreach (var next in timeBases) - { - // 如果类型相同,则扩展时间段 - if (current.Theme == next.Theme) - { - //current.EndTime = Math.Max(current.EndTime.Value, next.EndTime.Value); - current.Content += next.Content; - } - else - { - // 类型不同,将当前时间段加入结果列表,并开始新时间段 - mergedList.Add(current); - current = next; - } - } - // 添加最后的时间段 - mergedList.Add(current); - return mergedList; - } - - - /// - /// 获取知识点 - /// - /// 任务id - /// - public async Task GetKnow(string task) - { - var taskId = long.Parse(task); - var taskInfo = await videoTaskDB.AsQueryable() - .Where(s => s.Id == taskId) - .FirstAsync(); - var subject = "数学"; - var xkwKnows = await knowledgeInfoDB.AsQueryable() - .Where(s => s.Course_Id == 27 - && s.Depth == 2) - .Select(s => s.Name).ToArrayAsync(); - var fileNameResFormat = "{授课章节: string|null, 授课内容:string}"; - var fileNamePostMessages = - " 这是一堂课的标题,请你帮我分析一些关于课堂方面的内容." + - $"1.分析出高中{subject}课堂授课的主要章节(例如 章节: 数列),章节范围限定在[{string.Join(',', xkwKnows)}]范围." + - $"2.分析出这堂课的主要授课内容." + - $"输出格式 json字符串 对象格式{fileNameResFormat}"; - var fileNameInfoRes = await ChatAsync(task, fileNamePostMessages, null, fileNameResFormat); - - var speakerArr = JsonSerializer.Deserialize(taskInfo.Speaker); - var captionsArr = JsonSerializer.Deserialize(taskInfo.Captions); - var captions = ExpandFunction.GetSpeakerCaptions(captionsArr, speakerArr); - - var maxVideoTime = captions?.TimeBase?.LastOrDefault()?.End ?? 0; - var criteriaBuilder = new StringBuilder(); - - //var resFormat = """[{"StartTime":开始秒(number),"EndTime":结束秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; - var resFormat = """[{"StartTime":开始秒(number),"Section":章节(string),"Theme":主题(string),"ThemeDetalis":主题详情(string),"Content":内容总结(string)}]"""; - var know = await knowledgeInfoDB.GetFirstAsync(s => s.Name == fileNameInfoRes.授课章节); - var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, know.Id); - var knows = string.Join(',', knowledgeInfos.Select(s => s.Name)); - - var postMessages = - $"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后根据步骤分析出知识片段" + - $"按以下步骤完成:" + - $"1.识别方法点:提取字幕内容中与{subject}考试属于{fileNameInfoRes.授课章节}章节相关的方法点。" + - $"2.分析总结:基于提取出的方法点名称来匹配我提供的方法点名称" + - $"提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" + - $"3.关联合并知识内容相似的知识点来合并为知识片段。" + - $"知识片段使用关联知识点中的最小开始时间主题为关联知识点的主题分析,内容总结为关联知识点的内容总结分析。" + - $"4.基于'知识片段'的'内容总结'加上'主题'来分析这个片段对主题的讲解内容为新的主题 例如(数列的基本概念)。" + - $"5.分配空余未使用的时间段到内容相近的知识片段时间区间来获取更加详细的上下文,但是请避免知识片段之间时间重合。" + - $"输入:包含时间戳的视频字幕文本。" + - $"以下是包含时间的视频字幕文本。" + - $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + - $"输出格式({resFormat})"; - postMessages = - $"你现在需要处理用户提供的视频字幕内容,从中提取与中国高考数学考试{fileNameInfoRes.授课章节}相关的方法点,并按照指定的步骤进行分析。首先,你得仔细阅读用户的需求,确保每一步都正确执行" + - $"用户的需求:" + - $"1.要求识别字幕中的方法点,这些方法点需要属于{fileNameInfoRes.授课章节}章节。需要你通读字幕,找到这些关键词出现的地方,并记录对应的时间戳" + - $"2.是根据提取的方法点名称匹配用户提供的列表。例如,如果字幕中提到“递增数列”或“通项公式”,你需要确认这些是否在用户给出的方法点列表中,并将它们归类到正确的名称下" + - $"用户提供的方法点名称({fileNameInfoRes.授课章节}的基本概念,{fileNameInfoRes.授课章节}的练习与应用,{fileNameInfoRes.授课章节}的例题讲解,{knows})。" + - $"3.关联合并相似的知识点,形成知识片段。比如,如果多个时间段都讨论数列的基本概念,你需要将它们合并,并选择最早的时间作为开始时间。同时,需要总结这些片段的内容,并生成新的主题名称,如“数列的基本概念”。" + - $"如果多个片段都涉及数列的定义和例子,可能合并为“数列的基本概念”" + - $"4.分配未使用的时间段到相近的知识片段,避免时间重叠。这可能需要检查是否有剩余的时间段未被归类,并将其合并到合适的知识片段中,以丰富上下文" + - $"输入:包含时间戳的视频字幕文本。" + - $"以下是包含时间的视频字幕文本。" + - $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + - $"输出格式({resFormat})"; - - var questionRes = await ChatAsync(task, postMessages, null, resFormat); - //var postMessages = - // $"你的任务是分析视频字幕内容,精准提取出与中国高考数学考试相关的试题方法点。" + - // $"按以下步骤完成:" + - // $"1.准确识别方法点:从字幕中提取与考试紧密相关的方法点,尤其关注与给定方法点类别相关的内容" + - // $"2.深入分析总结:依据给定的方法点类别进行约束,确定提取出的方法点所属类别。" + - // $"给定方法点包括(基本概念,课堂练习,例题讲解,解题技巧...)。" + - // $"3.细致分类方法点:按照学科方法点进行分类,具体细化到特定章节与主题,格式为 “章节:具体章节名称 主题:具体主题名称" + - // $"4.合理合并相似方法点为内容片段,确保内容的连贯性和逻辑性" + - // $"5.关联每个内容片段的方法点所有的时间并结构化输出。" + - // $"6.基于内容片段的内容总结加上主题来分析这个片段对主题的讲解内容为新的主题 例(数列的基本概念)。" + - // $"尽可能延内容片段时间区间,以获取详细的方法点上下文。" + - // $"输入:包含时间戳的视频字幕文本。输出格式:开始秒,结束秒,主题,内容总结" ; - //var postM2 = $"以下是包含时间的视频字幕文本。" + - // $"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).字幕列表 {captions.Captions}" + - // $"返回固定的JSON格式({resFormat})"; - - //var questionRes = await ChatAsync(task, postMessages, postM2, resFormat); - if (questionRes.Length <= 3) - throw new Exception("视频分段数量过低 =>" + questionRes.Length); - - - for (int i = 0; i < questionRes.Length; i++) - { - var item = questionRes[i]; - if (i == questionRes.Length - 1) - item.EndTime = maxVideoTime; - else - item.EndTime = (int)(questionRes[i + 1]?.StartTime ?? 0) - 1; - } - - await redisManager.Redis - .HMSetAsync(RedisExpandKey.Task(task), "VideoKnows", questionRes); - - //var postMessages1 = - // $"你的任务是分析json内容并合并含义相似的主题为新的主题" + - // $"按以下步骤完成:" + - // $"1.合理合并主题字段重复相似的对象为新的json对象,确保内容的连贯性和逻辑性。" + - // $"2.合并对象属性持续时间低于60秒的对象" + - // $"3.结构化输出。" + - // $"输入:json对象 包含总结开始秒,结束秒,持续时间,主题,章节,内容总结" + - // $"以下是包含json内容的文本。" + - // $" {JsonSerializer.Serialize(questionRes)}" + - // $"返回固定的JSON格式({resFormat})"; - - - //var questionRes1 = await ChatAsync(task, postMessages1, resFormat); - ////questionRes1 = MergeRes(questionRes1).ToArray(); - - var gptRes = new TaskRes(captions); - await redisManager.Redis - .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); - return gptRes; - } - public async Task ChatAsync(string task, string postMessages, string postMessages1, string resFormat) - { - var maxTokens = 4000; - Message[] messageArr = [ - new Message(postMessages,"system"), - string.IsNullOrEmpty(postMessages1)?null:new Message(postMessages1,"user"), - new Message(resFormat,"assistant"), - ]; - messageArr = messageArr.Where(s => s != null).ToArray(); - var chatRep = new ChatRequest - { - model = ChatGPTType.GPT5_mini, - max_completion_tokens = maxTokens, - temperature = 0.2f, - messages = messageArr - }; - - var time = DateTime.Now.ToString("MMddHHmmss"); - redisManager.SetTaskGPTCached(task, time,chatRep); - var chatResp = await chatClient.Chat(chatRep); - var chatResContent = chatResp?.res; - if (string.IsNullOrEmpty(chatResContent)) - throw new Exception("GPT返回message无效结果"); - if (chatResp != null) - redisManager.SetTaskGPTCached(task, time, new object[] { chatResp.Value.res, chatResp.Value.u }); - - chatResContent = chatResContent?.Replace("字幕内容", "课堂情况"); - chatResContent = chatResContent?.Replace("\n", ""); - chatResContent = chatResContent?.Replace("```json", ""); - chatResContent = chatResContent?.Replace("```", ""); - chatResContent = chatResContent?.Replace("}{", "},{"); - chatResContent = chatResContent?.Replace("}|{", "},{"); - chatResContent = chatResContent?.Trim(); - var startsStr = typeof(T).IsArray ? "[" : "{"; - var endStr = typeof(T).IsArray ? "]" : "}"; - if (!chatResContent.StartsWith(startsStr)) - chatResContent = startsStr + chatResContent; - if (!chatResContent.EndsWith(endStr)) - chatResContent = chatResContent + endStr; - var questionRes = JsonSerializer.Deserialize(chatResContent); - if (questionRes is null) - throw new Exception("ChatGPT返回无效结果"); - //var totalTokens = chatResp?.u.total_tokens ?? 0; - //if (totalTokens > 1) - //{ - // var tid = long.Parse(task); - // await videoTaskDB.AsUpdateable() - // .SetColumns(it => it.TotalTokens == totalTokens)//SetColumns是可以叠加的 写2个就2个字段赋值 - // .Where(it => it.Id == tid) - // .ExecuteCommandAsync(); - //} - return questionRes; - } - - - /// - /// 访问GPT - /// - /// 任务id - /// - public async Task CallGPT(string task) - { - var taskId = long.Parse(task); - var taskInfo = await videoTaskDB.AsQueryable() - .Where(s => s.Id == taskId) - .FirstAsync(); - - var speakerArr = JsonSerializer.Deserialize(taskInfo.Speaker); - var captionsArr = JsonSerializer.Deserialize(taskInfo.Captions); - var captions = ExpandFunction.GetSpeakerCaptions(captionsArr, speakerArr); - var criteriaArr = await criteriaDB.GetListAsync(s => s.Subject == taskInfo.Subject); - var criteriaBuilder = new StringBuilder(); - foreach (var item in criteriaArr) - { - criteriaBuilder.Append(item.Id); - criteriaBuilder.Append(":"); - criteriaBuilder.Append(item.NamePrompt); - criteriaBuilder.Append("? 请基于问题的回答苛刻的给出[优/良/中/差]作为得分"); - //criteriaBuilder.Append("0-"); - //criteriaBuilder.Append((int)(item.TotalScore * 10)); - //criteriaBuilder.Append("分"); - //criteriaBuilder.Append((int)(item.PassScore * 10)); - //criteriaBuilder.Append("分为及格"); - criteriaBuilder.Append(":"); - criteriaBuilder.Append("array=[得分,问题的回答,问题的详细改进意见,问题的详细扣分原因]|"); - - } - //拼接枚举提问 - foreach (var value in System.Enum.GetValues(typeof(QuestionTypeEnum))) - { - var enumValue = (QuestionTypeEnum)value; - var displayAttribute = enumValue.GetType() - .GetField(enumValue.ToString())? - .GetCustomAttribute(); - if (displayAttribute == null) continue; - criteriaBuilder.Append(enumValue.GetHashCode()); - criteriaBuilder.Append(":"); - criteriaBuilder.Append(displayAttribute.Prompt); - criteriaBuilder.Append("|"); - } - - var resFormat = """[{"问题编号":number,"结果":array|bool|object,"问题解释":string}]"""; - var postMessages = - $"你是一个教学经验老道老师对教学工作有着深入的理解和丰富的经验,能够准确把握教学大纲的要求和教学重点。" + - $"熟练掌握各种教学管理方法和手段,能够制定科学合理的教学计划和教学评估体系。" + - $"善于发现教学中的问题,并能迅速提出有效的解决方案,确保教学工作的顺利进行。" + - $"以下是一段音频的字幕,分析这段字幕 字幕格式(说话人:开始秒:结束秒:内容|下一段字幕)." + - $"字幕列表 {captions.Captions} " + - $"基于字幕内容回答提出的所有问题 问题格式(问题编号:问题描述:结束秒:结果类型|下一个问题)" + - $"问题列表 {criteriaBuilder} " - + $"返回固定的JSON格式({resFormat})"; - - var maxTokens = 4000; - var chatRep = new ChatRequest - { - max_completion_tokens = maxTokens, - temperature = 0.3f, - messages = [ - new Message(postMessages,"system"), - new Message(resFormat,"assistant"), - ] - }; - - var time = DateTime.Now.ToString("MMddHHmmss"); - redisManager.SetTaskGPTCached(task, time, chatRep); - var chatResp = await chatClient.Chat(chatRep); - var chatResContent = chatResp?.res; - if (string.IsNullOrEmpty(chatResContent)) - throw new Exception("GPT返回message无效结果"); - if (chatResp != null) - redisManager.SetTaskGPTCached(task, time, new object[] { chatResp.Value.res, chatResp.Value.u }); - - chatResContent = chatResContent?.Replace("字幕内容", "课堂情况"); - chatResContent = chatResContent?.Replace("\n", ""); - chatResContent = chatResContent?.Replace("```json", ""); - chatResContent = chatResContent?.Replace("```", ""); - chatResContent = chatResContent?.Replace("}{", "},{"); - chatResContent = chatResContent?.Replace("}|{", "},{"); - chatResContent = chatResContent?.Trim(); - - if (!chatResContent.StartsWith("[")) - chatResContent = "[" + chatResContent; - if (!chatResContent.EndsWith("]")) - chatResContent = chatResContent + "]"; - var questionRes = JsonSerializer.Deserialize(chatResContent); - var gptRes = new TaskRes(captions); - if (questionRes is null) - throw new Exception("ChatGPT返回无效结果"); - var qEnum = (int)QuestionTypeEnum.高频词; - //处理 ai问答提问 - var arr1 = questionRes.Where(s => s.问题编号 < qEnum); - var arr2 = questionRes.Where(s => s.问题编号 >= qEnum) - .ToDictionary(s => s.问题编号); - //AI综合评估 - var criteriaDic = criteriaArr.ToDictionary(s => s.Id); - - var random = new Random(); - var ccArr = new List(arr1.Count()); - foreach (var s in arr1) //处理问题 - { - var Id = criteriaDic[s.问题编号].Id; - var PassScore = criteriaDic[s.问题编号].PassScore; - var TotalScore = criteriaDic[s.问题编号].TotalScore; - var Score = Math.Round(criteriaDic[s.问题编号].TotalScore * 0.01m - * ((int)(s.ToObject()?[0].ToEnum() ?? ScoreTypeEnum.中) - + random.Next(-6, 25)), - 1, MidpointRounding.AwayFromZero); - var Prompt = s.ToObject()?[1].ToString() ?? string.Empty; - var ImprovedMethods = s.ToObject()?[2].ToString() ?? string.Empty; - var Analyze = s.问题解释 ?? string.Empty; - var kf = s.ToObject()?[3].ToString() ?? string.Empty; - kf = kf.Replace("无", ""); - if (string.IsNullOrEmpty(kf)) - Score = criteriaDic[s.问题编号].TotalScore; - else - Analyze += " 扣分原因:" + kf; - ccArr.Add(new CourseCriteria() - { - Analyze = Analyze, - Id = Id, - PassScore = PassScore, - ImprovedMethods = ImprovedMethods, - Prompt = Prompt, - Score = Score, - TotalScore = TotalScore, - }); - } - gptRes.Assessment = new AssessmentDto() - { - Bad = ccArr.Where(s => s.Score < s.PassScore).ToArray(), - Merit = ccArr.Where(s => s.Score >= s.PassScore).ToArray(), - }; - //高频词汇 - gptRes.Hotwords = - arr2[(int)QuestionTypeEnum.高频词].ToObject() ?? ["暂无数据"]; - //时间段概览 - gptRes.TimeOverview = arr2[(int)QuestionTypeEnum.时间段概览] - .ToObject(); - //提问类型 - gptRes.QuestionType = arr2[(int)QuestionTypeEnum.提问类型] - .ToObject>(); - - //分析上课时间段情况 分析 独立学习 小组合作 随堂练习等情况 - var extraTimeBase = arr2[(int)QuestionTypeEnum.额外课堂情况] - .ToObject(); - if (extraTimeBase is not null) - foreach (var item in extraTimeBase) - { - if (item is null) - continue; - var r = item.Content.ToEnum(); - if (r is null) - continue; - var arr = gptRes.TimeBase? - .Where(s => s.Start >= item.Start && s.End <= item.End); - if (arr is null) - continue; - foreach (var s in arr) - s.TimeBaseType = r; - } - var totalTokens = chatResp?.u.total_tokens ?? 0; - if (totalTokens > 1) - { - var tid = long.Parse(task); - await videoTaskDB.AsUpdateable() - .SetColumns(it => it.TotalTokens == totalTokens)//SetColumns是可以叠加的 写2个就2个字段赋值 - .Where(it => it.Id == tid) - .ExecuteCommandAsync(); - } - - await redisManager.Redis - .HMSetAsync(RedisExpandKey.Task(task), "ChatAnalysis", gptRes); - - return gptRes; - } - - public Task GetVideoQuestion(string task) - { - throw new NotImplementedException(); - } - - Task IBserGPT.GetVideoType(string task) - { - throw new NotImplementedException(); - } - } -} diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs index 33114ac..7956557 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeekClient.cs @@ -11,6 +11,7 @@ using System.Threading; using System; using System.IO; using VideoAnalysisCore.AICore.GPT.ChatGPT; +using VideoAnalysisCore.AICore.GPT; using System.Threading.Tasks; using System.Text.Json; @@ -56,16 +57,14 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek postStar: var requestBody = chatReq.ToJson(); - HttpResponseMessage chatResp = PostJsonStream(Path, requestBody); + HttpResponseMessage chatResp = await _httpClientFactory.PostJsonStreamAsync(Host+Path, requestBody, ApiKey,true); var res1 = await chatResp.Content.ReadAsStringAsync(); if (res1 == null || string.IsNullOrEmpty(res1)|| !chatResp.IsSuccessStatusCode) { - Console.WriteLine(DateTime.Now + $"=>GPT请求失败重试 Code = {chatResp.StatusCode} Res={res1}"); + await redisManager.AddTaskLog(chatReq.taskId,$"=>GPT请求失败重试 Code = {chatResp.StatusCode} Res={res1}"); goto postStar; } - //throw new Exception($" GPT模型返回异常 返回参数: " + - // $" {System.Text.Json.JsonSerializer.Serialize(res1)}"); - Console.WriteLine(DateTime.Now + $"=>GPT请求头获取成功 Code = {chatResp.StatusCode} Res={res1}"); + await redisManager.AddTaskLog(chatReq.taskId, $"=>GPT请求头获取成功 Code = {chatResp.StatusCode} Res={res1}"); var res = await chatResp.Content.ReadFromJsonAsync(); if (res is null || res.error != null) throw new Exception($" GPT模型返回异常 返回参数: " + @@ -94,41 +93,6 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek return (chatResContent, chatResReasoning); } - private HttpResponseMessage PostJsonStream(string path, string json) - { - var uriBuilder = new UriBuilder(Host + Path); - var maxRestart = 4; - var errorMSG = new Exception[maxRestart]; - for (int i = 0; i < maxRestart; i++) - { - try - { - var client = _httpClientFactory.CreateClient(); - client.DefaultRequestHeaders.Authorization = - new AuthenticationHeaderValue("Bearer", ApiKey); - client.Timeout = TimeSpan.FromSeconds(60 * 20);//超时时间20分钟 - client.DefaultRequestVersion = HttpVersion.Version20; - client.DefaultVersionPolicy = HttpVersionPolicy.RequestVersionOrLower; - client.DefaultRequestHeaders.ConnectionClose = true; - - var request = new HttpRequestMessage(HttpMethod.Post, uriBuilder.Uri); - request.Content = new StringContent(json, Encoding.UTF8, "application/json"); - - return client.Send(request, HttpCompletionOption.ResponseHeadersRead); - } - catch (Exception e) - { - errorMSG[i] = e; - Console.WriteLine("====================[请求异常,重试]===================="); - Console.WriteLine(uriBuilder.Uri); - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); - Console.WriteLine("=============================================="); - Thread.Sleep(1000); - } - } - throw errorMSG.Last(s => s != null); - } @@ -142,11 +106,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek { var requestBody = chatReq.ToJson(); PostJsonStream: - var chatResp = PostJsonStream(string.Empty, requestBody); + var chatResp = await _httpClientFactory.PostJsonStreamAsync(Host, requestBody,ApiKey); if (!chatResp.IsSuccessStatusCode) { - Console.WriteLine(DateTime.Now + "=>请求GPT服务器异常 " + chatResp?.StatusCode); - Console.WriteLine(await chatResp.Content.ReadAsStringAsync()); + await redisManager.AddTaskLog(chatReq.taskId,"=>请求GPT服务器异常 " + chatResp?.StatusCode +" "+ await chatResp.Content.ReadAsStringAsync()); goto PostJsonStream; } using var stream = chatResp.Content.ReadAsStream(); @@ -200,14 +163,11 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } catch (Exception e) { - Console.WriteLine("异常 ChatSSE=>"); - Console.WriteLine(line); - Console.WriteLine(e.Message); - Console.WriteLine(e.StackTrace); + await redisManager.AddTaskLog(chatReq.taskId, "异常 ChatSSE=>" + line + "\r\n" + e.Message + "\r\n" + e.StackTrace); } } } - Console.WriteLine(DateTime.Now + "=>AI请求超时 " + chatReq.taskId); + await redisManager.AddTaskLog(chatReq.taskId, DateTime.Now + "=>AI请求超时 " + chatReq.taskId); return null; } @@ -287,10 +247,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } catch (Exception ex) { - Console.WriteLine(ex.Message); - Console.WriteLine(DateTime.Now + $"=>ChatGPT结果解析错误 重试剩余{tryCount}"); + await redisManager.AddTaskLog(task, $"=>ChatGPT结果解析错误 重试剩余{tryCount} {ex.Message}"); } } + await redisManager.AddTaskLog(task, $"=>ChatGPT请求失败次数过多!!!"); throw new Exception(DateTime.Now + "=>ChatGPT请求失败次数过多!!!"); } diff --git a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs index 164414a..b8d345e 100644 --- a/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs +++ b/VideoAnalysisCore/AICore/GPT/DeepSeek/DeepSeek_GPT.cs @@ -88,7 +88,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek $"输出内容只返回json格式({checkResFormat1})" + $" 格式 (方法点Id|方法点名称) " + $"提供的知识点名称({knows})。"; - Console.WriteLine(DateTime.Now + "=>2.开始分析视频内容知识点"); + await redisManager.AddTaskLog(taskInfo.Id, DateTime.Now + "=>2.开始分析视频内容知识点"); VideoKnowRes[] konwRes; for (int i = 0; i < 5; i++) { @@ -255,12 +255,11 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek $"`{nowCaptionStr}`" + $"字幕结束。" + $"最后请确保输出字幕条数与输入字幕条数一致!!! 如果不一致则重新优化并且确保字幕条数一致!!!!"; - //Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s}开始..."); var resData = await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "优化字幕", "deepseek-chat", 3000); if (resData.Count() != cArr.Count()) { resData = cStrArr.ToArray(); - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s} AI结果数量不匹配,采用原始值"); + await redisManager.AddTaskLog(taskInfo.Id, $"=>字幕优化 分段{s} AI结果数量不匹配,采用原始值"); } newCaptionsList.AddRange(resData.Select((text, i) => new SenseVoiceRes() { @@ -268,11 +267,9 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek End = captionsArr[spanCount * s + i].End, Text = text, })); - //Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id}字幕优化 分段{s}完成√ "); return; }); var res = newCaptionsList.OrderBy(s => s.Start).ToArray(); - //Console.WriteLine(DateTime.Now + $"=>字幕优化执行完成"); var jsonData = res.ToJson(); await videoTaskDB.AsUpdateable() .SetColumns(it => it.CaptionsAI == jsonData) @@ -344,15 +341,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek throw new Exception("无效的课程类型"); } - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}.开始分析视频内容 {tryCount}"); + await redisManager.AddTaskLog(taskInfo.Id, $"开始分析视频内容 {tryCount}"); //return await chatGPTClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕"); return await chatClient.ChatAsync(taskInfo.Id.ToString(), postMessages, "分析字幕"); } catch (Exception ex) { - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id.ToString()}分析视频内容失败 {tryCount}"); - Console.WriteLine(DateTime.Now + ex.Message); - Console.WriteLine(DateTime.Now + ex.StackTrace); + await redisManager.AddTaskLog(taskInfo.Id, $"分析视频内容失败 {tryCount} \r\n{ex.Message}\r\n{ex.StackTrace}"); } } return null; @@ -368,7 +363,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek /// private async Task AnalysisVideoQuestions(VideoTask taskInfo, List knowledgeInfos) { - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取试题"); + await redisManager.AddTaskLog(taskInfo.Id, DateTime.Now + $"=>{taskInfo.Id} 提取试题"); if (taskInfo is null || string.IsNullOrEmpty(taskInfo.PPTKeyFrame)) return null; var farmeArr = JsonSerializer.Deserialize(taskInfo.PPTKeyFrame); @@ -409,7 +404,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek break; #if DEBUG - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题的试题内容"); + await redisManager.AddTaskLog(taskInfo.Id, DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题的试题内容"); #endif //var knowArr=JsonSerializer.Serialize(knowInfoArr.Select(s => new { s.KnowPointId, s.KnowPoint })); @@ -468,7 +463,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } catch (Exception ex) { - Console.WriteLine(DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题出现错误 {ex.Message}"); + await redisManager.AddTaskLog(taskInfo.Id, DateTime.Now + $"=>{taskInfo.Id} 提取{knowInfoArr.StartTime}秒试题出现错误 {ex.Message}"); } } } @@ -563,7 +558,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek } if (questionRes.Any(s => s.KeepTime < 30)) { - Console.WriteLine(DateTime.Now + "=>视频分段过短!! 重新进行AI分析"); + await redisManager.AddTaskLog(taskInfo.Id, DateTime.Now + "=>视频分段过短!! 重新进行AI分析"); continue; } } diff --git a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs index db08f35..49daf94 100644 --- a/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs +++ b/VideoAnalysisCore/AICore/SherpaOnnx/SenseVoice.cs @@ -195,7 +195,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx //如果携带任务ID if (!string.IsNullOrEmpty(task)) { - Console.WriteLine(DateTime.Now + "=> SenseVoice 字幕数量" + res.Count); + await redisManager.AddTaskLog(task, "=> SenseVoice 字幕数量" + res.Count); var captionsStr = res.ToJson(); await videoTaskDB.AsUpdateable() .SetColumns(it => it.Captions == captionsStr) diff --git a/VideoAnalysisCore/Common/AppCommon.cs b/VideoAnalysisCore/Common/AppCommon.cs index 35088fd..7a473ce 100644 --- a/VideoAnalysisCore/Common/AppCommon.cs +++ b/VideoAnalysisCore/Common/AppCommon.cs @@ -103,7 +103,7 @@ namespace VideoAnalysisCore.Common /// /// /// - public static bool DeleteTaskFile(long? taskId) + public static async Task DeleteTaskFileAsync(long? taskId,RedisManager redisManager) { if (taskId is null || taskId == 0) return false; var path = taskId.ToString().LocalPath(); @@ -118,16 +118,17 @@ namespace VideoAnalysisCore.Common try { File.Delete(filePath); - Console.WriteLine($"已成功删除文件: {filePath}"); + + await redisManager.AddTaskLog(taskId, $"已成功删除文件: {filePath}"); } catch (Exception ex) { - Console.WriteLine($"删除文件 {filePath} 时出错: {ex.Message}"); + await redisManager.AddTaskLog(taskId, $"删除文件 {filePath} 时出错: {ex.Message}"); } } //else //{ - // Console.WriteLine($"文件不存在,跳过删除: {filePath}"); + // await redisManager.AddTaskLog(chatReq.taskId, $"文件不存在,跳过删除: {filePath}"); //} } return true; diff --git a/VideoAnalysisCore/Common/RedisExpand.cs b/VideoAnalysisCore/Common/RedisExpand.cs index 5be3555..bda37df 100644 --- a/VideoAnalysisCore/Common/RedisExpand.cs +++ b/VideoAnalysisCore/Common/RedisExpand.cs @@ -65,6 +65,10 @@ namespace VideoAnalysisCore.Common /// 任务数组 /// public const string TaskArr = BaseKey + "TaskArr"; + /// + /// 任务日志缓存 + /// + public static string TaskLog => BaseKey + "TaskLog:" + AppCommon.Config.ID; /// @@ -87,8 +91,10 @@ namespace VideoAnalysisCore.Common { Console.WriteLine($"{DateTime.Now}=>初始化 Redis"); var redis = new RedisClient(AppCommon.Config.Redis.ConnectionString); - redis.Serialize = obj => JsonSerializer.Serialize(obj); - redis.Deserialize = (json, type) => JsonSerializer.Deserialize(json, type); + redis.Serialize = obj => + JsonSerializer.Serialize(obj); + redis.Deserialize = (json, type) => + JsonSerializer.Deserialize(json, type); service.AddSingleton(redis); service.AddSingleton(); } @@ -106,6 +112,17 @@ namespace VideoAnalysisCore.Common FFMPGE = fFMPGE; this.senseVoice = senseVoice; this.redisManager = redisManager; + + for (int i = 1; i < 999; i++) + { + redisManager.AddTaskLog(-1, $"测试日志 {i}"); + } + + + + + + Init(); redisManager.InitChannel(); } @@ -169,11 +186,13 @@ namespace VideoAnalysisCore.Common static SubscribeListObject? Subscribe; public RedisClient Redis { get; set; } public Repository videoTaskDB { get; set; } + public Repository taskLogDB { get; set; } - public RedisManager(RedisClient redis, Repository videoTaskDB) + public RedisManager(RedisClient redis, Repository videoTaskDB, Repository taskLogDB) { Redis = redis; this.videoTaskDB = videoTaskDB; + this.taskLogDB = taskLogDB; } @@ -201,6 +220,39 @@ namespace VideoAnalysisCore.Common } } /// + /// 添加日志 + /// + /// 任务id + /// 内容 + public async Task AddTaskLog(object taskId, string msg) + { + await Redis.RPushAsync(RedisExpandKey.TaskLog, + new TaskLog() + { + VideoTaskId = long.Parse(taskId.ToString()), + CreateTime = DateTime.Now, + Message = msg + }); + lock (RedisExpandKey.TaskLog) + { + var oldTaskCount = Redis.LLen(RedisExpandKey.TaskLog); + if (oldTaskCount > 100) + { + try + { + var insertData = Redis.LRange(RedisExpandKey.TaskLog, 0, 99); + taskLogDB.AsInsertable(insertData).ExecuteCommand(); + //同步删除redis + Redis.LTrim(RedisExpandKey.TaskLog, 100, 1000); + } + catch (Exception ex) + { + Console.WriteLine("写入任务日志出错" + "\r\n" + ex.Message + "\r\n" + ex.StackTrace); + } + } + } + } + /// /// 获取任务进度 /// /// @@ -237,7 +289,7 @@ namespace VideoAnalysisCore.Common throw new Exception(@enum + " 未实现"); var tId = taskId.ToString(); - Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 开始执行任务 " + tId); + await AddTaskLog(tId, "-------------> 开始执行任务 "); try { while (true) @@ -302,7 +354,7 @@ namespace VideoAnalysisCore.Common }).ExecuteCommandAsync(); try { - ExpandFunction.DeleteTaskFile(tId); + await ExpandFunction.DeleteTaskFileAsync(tId, this); } catch (Exception) { @@ -315,7 +367,7 @@ namespace VideoAnalysisCore.Common /// /// 初始化 队列 任务 /// - public async void InitChannel() + public async Task InitChannel() { if (Redis is null) throw new Exception("redis未初始化"); //处理之前程序结束前未能执行完的情况 @@ -325,13 +377,13 @@ namespace VideoAnalysisCore.Common var oldTaskArr = Redis.LRange(RedisExpandKey.IDTask, 0, oldTaskCount); //不自动清理未完成任务 等待执行完毕/失败后自动清理 //Redis.LTrim(RedisExpandKey.IDTask, 1, 0);//删除 redis 列表 - foreach (var oldTask in oldTaskArr.Take(10)) + foreach (var oldTask in oldTaskArr) { _ = Task.Run(async () => { try { - Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收上次未完成任务 " + oldTask); + await AddTaskLog(oldTask, DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收上次未完成任务 " + oldTask); await ClearTaskError(long.Parse(oldTask)); var lastEnum = (await Redis.HMGetAsync(RedisExpandKey.Task(oldTask), "LastEnum")).FirstOrDefault(); await InsertChannel(lastEnum, oldTask); @@ -404,14 +456,14 @@ namespace VideoAnalysisCore.Common } lock (Redis) { - if (Subscribe?.IsUnsubscribed == false)//排除重试机制后 多次接收任务导致内存泄露 + if (Subscribe?.IsUnsubscribed == false)//排除重试机制后 多次接收任务导致内存泄露 return; Subscribe = Redis.SubscribeList(RedisExpandKey.ChannelKey, async (taskId) => { if (taskId is null) return; Subscribe?.Dispose();//取消接收任务监听 Redis.LPush(RedisExpandKey.IDTask, taskId); - Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 接收到任务 " + taskId); + await AddTaskLog(taskId, "-------------> 接收到任务 "); await InsertChannel(RedisChannelEnum.下载文件, taskId); }); } @@ -432,10 +484,7 @@ namespace VideoAnalysisCore.Common await Redis.LRemAsync(RedisExpandKey.IDTask, 1, taskID.ToString()); //执行任务时出现异常 error = ex.Message + ex.StackTrace; - Console.WriteLine("====================[出现异常]===================="); - Console.WriteLine(ex.Message); - Console.WriteLine(ex.StackTrace); - Console.WriteLine("=============================================="); + await AddTaskLog(taskID, $""" 出现异常 {ex.Message} {ex.StackTrace} """); //清除失败任务 重新接收任务 NewTask(); } @@ -478,7 +527,7 @@ namespace VideoAnalysisCore.Common var tryCount = 1; for (int i = 0; i < tryCount; i++) { - Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 开始执行 " + key + " " + taskId); + await AddTaskLog(taskId, " 开始执行 " + key + " " + taskId); try { Redis.HMSet(RedisExpandKey.Task(taskId), "LastEnum", key); @@ -493,12 +542,10 @@ namespace VideoAnalysisCore.Common } catch (Exception ex) { - Console.WriteLine("====================[出现异常]===================="); - Console.WriteLine(ex.Message); - Console.WriteLine(ex.StackTrace); - Console.WriteLine("=============================================="); + + await AddTaskLog(taskId, $""" 出现异常 {ex.Message} {ex.StackTrace} """); Thread.Sleep(1000); - Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-> 稍后后重试." + key + " " + taskId); + await AddTaskLog(taskId, "稍后后重试." + key + " " + taskId); if (i + 1 == tryCount) throw; } @@ -506,7 +553,7 @@ namespace VideoAnalysisCore.Common } else { - Console.WriteLine(key + " 任务函数 未实现"); + await AddTaskLog(taskId, "任务函数 未实现." + key); } } diff --git a/VideoAnalysisCore/Job/TaskFileClearJob.cs b/VideoAnalysisCore/Job/TaskFileClearJob.cs index a6b5385..e011188 100644 --- a/VideoAnalysisCore/Job/TaskFileClearJob.cs +++ b/VideoAnalysisCore/Job/TaskFileClearJob.cs @@ -24,10 +24,12 @@ namespace VideoAnalysisCore.Job public class TaskFileClearJob : IInvocable { private readonly Repository videotaskDB; + private readonly RedisManager redisManager; - public TaskFileClearJob(Repository videotaskDB) + public TaskFileClearJob(Repository videotaskDB, RedisManager redisManager) { this.videotaskDB = videotaskDB; + this.redisManager = redisManager; } public void DeleteTaskAllCaches() { @@ -68,7 +70,7 @@ namespace VideoAnalysisCore.Job } } } - public void DeleteTaskVideoCaches() + public async void DeleteTaskVideoCaches() { try { @@ -92,7 +94,7 @@ namespace VideoAnalysisCore.Job // 遍历查询结果,删除缓存文件 foreach (var taskId in completedTasks) - ExpandFunction.DeleteTaskFile(taskId); + await ExpandFunction.DeleteTaskFileAsync(taskId, redisManager); } catch (Exception ex) { diff --git a/VideoAnalysisCore/Model/TaskLog.cs b/VideoAnalysisCore/Model/TaskLog.cs new file mode 100644 index 0000000..ca57001 --- /dev/null +++ b/VideoAnalysisCore/Model/TaskLog.cs @@ -0,0 +1,39 @@ +using SqlSugar; +using System.ComponentModel.DataAnnotations; +using System.Net; +using System.Text.Json; +using UserCenter.Model.Enum; +using VideoAnalysisCore.AICore.GPT.Dto; +using VideoAnalysisCore.AICore.SherpaOnnx; +using VideoAnalysisCore.Model.Enum; +using VideoAnalysisCore.Model.Interface; +using Whisper.net; + +namespace VideoAnalysisCore.Model +{ + /// + /// 分析任务日志 + /// + [SugarTable("tasklog")] + public class TaskLog : IDB + { + /// + /// id + /// + [SugarColumn(IsPrimaryKey = true)] + public long Id { get; set; } + /// + /// 视频任务id + /// + public long VideoTaskId { get; set; } + /// + /// 创建时间 + /// + public DateTime CreateTime { get; set; } + /// + /// 内容 + /// + [SugarColumn(ColumnDataType ="text",IsNullable = true)] + public string? Message { get; set; } + } +}