优化 视频AI分析允许异步

新增 文件订阅管理后台页面
This commit is contained in:
小肥羊 2025-03-12 17:01:29 +08:00
parent c39fbb7501
commit 17f75c975e
17 changed files with 322 additions and 64 deletions

View File

@ -49,6 +49,7 @@ namespace VideoAnalysisRazor.Layouts
Name = "课堂指标",
Key = "EvaluationProject",
Icon = "question-circle",
HideInMenu = true,
},
new MenuDataItem
{
@ -63,6 +64,14 @@ namespace VideoAnalysisRazor.Layouts
Name = "视频任务预览",
Key = "VideoTaskShow",
HideInMenu = true,
},
new MenuDataItem
{
Path = "/NodeSubscriptionPage",
Name = "文件订阅",
Key = "NodeSubscriptionPage",
Icon="clock-circle",
HideInMenu = false,
}
];
}

View File

@ -0,0 +1,56 @@
@page "/NodeSubscriptionPage"
@using AntDesign
@using AntDesign.TableModels
@using System.ComponentModel.DataAnnotations
@using SqlSugar
@using VideoAnalysisCore.Model
@using UserCenter.Model.Enum
<Table @ref="_table" Loading="tableLoading" TItem="NodeSubscription" ScrollY="600px"
PageSize="20" Total="_total" DataSource="_dataSource" @bind-SelectedRows="_selectedRows" OnChange="OnChange">
<TitleTemplate>
<Flex Justify="end" Gap="10">
</Flex>
</TitleTemplate>
<ColumnDefinitions Context="row">
<ActionColumn Title="操作" Width="130px">
<Button Type="primary" @onclick="()=> StartEdit(row)">编辑</Button>
</ActionColumn>
<PropertyColumn Property="c=>c.Id" Width="130px" Filterable="true" Sortable="true" />
<PropertyColumn Property="c=>c.Subject" Filterable="true" Width="130px" />
<PropertyColumn Property="c=>c.TaskType" Width="230px" Filterable="true" />
<PropertyColumn Property="c=>c.NodeId" Width="130px" Filterable="true" />
<PropertyColumn Property="c=>c.Enable" Width="100px" Filterable="true" />
<PropertyColumn Property="c=>c.LastId" Width="200px" />
<PropertyColumn Property="c=>c.CreateTime" />
</ColumnDefinitions>
</Table>
@{
RenderFragment modelfooter = @<Template>
<Button OnClick="@EditOnOkAsync" @key="@( "submit" )"
Type="primary"
Loading="@modalBtnLoading">
提交
</Button>
<Button OnClick="()=>modalShow = false" @key="@( "back" )">取消</Button>
</Template>;
}
<Modal Title="@("编辑订阅节点")" Visible="modalShow" Width="650"
Footer="@modelfooter">
<Form @ref="form" Model="rowData" LabelAlign="AntLabelAlignType.Left">
<GridRow>
<GridCol Span="24">
<FormItem Label="是否启用">
<Switch Rows="1" @bind-Value="@context.Enable" />
</FormItem>
</GridCol>
</GridRow>
</Form>
</Modal>
@code
{
}

View File

