Compare commits

...

4 Commits

Author SHA1 Message Date
小肥羊 53951aa870 修复 过时的AI请求temperature 字段 2026-02-06 17:15:29 +08:00
小肥羊 d948f854fb 调试 FunASR的STT,修复流程上的bug 2026-01-30 17:23:40 +08:00
小肥羊 eed63794b8 修复 Ai字幕展示的方式
新增 页面持久化
2026-01-21 15:19:54 +08:00
小肥羊 43a59dd593 优化 字幕清洗方案 2026-01-21 13:08:08 +08:00
24 changed files with 214 additions and 58 deletions

View File

@ -18,7 +18,9 @@ namespace Learn.VideoAnalysis.Expand
Console.WriteLine($"{DateTime.Now}=>初始化 Coravel");
service.AddScheduler();
#if !DEBUG
service.AddTransient<TaskFileClearJob>();
#endif
service.AddTransient<NodePackageJob>();
}
public static void UseCoravelExpand(this IApplicationBuilder provider)

View File

@ -100,6 +100,7 @@ namespace Learn.VideoAnalysis
AppCommon.Services = app.Services;
app.UseMiddleware<BasicAuthMiddleware>("Swagger");
// Configure the HTTP request pipeline.
//¿ªÆôredis¶ÓÁзþÎñ
_ = app.Services.GetRequiredService<RedisInit>();
app.UseSwagger();
app.UseSwaggerUI();

View File

@ -18,7 +18,8 @@ export default {
component: () => import("@/views/welcome/runningTask.vue"),
meta: {
title: "进行中任务",
showLink: true
showLink: true,
keepAlive:true,
}
},
{
@ -36,7 +37,8 @@ export default {
component: () => import("@/views/welcome/showTask.vue"),
meta: {
title: "预览任务",
showLink: false
showLink: false,
keepAlive:true,
}
},
{
@ -45,7 +47,8 @@ export default {
component: () => import("@/views/welcome/errorTask.vue"),
meta: {
title: "错误任务",
showLink: true
showLink: true,
keepAlive:true,
}
}
]

View File

@ -139,6 +139,7 @@ function previewTask(row: any) {
meta: {
title: `任务预览` + row.id.toString().slice(-4),
dynamicLevel: 3,
keepAlive: true,
},
});
//

View File

@ -155,9 +155,13 @@ function timeupdateVideo() {
let subtitleI = subtitles.value.findIndex(
(subtitle) => currentTime >= subtitle.start && currentTime <= subtitle.end
);
// AI
let subtitleI1 = subtitles1.value.findLastIndex(
(subtitle) => currentTime >= subtitle.start
);
if (subtitleI > -1 && currentSubtitle.value !== subtitles.value[subtitleI].text) {
currentSubtitle.value = subtitles.value[subtitleI].text;
currentSubtitle1.value = subtitles1.value[subtitleI]?.text || "";
currentSubtitle1.value = subtitles1.value[subtitleI1]?.text || "";
} else if (subtitleI == -1) {
currentSubtitle.value = "";
currentSubtitle1.value = "";

View File

@ -62,12 +62,6 @@ namespace VideoAnalysisCore.AICore.GPT
public float? max_tokens { get; set; } = 8000;
public float? max_completion_tokens { get; set; } = 8000;
/// <summary>
/// 要使用的采样温度,介于 0 和 2 之间。较高的值(如 0.8)将使输出更加随机,而较低的值(如 0.2)将使其更加集中和确定。 我们通常建议更改此项或同时更改两者。top_p
/// <para> 默认为 1</para>
/// <para> <see cref="ChatRequest.top_p"/>联动</para>
/// </summary>
public float? temperature { get; set; } = 0.2f;
/// <summary>
/// 一种替代温度采样的方法,称为原子核采样, 其中模型考虑具有top_p概率的标记的结果 质量。所以 0.1 表示仅包含前 10% 概率质量的代币 被考
/// <para>建议与<see cref="ChatRequest.temperature"/>联动</para>
/// </summary>

View File

@ -58,7 +58,6 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
title = title,
max_tokens = max_tokens,
stream = true,
temperature = 0.2f,
messages = messageArr
};

View File

@ -57,7 +57,6 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
model = model ?? ChatGPTType.Deepseek_Chat,
max_tokens = model == ChatGPTType.Deepseek_Reasoner ? 32000 : max_tokens,
stream = true,
temperature = 0.2f,
messages = messageArr
};
return await base.ChatAsync<T>(chatReq);