@ -0,0 +1,132 @@
using AntDesign;
using AntDesign.TableModels;
using FreeRedis;
using Learn.VideoAnalysis.Controllers.Dto;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Components.Web;
using SqlSugar;
using System.Drawing;
using System.Linq.Expressions;
using System.Reflection;
using UserCenter.Model;
using UserCenter.Model.Enum;
using VideoAnalysisCore.Common;
using VideoAnalysisCore.Model.Enum;
using VideoAnalysisCore.Model;
namespace Learn.VideoAnalysis.Components.Pages
{
public partial class NodeSubscriptionPage : ComponentBase
{
[Inject] private ConfirmService ComfirmService { get; set; } = default!;
[Inject] private ModalService ModalService { get; set; } = default!;
[Inject] private Repository<NodeSubscription> criteria { get; set; } = default!;
[Inject] private INotificationService _notice { get; set; } = default!;
IEnumerable<NodeSubscription> _selectedRows = [];
ITable _table;
IForm? form;
List<NodeSubscription> _dataSource = null;
RefAsync<int> _total = 0;
bool tableLoading = false;
Table<NodeSubscription> tableRef;
List<NodeSubscription> _editSource = null;
bool modalShow =false;
bool modalBtnLoading = false;
NodeSubscription rowData;
SubjectEnum editSubject;
async void SubjectEnumSelect()
{
_editSource = await criteria.GetListAsync(x => x.Subject == editSubject);
await this.InvokeAsync(StateHasChanged);
}
void EditAddRow()
{
if (form is not null && form.Validate())
{
var data = rowData;
data.Subject = editSubject;
if (_editSource is null)
_editSource = new() { data };
else
_editSource.Add(data);
rowData = new();
StateHasChanged();
}
}
/// <summary>
/// 新增或者修改
/// </summary>
void StartEdit(NodeSubscription data)
{
rowData = data?? new();
modalShow = true;
}
async Task EditOnOkAsync()
{
var data = rowData;
modalBtnLoading = true;
await criteria.DeleteAsync(s => s.Subject == editSubject);
await criteria.InsertRangeAsync(_editSource);
_table.ReloadData();
modalShow = false;
modalBtnLoading = false;
StateHasChanged();
}
/// <summary>
/// 分页 查询 筛选 时
/// </summary>
/// <param name="query"></param>
async void OnChange(QueryModel<NodeSubscription> query)
{
tableLoading = true;
List<IConditionalModel> where = default!;
if (query.FilterModel != null && ((query.FilterModel?.Count() ?? 0) > 0))
{
where = query.ToSqlSugerWhere();
}
_dataSource = await criteria.AsQueryable()
.Where(where)
.ToPageListAsync(query.PageIndex, query.PageSize, _total);
tableLoading = false;
StateHasChanged();
}
/// <summary>
/// 删除行
/// </summary>
/// <param name="row"></param>
/// <returns></returns>
async Task Delete(NodeSubscription row)
{
if (!await Comfirm($"确定要删除这条数据吗? [{row.NodeId}]?"))
return;
await criteria.DeleteByIdAsync(row.Id);
_table.ReloadData();
}
/// <summary>
/// 初始化
/// </summary>
protected override void OnInitialized()
{
}
private async Task<bool> Comfirm(string message)
{
return await ComfirmService.Show(message, "提示", ConfirmButtons.YesNo, ConfirmIcon.Warning) == ConfirmResult.Yes;
}
}
}

View File

@ -0,0 +1,9 @@
input[aria-hidden="true"] {
display: none !important;
}
.displayNone {
display:none !important;
}
.ant-table-pagination {
display:none !important;
}

View File

@ -25,7 +25,7 @@ namespace Learn.VideoAnalysis.Expand
provider.UseScheduler(scheduler =>
{
//每5分钟执行一次 未处理视频扫描
//scheduler.Schedule<NodeSubscriptionJob>().EveryFiveMinutes();
scheduler.Schedule<NodeSubscriptionJob>().EveryFiveMinutes();
//每天两点
scheduler.Schedule<TaskFileClearJob>().DailyAtHour(2);
});

View File

@ -157,7 +157,7 @@ namespace Learn.VideoAnalysis.Expand
foreach (var t in entityTypes)
{
Console.Write($"【1】{entityTypes.Count()}/{(++i).ToString().PadLeft(totalCount, '0')} 执行 {t.FullName}".PadRight(60, ' '));
DbScoped.SugarScope.CodeFirst.InitTables(t);
DbScoped.Sugar.CodeFirst.InitTables(t);
Console.WriteLine($"【√】");
}
Console.WriteLine($"【1】数量{entityTypes.Count()} 执行完毕");

View File

@ -43,6 +43,9 @@
</ItemGroup>
<ItemGroup>
<Content Update="Components\Pages\NodeSubscriptionPage.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>
<Content Update="Components\Pages\Login.razor">
<ExcludeFromSingleFile>true</ExcludeFromSingleFile>
</Content>

View File

@ -58,6 +58,7 @@ namespace Learn.VideoAnalysis
//³õʼ»¯ ²å¼þ
builder.Services.AddSqlSugarExpand();
builder.Services.AddDownloadFileExpand(2);
builder.Services.AddAlibabaCloudVod();
builder.Services.AddRedisExpand();
builder.Services.AddSpeakerAI();
@ -93,6 +94,7 @@ namespace Learn.VideoAnalysis
builder.Services.Configure<ProSettings>(builder.Configuration.GetSection("ProSettings"));
builder.Services.AddHttpClient();
builder.Services.AddHttpContextAccessor();
builder.Services.AddSingleton<ChatGPTClient>();
builder.Services.AddSingleton<DeepSeekGPTClient>();
//builder.Services.AddSingleton<IBserGPT, KIMI_GPT>();

View File

@ -33,7 +33,7 @@ namespace VideoAnalysisCore.AICore.FFMPGE
public static async Task Audio2WAV16KAsync(string task)
{
Task = task;
var filePath = await DbScoped.SugarScope
var filePath = await DbScoped.Sugar
.Queryable<VideoTask>()
.Where(s => s.Id == long.Parse(task))
.Select(s=>s.LocalMediaPath).FirstAsync();

View File

@ -88,7 +88,7 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
throw new Exception("未能找到对应知识点=>" + fileNameInfoRes.);
//提升到父级
var kInfo = await knowledgeInfoDB.GetByIdAsync(know.Parent_Id);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0? kInfo.Id: kInfo.Parent_Id);
var knowledgeInfos = await knowledgeInfoDB.AsQueryable().ToChildListAsync(s => s.Parent_Id, kInfo.Parent_Id == 0 ? kInfo.Id : kInfo.Parent_Id);
var knows = string.Join(',', knowledgeInfos.Select(s => s.Id + "|" + s.Name));
var knowDic = knowledgeInfos
.OrderBy(s => s.Id)
@ -112,15 +112,17 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var resFormat = """[{"StartTime":开始秒(number),"Theme":主题(string),"Content":内容总结(string)}]""";
var postMessages =
$"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后分析出<知识块>,来帮助学生快速了解视频字幕的内容" +
$"你的任务是分析视频字幕内容并提取出中国高考考试试题方法点,然后分析出<知识块>来帮助学生快速了解视频字幕的内容" +
$"通过阅读并理解字幕内容.然后识别出{subject}学科中属于{fileNameInfoRes.授课章节}章节相关的时间段。" +
$"关联合并知识内容相似的知识点来合并为<知识块>。" +
$"分配空余未使用的时间段到内容相近的<知识块>时间区间来获取更加详细的上下文,但是请避免<知识块>之间时间重合。" +
$"字幕格式(开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
$"最后请检查某些<知识块>之间的过渡是否自然,如果<知识块>时长超过500秒则考虑拆封为两个更加贴切的<知识块>.或者<知识块>时长小于30秒则考虑合并<知识块>到相邻的<知识块>)。" +
$"字幕格式(开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。" +
$"字幕列表 {captions.Captions}。" +
$"最后请检查某些<知识块>之间的过渡是否自然,如果<知识块>时长超过500秒则考虑拆封为两个更加贴切的<知识块>。" +
$"请检查<知识块>时长小于30秒则考虑合并<知识块>到相邻的<知识块>)。" +
$"输出内容只返回json格式({resFormat})";
Console.WriteLine(DateTime.Now + "=>开始分析视频内容");
Console.WriteLine(DateTime.Now + "=>1.开始分析视频内容");
questionRes = await ChatAsync<VideoKnowRes[]>(task, postMessages, null);
@ -130,54 +132,55 @@ namespace VideoAnalysisCore.AICore.GPT.DeepSeek
var thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>());// string.Join(',', questionRes.Select(s => s.StartTime + "->" + s.Theme));
var checkResFormat1 = """[{"StartTime":开始秒(number),"KnowPoint":知识点名称(string),"KnowPointId":知识点Id(string)}]""";
var knowStr = string.Join(',', knowledgeInfos.Select(s => s.Name));
var knowMessages =
$"我针对视频<{title}>分析出了一些视频的知识片段,现在需要你帮我将每个片段分配恰当的知识点(单个片段允许多个知识点用逗号','分割)。" +
$"这是我的分段 {thems}。" +
$"提供的知识点名称({knows})。 格式 (方法点Id|方法点名称) " +
$"最后请确保分配的知识点是用户提供的,否则片段知识点值留空!。" +
$"输出内容只返回json格式({checkResFormat1})";
Console.WriteLine(DateTime.Now + "=>开始分析视频内容知识点");
Console.WriteLine(DateTime.Now + "=>2.开始分析视频内容知识点");
var konwRes = await ChatAsync<VideoKnowRes[]>(task, knowMessages, null);
for (int i = 0; i < konwRes.Count(); i++)
questionRes[i].KnowPoint = konwRes[i].KnowPoint;
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;
}
thems = JsonSerializer.Serialize(questionRes.Adapt<VideoKnowQueryDto[]>());
var checkResFormat = """{"Score":打分(number),"Evaluation":评价(string)""";//,"Data":优化后的分段(array)}""";
var checkMessage = "我为视频的讲解内容做了一些分段,你能帮我检查下这些分段的时间,主题,知识点分配是否合理符合实际吗?" +
$"请给出你的打分(0-100,70分及格)以及打分原因" +//,并且给出优化后的分段 分段格式(${checkResFormat1})" +
//$"如果不合理的话强帮我修改优化他们(注意优化知识点时只允许使用已存在的知识点不允许杜撰猜测捏造)" +
$"这是我的分段 {thems}." +
var checkMessage = "我为视频的讲解内容做了一些分段,希望你能通读字幕内容后检查下的分段是否符合我的要求?" +
$"检查这些分段的时间是否合理 与相邻的时间段间隔是否处于合理区间30~900秒之间?" +
$"分段的主题内容,知识点分配是否合理符合实际吗?" +
$"请给出你的打分(0-100,70分及格)以及打分原因。" +
$"这是我的分段 {thems}。" +
$"后续的内容是包含时间戳的视频字幕的固定格式文本。" +
$"字幕格式(说话人:开始秒:结束秒:内容|下一段字幕).以下是包含时间的视频字幕文本。字幕列表 {captions.Captions}。" +
$"最后输出格式为json({checkResFormat})";
Console.WriteLine(DateTime.Now + "=>开始检查视频分段结果");
Console.WriteLine(DateTime.Now + "=>3.开始检查视频分段结果");
var checkRes = await ChatAsync<CheckMessageDto>(task, checkMessage, null);
if (checkRes != null && checkRes.Score >= 80 && questionRes.Count() == checkRes.Data.Count())
if (checkRes != null && checkRes.Score >= 80)
{
for (int i = 0; i < checkRes.Data.Count(); i++)
{
questionRes[i].Theme = checkRes.Data[i].Theme;
questionRes[i].StartTime = checkRes.Data[i].StartTime;
}
break;
}
else
{
Console.WriteLine(DateTime.Now + $"=>{task} 得分过低/分段长度不匹配 得分{checkRes?.Score} 长度 {questionRes.Count()}/{checkRes.Data.Count()}");
Console.WriteLine(DateTime.Now + $"=>{task} 得分过低/分段长度不匹配 得分{checkRes?.Score} ");
Console.WriteLine(checkRes.Evaluation);
Console.WriteLine();
}
if (questionRes.Any(s => s.KeepTime < 30))
{
Console.WriteLine(DateTime.Now + "=>视频分段过短!! 重新进行AI分析");
continue;
}
}
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;
}
//todo 未包含的知识点片段 如何处理
var insertData = questionRes