View File

@ -1,4 +1,4 @@
using Newtonsoft.Json;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
using System;
using System.Collections.Generic;
@ -34,9 +34,10 @@ namespace VideoAnalysisCore.AICore.GPT.Dto
}
public class VideoKnowPointDto
{
public float KnowPointWeight { get; set; }
public string KnowPoint { get; set; }
public string KnowPointId { get; set; }
public float KnowSourceTime { get; set; }
public float KnowPointWeight { get; set; }
public string KnowSource { get; set; }
}
public class VideoKnowRes
@ -65,10 +66,6 @@ namespace VideoAnalysisCore.AICore.GPT.Dto
/// </summary>
public virtual string? KnowPoint { get; set; }
/// <summary>
/// 知识点权重
/// </summary>
public virtual float? KnowPointWeight { get; set; }
/// <summary>
/// 知识点ID
/// </summary>
public virtual string? KnowPointId { get; set; }

View File

@ -31,7 +31,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
/// <summary>
/// 视频分析工作流1
/// </summary>
public class GTP_Analysis_1 : IBserGPTWorkflow
public class GTP_Analysis_1 : IBserGPTWorkflow
{
private readonly GeminiGPTClient geminiClient;
private readonly DeepSeekGPTClient deepSeekClient;
@ -187,7 +187,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
$"字幕列表 {rCaptionArr}。" +
$"输出格式 json字符串 对象格式{fileNameResFormat}";
var task = taskInfo.Id.ToString();
var fileNameInfoRes = await chatGPTClient.ChatAsync<FileNameInfo>
var fileNameInfoRes = await geminiClient.ChatAsync<FileNameInfo>
(task, fileNamePostMessages, "授课章节");
taskInfo.Sections = fileNameInfoRes.;
await videoTaskDB.AsUpdateable()
@ -309,7 +309,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
await Parallel.ForAsync(0, totalCount,
new ParallelOptions()
#if DEBUG
{ MaxDegreeOfParallelism = 1 },
{ MaxDegreeOfParallelism = 4 },
#else
{ MaxDegreeOfParallelism = 9 },
#endif
@ -326,25 +326,23 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var postMessages =
$$"""
# Role
{{subject}}{{sections}}STTJSON
{{subject}},{{sections}}STTJSON
# Input & Output Protocol
JSON `[{"t": number, "r": string}]`
* `t` (Time):
* `r` (Result):
# Processing Rules ()
`t` (Time):
`r` (Result):
Processing Rules ()
1. ()
N N
i `t` i `t`
使
2. ()
2.
`r` `""`
LaTeX / LaTeX ( `$\\frac{a}{b}$`)JSON (`\\\\`)
3. ()
3.
`r` `""`
LaTeX / LaTeX ( `$\\frac{a}{b}$`)JSON (`\\\\`)
# Task Data
( {{cStrArr.Count()}} ):
{{nowCaptionStr}}
@ -436,7 +434,13 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
}
return null;
}
/// <summary>
/// 作业内容检查
/// </summary>
/// <param name="taskInfo"></param>
/// <param name="captions"></param>
/// <param name="sections"></param>
/// <returns></returns>
private async Task<VideoKnowRes?> DetectHomeworkAssignment(VideoTask taskInfo, TotalCaptionsDto captions, string sections)
{
if (captions is null || string.IsNullOrWhiteSpace(captions.Captions))
@ -721,6 +725,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
throw new Exception("未能找到对应知识点=>" + sections);
List<KnowledgeInfo>? knowledgeInfos = new List<KnowledgeInfo>();
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
if (know.Parent_Id == 0)
kInfo = know;
try
{
knowledgeInfos = await knowledgeInfoDB.AsQueryable()
@ -796,6 +802,10 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
Stage = s.First().Stage,
Theme = s.First().Theme,
VideoTaskId = taskInfo.Id,
CourseLevel = taskInfo.CourseLevel,
TextBookVersionId = taskInfo.TextBookVersionId,
GradeSemester= taskInfo.GradeSemester,
GradeId = taskInfo.GradeId,
}).ToList();
//尝试追加 作业布置分段
if (homework != null && (!questionRes.Any(s => s.Stage == StageEnum..ToString())))
@ -846,6 +856,8 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
if (know is null)
throw new Exception("未能找到对应知识点=>" + sections);
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
if (know.Parent_Id == 0)
kInfo = know;
var knowledgeInfos = await knowledgeInfoDB.AsQueryable()
.ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
//开始分析复习课 试题

View File

@ -56,7 +56,6 @@ namespace VideoAnalysisCore.AICore.GPT.ChatGPT
model = model,
max_tokens = max_tokens,
stream = true,
temperature = 0.2f,
messages = messageArr,
max_completion_tokens= 12288,
};

View File

@ -11,6 +11,7 @@ using System.Text;
using System.Text.Json;
using System.Text.RegularExpressions;
using System.Threading.Tasks;
using UserCenter.Model.Enum;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Enum;
@ -49,7 +50,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
/// </summary>
/// <param name="numThreads">默认6线程</param>
/// <param name="useGPU">是否使用gpu 报错请看安装CUDA环境 <see cref="https://k2-fsa.github.io/sherpa/onnx/pretrained_models/whisper/large-v3.html#run-with-gpu-float32"/></param>
public void Init(int numThreads = 6, bool useGPU = false, bool useHotwords = false)
public void Init(SubjectEnum? subject = null, int numThreads = 10, bool useGPU = false, bool useHotwords = false)
{
Console.WriteLine("初始化 FunASRNano");
OfflineRecognizerConfig config = new OfflineRecognizerConfig();
@ -63,17 +64,23 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
//将非结构化数据(文本、图像、音频等)转换为低维稠密向量
config.ModelConfig.FunAsrNano.EncoderAdaptor = Path.Combine(topFolder, "encoder_adaptor.int8.onnx");
//接入的大语言模型
config.ModelConfig.FunAsrNano.LLM = Path.Combine(topFolder, "llm.fp16.onnx");
//config.ModelConfig.FunAsrNano.LLM = Path.Combine(topFolder ,"llm.fp16.onnx");
config.ModelConfig.FunAsrNano.LLM = Path.Combine(topFolder, "int8-2025-12-30", "llm.int8.onnx");
//插入预训练模型如Transformer的小型可训练模块 (如语音识别、情感分析)
config.ModelConfig.FunAsrNano.Embedding = Path.Combine(topFolder, "embedding.int8.onnx");
//分词器
config.ModelConfig.FunAsrNano.Tokenizer = Path.Combine(topFolder, "Qwen3-0.6B");
//提示词
config.ModelConfig.FunAsrNano.SystemPrompt = "You are a professional video audio transcription assistant.";
config.ModelConfig.FunAsrNano.UserPrompt = "这是一堂中国的课堂视频音频,请你帮我分析出它讲述的内容!";
//加上学科为空的处理
if (subject != null)
config.ModelConfig.FunAsrNano.UserPrompt = $"这是一堂中国{subject}的课堂视频音频,请你帮我分析出它讲述的内容!";
else
config.ModelConfig.FunAsrNano.UserPrompt = "这是一堂中国课堂的视频音频,请你帮我分析出它讲述的内容!";
config.ModelConfig.FunAsrNano.MaxNewTokens = 512;
config.ModelConfig.FunAsrNano.Temperature = 1E-06f;
config.ModelConfig.FunAsrNano.TopP = 0.8f;
config.ModelConfig.FunAsrNano.TopP = 0.7f;
//种子
config.ModelConfig.FunAsrNano.Seed = 42;
//模型类型
@ -81,10 +88,10 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
config.ModelConfig.NumThreads = numThreads;
config.ModelConfig.Provider = "cpu";
//需要使用GPU
if (!useGPU)
if (useGPU)
config.ModelConfig.Provider = "cuda";
#if DEBUG
config.ModelConfig.Debug = 1;
//config.ModelConfig.Debug = 1;
#endif
OR = new OfflineRecognizer(config);
}
@ -106,15 +113,17 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public Task RunTask(string task)
public Task RunTask(string task)
{
var taskInfo = serviceProvider.GetRequiredService<Repository<VideoTask>>().GetById(task);
if(taskInfo is null)
throw new Exception("task 未找到");
var filePath = Path.Combine(task.LocalPath(), "task.wav");
if (string.IsNullOrEmpty(filePath) || !File.Exists(filePath))
throw new Exception("task 音频路径未找到");
if (OR is null) Init();
if (OR is null) Init(taskInfo.Subject);
serviceProvider.GetRequiredService<SherpaVad>()
.TaskHandle(new WaveReader(filePath), null, SoundHandle, SherpaVadVersion.ten_vad_324);
.TaskHandle(new WaveReader(filePath), task, SoundHandle, SherpaVadVersion.ten_vad_324);
return Task.CompletedTask;
}
/// <summary>

View File

@ -90,7 +90,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
VADModelConfig.SileroVad = new SileroVadModelConfig();
VADModelConfig.SileroVad.Model = path;
//(阈值 / 灵敏度) 含义:判定为“语音”的置信度。取值范围通常在 0 到 1 之间。
VADModelConfig.SileroVad.Threshold = 0.3f;
VADModelConfig.SileroVad.Threshold = 0.25f;
//(最小静音长度)秒。 含义:“要沉默多久,我才认为这句话说完了?”
VADModelConfig.SileroVad.MinSilenceDuration = 0.2f;
// (最小语音长度)秒 含义:“这段声音至少要多长,我才认为它是有效的说话?”

View File

@ -255,7 +255,7 @@ namespace VideoAnalysisCore.Common
}
}
var sp = spList.Distinct().ToList();
if (sp.Count > 0)
if (sp.Count > 0 && !string.IsNullOrWhiteSpace(segment.Text))
results.Add(segment, sp);
}

View File

@ -110,12 +110,14 @@ namespace VideoAnalysisCore.Common
public FFMPGEHandle FFMPGE { get; set; }
public SenseVoice senseVoice { get; set; }
public FunASRNano funASRNano { get; set; }
public RedisManager redisManager { get; set; }
public RedisInit(FFMPGEHandle fFMPGE, SenseVoice senseVoice, RedisManager redisManager)
public RedisInit(FFMPGEHandle fFMPGE, SenseVoice senseVoice, RedisManager redisManager, FunASRNano funASRNano)
{
FFMPGE = fFMPGE;
this.senseVoice = senseVoice;
this.funASRNano = funASRNano;
this.redisManager = redisManager;
Init();
redisManager.InitChannel();
@ -138,6 +140,7 @@ namespace VideoAnalysisCore.Common
});
SubscribeList.Add(RedisChannelEnum., FFMPGE.RunAsync);
SubscribeList.Add(RedisChannelEnum., senseVoice.RunTask);
//SubscribeList.Add(RedisChannelEnum.解析字幕, funASRNano.RunTask);
//SubscribeList.Add(RedisChannelEnum.解析说话人,Speaker.Run);
SubscribeList.Add(RedisChannelEnum.AI课程类型, async (task) =>
{
@ -430,15 +433,20 @@ namespace VideoAnalysisCore.Common
var oldTaskCount = Redis.LLen(RedisExpandKey.IDTask);
//重试任务并发过多可能会导致程序崩溃
// 未能重新分析的中断任务 则单独开一个网页来处理
if (oldTaskCount > 0 )
if (oldTaskCount > 0)
{
var oldTaskArr = Redis.LRange(RedisExpandKey.IDTask, 0, oldTaskCount);
//不自动清理未完成任务 等待执行完毕/失败后自动清理
//Redis.LTrim(RedisExpandKey.IDTask, 1, 0);//删除 redis 列表
//最多执行5个上次中断/或者未完成的任务
foreach (var oldTask in oldTaskArr.Take(5))
//获取所有未完成的任务
var oldTaskArr = Redis.LRange(RedisExpandKey.IDTask, 0, -1);
Console.WriteLine($"{DateTime.Now:HH:mm:ss}-------------> 发现 {oldTaskArr.Length} 个未完成任务,准备恢复...");
//使用信号量限制并发数(5),防止崩溃
using var semaphore = new System.Threading.SemaphoreSlim(5);
var retryTaskArr = new List<Task>();
foreach (var oldTask in oldTaskArr)
{
_ = Task.Run(async () =>
await semaphore.WaitAsync();
var res = Task.Run(async () =>
{
try
{
@ -450,10 +458,21 @@ namespace VideoAnalysisCore.Common
catch (Exception ex)
{
await SetTaskErrorMessage(long.Parse(oldTask), ex);
throw;
Console.WriteLine($"恢复任务 {oldTask} 失败: {ex.Message}");
}
finally
{
semaphore.Release();
}
});
retryTaskArr.Add(res);
}
//等待所有 重试任务完成后接收新任务
await Task.WhenAll(retryTaskArr);
Console.WriteLine(DateTime.Now.ToString("HH:mm:ss") + "-------------> 所有未完成任务处理完毕!");
ReceivingTaskAsync();
}
else
{

View File

@ -128,11 +128,23 @@ namespace VideoAnalysisCore.Controllers.Dto
/// 用户中心的云校id
/// </summary>
public long? UserCenterCloudSchoolId { get; set; }
/// <summary>
/// 教材层次
/// </summary>
public CourselevelTypeEnum? CourseLevel { get; set; }
/// <summary>
/// 年级
/// </summary>
public GradeEnum? GradeId { get; set; }
/// <summary>
/// 教育阶段
/// </summary>
public EducationStageEnum StageId { get; set; }
/// <summary>
/// 年级册(上册/下册)
/// </summary>
public GradeSemesterEnum? GradeSemester { get; set; }
}
/// <summary>
/// 视频处理 请求

View File

@ -115,6 +115,10 @@ namespace VideoAnalysisCore.Controllers
CloudSchoolId =s.UserCenterCloudSchoolId,
Area = s.Area,
HostIP = s.HostIP,
StageId = s.StageId,
GradeId = s.GradeId,
GradeSemester = s.GradeSemester,
TextBookVersionId = s.TextBookVersionId,
};
nodePackages.Add(np);
if (videoIdArr.Contains(s.VideoCode)) continue;
@ -134,6 +138,10 @@ namespace VideoAnalysisCore.Controllers
PPTVideoUrl = pptUrl,
VideoType = s.CourseType,
CloudSchoolId = s.UserCenterCloudSchoolId,
TextBookVersionId = s.TextBookVersionId,
GradeSemester =s .GradeSemester,
CourseLevel =s.CourseLevel,
GradeId = s.GradeId,
});
}
await nodePackageInfoDB.InsertRangeAsync(nodePackages);

View File

@ -153,8 +153,8 @@ namespace VideoAnalysisCore.Controllers
public IActionResult AudioRecognition(IFormFile file)
{
using var s = file.OpenReadStream();
senseVoice.RunTask(s);
return Ok();
var res = senseVoice.RunTask(s);
return Ok(res);
}
/// <summary>
/// 语音识别
@ -266,7 +266,7 @@ namespace VideoAnalysisCore.Controllers
TagId = req.TagId,
PPTVideoCode = req.PPTVideoCode,
PPTVideoUrl = req.PPTVideoUrl,
VideoType = req.VideoType
VideoType = req.VideoType,
};
//入库
await baseService.InsertAsync(task);

View File

@ -0,0 +1,18 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VideoAnalysisCore.Model.Enum
{
/// <summary>
/// 课程层次
/// </summary>
public enum CourselevelTypeEnum
{
= 1,
= 2,
= 3
}
}

View File

@ -0,0 +1,17 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace VideoAnalysisCore.Model.Enum
{
/// <summary>
/// 年级层次
/// </summary>
public enum GradeSemesterEnum
{
= 1,
= 2
}
}

View File

@ -83,6 +83,25 @@ namespace VideoAnalysisCore.Model
/// </summary>
public string Area { get; set; }
/// <summary>
/// 年级
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeEnum? GradeId { get; set; }
/// <summary>
/// 教育阶段
/// </summary>
[SugarColumn(IsNullable = true)]
public EducationStageEnum StageId { get; set; }
/// <summary>
/// 年级册(上册/下册)
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeSemesterEnum? GradeSemester { get; set; }
/// <summary>
/// 学科网的教材版本Id
/// </summary>
public long TextBookVersionId { get; set; }
/// <summary>
/// 请求区域
/// <para>回调添加到Headers</para>
/// </summary>

View File

@ -145,5 +145,26 @@ namespace VideoAnalysisCore.Model
[SugarColumn(IsNullable = true)]
public long? CloudSchoolId { get; set; }
/// <summary>
/// 教材层次
/// </summary>
[SugarColumn(IsNullable = true)]
public CourselevelTypeEnum? CourseLevel { get; set; }
/// <summary>
/// 年级
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeEnum? GradeId { get; set; }
/// <summary>
/// 年级册(上册/下册)
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeSemesterEnum? GradeSemester { get; set; }
/// <summary>
/// 学科网的教材版本Id
/// </summary>
public long TextBookVersionId { get; set; }
}
}

View File

@ -57,6 +57,7 @@ namespace VideoAnalysisCore.Model
/// <summary>
/// 知识点来源 视频秒,来源原因
/// </summary>
[SugarColumn(Length = 500, IsNullable = true)]
public string Content { get; set; }
/// <summary>
/// 课程阶段
@ -75,5 +76,26 @@ namespace VideoAnalysisCore.Model
/// </summary>
[SugarColumn(IsNullable = true)]
public virtual string? TextbookSource { get; set; }
/// <summary>
/// 教材层次
/// </summary>
[SugarColumn(IsNullable = true)]
public CourselevelTypeEnum? CourseLevel { get; set; }
/// <summary>
/// 年级
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeEnum? GradeId { get; set; }
/// <summary>
/// 年级册(上册/下册)
/// </summary>
[SugarColumn(IsNullable = true)]
public GradeSemesterEnum? GradeSemester { get; set; }
/// <summary>
/// 学科网的教材版本Id
/// </summary>
public long TextBookVersionId { get; set; }
}
}

View File

@ -71,7 +71,7 @@
<PackageReference Include="Microsoft.Extensions.DependencyModel" Version="7.0.0" />
<PackageReference Include="Microsoft.Extensions.Http" Version="8.0.0" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
<PackageReference Include="org.k2fsa.sherpa.onnx" Version="1.12.21" />
<PackageReference Include="org.k2fsa.sherpa.onnx" Version="1.12.22" />
<PackageReference Include="SixLabors.ImageSharp" Version="3.1.7" />
<PackageReference Include="SqlSugar.IOC" Version="2.0.0" />
<PackageReference Include="SqlSugarCore" Version="5.1.4.205" />