View File

@ -262,13 +262,16 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
Console.WriteLine(DateTime.Now + "=> SenseVoice 字幕数量"+ res.Count);
var captionsStr = JsonSerializer.Serialize(res);
await DbScoped.SugarScope
await DbScoped.Sugar
.Updateable<VideoTask>()
.SetColumns(it => it.Captions == captionsStr)
.Where(it => it.Id == long.Parse(task))
.ExecuteCommandAsync();
await RedisExpand.Redis.HMSetAsync(RedisExpandKey.Task(task), "Captions", res);
//RedisExpand.InsertChannel(Enum.RedisChannelEnum.ParsingSpeaker, task);
//分析完成视频字幕后继续接收任务
RedisExpand.NewTask();
RedisExpand.InsertChannel(RedisChannelEnum.ChatModelAnalysis, task);
}

View File

@ -78,7 +78,7 @@ namespace VideoAnalysisCore.AICore.SherpaOnnx
var res = segments.Select(s => new OfflineSpeakerRes(s));
await RedisExpand.Redis.HSetAsync(RedisExpandKey.Task(task), "Speaker", res);
var speakerStr = JsonSerializer.Serialize(res);
DbScoped.SugarScope
DbScoped.Sugar
.Updateable<VideoTask>()
.SetColumns(it => it.Speaker == speakerStr)
.Where(it => it.Id == long.Parse(task));

View File

@ -1,5 +1,6 @@
using AntDesign;
using Downloader;
using Microsoft.Extensions.DependencyInjection;
using SqlSugar;
using SqlSugar.IOC;
using System;
@ -8,22 +9,35 @@ using System.Diagnostics;
using System.IO;
using System.Net;
using System.Threading.Tasks;
using VideoAnalysisCore.Job;
using VideoAnalysisCore.Model;
using VideoAnalysisCore.Model.Enum;
namespace VideoAnalysisCore.Common
{
public static class DownloadFileExpand
{
/// <summary>
/// 初始化下载器
/// </summary>
/// <param name="DownloadSpeed">下载速度mb/s 默认8</param>
public static void AddDownloadFileExpand(this IServiceCollection services, int DownloadSpeed)
{
DownloadFile.DownloadSpeed = DownloadSpeed;
services.AddSingleton<DownloadFile>();
}
}
/// <summary>
///
/// </summary>
public class DownloadFile
{
static DownloadConfiguration Opt { get; set; } = default!;
/// <summary>
/// 初始化下载器
/// </summary>
/// <param name="DownloadSpeed">下载速度mb/s 默认8</param>
static void Init(int DownloadSpeed = 8)
static DownloadConfiguration Opt { get; set; } = default!;
public static int DownloadSpeed { get; set; } = default!;
private readonly Repository<VideoTask> videoTaskDB;
public DownloadFile(Repository<VideoTask> videoTaskDB)
{
Opt = new DownloadConfiguration()
{
@ -77,6 +91,7 @@ namespace VideoAnalysisCore.Common
//}
}
};
this.videoTaskDB = videoTaskDB;
}
// 根据 Content-Type 映射文件后缀
@ -103,15 +118,15 @@ namespace VideoAnalysisCore.Common
/// </summary>
/// <param name="task"></param>
/// <returns></returns>
public static async Task RunTask(string task)
public async Task RunTask(string task)
{
if (Opt is null)
Init();
var taskId = long.Parse(task);
//获取资源文件 地址
var fileUrl =await DbScoped.SugarScope.Queryable<VideoTask>()
.Where(s => s.Id == taskId)
.Select(s=>s.MediaUrl).FirstAsync();
var taskInfo =await videoTaskDB.AsQueryable()
.Where(s => s.Id == taskId).FirstAsync();
if (taskInfo is null || string.IsNullOrEmpty(taskInfo.MediaName) || taskInfo.MediaName.Contains("教研"))
throw new Exception($"任务为null/是教研视频/没有视频课程名称");
var fileUrl = taskInfo.MediaUrl;
if (string.IsNullOrEmpty(fileUrl))
throw new Exception($"任务id[{task}] 资源地址无效 {fileUrl}");
@ -128,8 +143,8 @@ namespace VideoAnalysisCore.Common
var outputPath = Path.Combine(localPath, task + fileExtension);
if (!Directory.Exists(localPath)) Directory.CreateDirectory(localPath);
await DbScoped.SugarScope
.Updateable<VideoTask>()
await videoTaskDB
.AsUpdateable()
.SetColumns(it => it.LocalMediaPath == outputPath)
.Where(it => it.Id == long.Parse(task))
.ExecuteCommandAsync();

View File

@ -180,7 +180,7 @@ namespace VideoAnalysisCore.Common
if (gptRes is null)
throw new Exception("未能读取到GPT处理结果");
var taskData = await DbScoped.SugarScope.Queryable<VideoTask>()
var taskData = await DbScoped.Sugar.Queryable<VideoTask>()
.FirstAsync(s => s.Id == tId);
if (taskData.Captions == "[]")
taskData.Captions = (await Redis.HMGetAsync(RedisExpandKey.Task(task), "Captions")).First();
@ -193,7 +193,7 @@ namespace VideoAnalysisCore.Common
taskData.ChatAnalysisScore = gptRes?.Assessment?.Merit?.Sum(s => s.Score) ?? 0;
taskData.ErrorMessage = string.Empty;
taskData.LastEnum = RedisChannelEnum.EndTask;
await DbScoped.SugarScope.Updateable(taskData)
await DbScoped.Sugar.Updateable(taskData)
.UpdateColumns(it => new
{
it.ChatAnalysis,
@ -205,9 +205,7 @@ namespace VideoAnalysisCore.Common
it.LastEnum,
}).ExecuteCommandAsync();
await Redis.DelAsync(RedisExpandKey.IDTask);
await ReceivingTaskAsync();
//NewTask();
}
/// <summary>
@ -218,8 +216,16 @@ namespace VideoAnalysisCore.Common
if (Redis is null) throw new Exception("redis未初始化");
SubscribeList.Add(RedisChannelEnum.DownloadFile,
(msg) => { TouchChannel(RedisChannelEnum.DownloadFile, msg, DownloadFile.RunTask); });
(msg) => { TouchChannel(RedisChannelEnum.DownloadFile, msg,
(task) =>
{
using var scope = AppCommon.Services?.CreateScope();
if (scope is null || scope.ServiceProvider.GetService<DownloadFile>() is null)
throw new Exception("DownloadFile 未注入");
else
return scope.ServiceProvider.GetService<DownloadFile>()?.RunTask(task) ?? Task.CompletedTask;
});
});
SubscribeList.Add(RedisChannelEnum.SeparateAudio,
(msg) => { TouchChannel(RedisChannelEnum.SeparateAudio, msg, FFMPGEHandle.Audio2WAV16KAsync); });
@ -247,6 +253,19 @@ namespace VideoAnalysisCore.Common
await ReceivingTaskAsync();
}
/// <summary>
/// 重新执行新任务
/// </summary>
/// <returns></returns>
public static void NewTask()
{
Task.Run(async () =>
{
await Redis.DelAsync(RedisExpandKey.IDTask);
await ReceivingTaskAsync();
});
}
/// <summary>
/// 重新接收新任务
/// </summary>
@ -297,7 +316,7 @@ namespace VideoAnalysisCore.Common
Redis.HMSet(RedisExpandKey.Task(taskID), "ErrorMessage", error);
return await DbScoped.SugarScope.Updateable<VideoTask>()
return await DbScoped.Sugar.Updateable<VideoTask>()
.SetColumns(it => it.ErrorMessage == error)//SetColumns是可以叠加的 写2个就2个字段赋值
.Where(it => it.Id == taskID)
.ExecuteCommandAsync() == 1;
@ -323,10 +342,14 @@ namespace VideoAnalysisCore.Common
{
Redis.HMSet(RedisExpandKey.Task(taskId), "LastEnum", key);
Redis.HMSet(RedisExpandKey.Task(taskId), "Progress", 0);
await DbScoped.SugarScope.Updateable<VideoTask>()
.SetColumns(it => it.LastEnum == key)
.Where(it => it.Id == tID)
.ExecuteCommandAsync();
lock (Redis)
{
DbScoped.Sugar.Updateable<VideoTask>()
.SetColumns(it => it.LastEnum == key)
.Where(it => it.Id == tID)
.ExecuteCommand();
}
await action(taskId);
return;
}

View File

@ -23,15 +23,15 @@ namespace VideoAnalysisCore.Common
var t = typeof(T);
if (CID.ContainsKey(t))
{
base.Context = DbScoped.SugarScope.GetConnectionScope(CID[t]);
base.Context = DbScoped.Sugar.GetConnectionScope(CID[t]);
return;
}
var c = t.GetCustomAttribute<TenantAttribute>();
if (!CID.ContainsKey(typeof(T)))
CID.Add(typeof(T), c?.configId);
base.Context = c != null
? DbScoped.SugarScope.GetConnectionScope(c.configId)
: DbScoped.SugarScope;
? DbScoped.Sugar.GetConnectionScope(c.configId)
: DbScoped.Sugar;
}
}
}

View File

@ -33,11 +33,12 @@ namespace VideoAnalysisCore.Model
/// <summary>
/// 绑定学科
/// </summary>
[DisplayName("绑定学科")]
[DisplayName("任务类型")]
public TaskTypeEnum TaskType { get; set; }
/// <summary>
/// 学科
/// </summary>
[DisplayName("学科")]
public SubjectEnum Subject { get; set; }
/// <summary>
/// 是否启用

View File

@ -101,6 +101,8 @@ namespace VideoAnalysisCore.Model
/// 创建时间
/// </summary>
public DateTime CreateTime { get; set; } = DateTime.Now;
[SugarColumn( IsNullable = true)]
public DateTime EndTime { get; set; }
/// <summary>
/// 开始时间轴
/// </summary>