fix: improve event script controls and daily reviews

This commit is contained in:
2026-04-26 21:16:26 +08:00
parent d2b2f714e0
commit ca2c559555
7 changed files with 86 additions and 12 deletions
+3
View File
@@ -383,12 +383,15 @@ export interface SystemSettings {
webDavUpload: WebDavUploadSettings; webDavUpload: WebDavUploadSettings;
s3Upload: S3UploadSettings; s3Upload: S3UploadSettings;
enableEventScripts: boolean; enableEventScripts: boolean;
enableLiveStartedScript: boolean;
liveStartedScriptMode: string; liveStartedScriptMode: string;
liveStartedScriptPath: string; liveStartedScriptPath: string;
liveStartedScriptContent: string; liveStartedScriptContent: string;
enableLiveEndedScript: boolean;
liveEndedScriptMode: string; liveEndedScriptMode: string;
liveEndedScriptPath: string; liveEndedScriptPath: string;
liveEndedScriptContent: string; liveEndedScriptContent: string;
enableSegmentCompletedScript: boolean;
segmentCompletedScriptMode: string; segmentCompletedScriptMode: string;
segmentCompletedScriptPath: string; segmentCompletedScriptPath: string;
segmentCompletedScriptContent: string; segmentCompletedScriptContent: string;
+9 -8
View File
@@ -86,9 +86,10 @@ onMounted(loadReport);
<div class="page-stack"> <div class="page-stack">
<div class="page-header"> <div class="page-header">
<div> <div>
<h1 class="page-title">鍥為鏃ユ姤</h1> <h1 class="page-title">回顾日报</h1>
<p class="page-subtitle"> <p class="page-subtitle">
鎸夊ぉ鑱氬悎褰曞埗鏃堕暱銆佸紓甯稿拰寮瑰箷鐑害銆傛棩鎶ュ彧鍋氶闈㈠唴鏌ョ湅锛屼笉浼氫富鍔ㄦ帹閫併? </p> 按天聚合录制时长异常和弹幕热度日报只做页面内查看不会主动推送
</p>
</div> </div>
<el-space wrap class="header-actions"> <el-space wrap class="header-actions">
@@ -101,7 +102,7 @@ onMounted(loadReport);
:disabled-date="disableFutureDates" :disabled-date="disableFutureDates"
@change="loadReport" @change="loadReport"
/> />
<el-button @click="loadReport">鍒锋柊鏃ユ姤</el-button> <el-button @click="loadReport">刷新日报</el-button>
</el-space> </el-space>
</div> </div>
@@ -203,15 +204,15 @@ onMounted(loadReport);
<span>{{ formatDuration(item.durationSeconds) }}</span> <span>{{ formatDuration(item.durationSeconds) }}</span>
</div> </div>
<div class="highlight-item__stats"> <div class="highlight-item__stats">
<span>鍒嗙墖 {{ item.segmentCount }}</span> <span>分片 {{ item.segmentCount }}</span>
<span>寮瑰箷 {{ item.danmakuCount }}</span> <span>弹幕 {{ item.danmakuCount }}</span>
<span> {{ item.warningCount }}</span> <span>警告 {{ item.warningCount }}</span>
<span>閿欒 {{ item.errorCount }}</span> <span>错误 {{ item.errorCount }}</span>
</div> </div>
<div class="highlight-item__summary">{{ item.summary || "-" }}</div> <div class="highlight-item__summary">{{ item.summary || "-" }}</div>
<div class="highlight-item__footer"> <div class="highlight-item__footer">
<span>{{ formatDate(item.startedAt || item.endedAt) }}</span> <span>{{ formatDate(item.startedAt || item.endedAt) }}</span>
<el-button size="small" @click="openSession(item.recordSessionId)">鏌ョ湅浼氳瘽</el-button> <el-button size="small" @click="openSession(item.recordSessionId)">查看会话</el-button>
</div> </div>
</section> </section>
</div> </div>
+10
View File
@@ -95,12 +95,15 @@ const form = reactive<SystemSettings>({
forcePathStyle: false forcePathStyle: false
}, },
enableEventScripts: false, enableEventScripts: false,
enableLiveStartedScript: false,
liveStartedScriptMode: "path", liveStartedScriptMode: "path",
liveStartedScriptPath: "", liveStartedScriptPath: "",
liveStartedScriptContent: "", liveStartedScriptContent: "",
enableLiveEndedScript: false,
liveEndedScriptMode: "path", liveEndedScriptMode: "path",
liveEndedScriptPath: "", liveEndedScriptPath: "",
liveEndedScriptContent: "", liveEndedScriptContent: "",
enableSegmentCompletedScript: false,
segmentCompletedScriptMode: "path", segmentCompletedScriptMode: "path",
segmentCompletedScriptPath: "", segmentCompletedScriptPath: "",
segmentCompletedScriptContent: "", segmentCompletedScriptContent: "",
@@ -986,6 +989,7 @@ onMounted(loadSettings);
<p class="event-script-section__subtitle">房间首次进入在线状态时触发</p> <p class="event-script-section__subtitle">房间首次进入在线状态时触发</p>
</div> </div>
<div class="event-script-section__actions"> <div class="event-script-section__actions">
<el-switch v-model="form.enableLiveStartedScript" inline-prompt active-text="开" inactive-text="关" />
<el-radio-group v-model="form.liveStartedScriptMode" size="small"> <el-radio-group v-model="form.liveStartedScriptMode" size="small">
<el-radio-button <el-radio-button
v-for="option in eventScriptModeOptions" v-for="option in eventScriptModeOptions"
@@ -1039,6 +1043,7 @@ onMounted(loadSettings);
<p class="event-script-section__subtitle">房间从在线切回离线时触发</p> <p class="event-script-section__subtitle">房间从在线切回离线时触发</p>
</div> </div>
<div class="event-script-section__actions"> <div class="event-script-section__actions">
<el-switch v-model="form.enableLiveEndedScript" inline-prompt active-text="开" inactive-text="关" />
<el-radio-group v-model="form.liveEndedScriptMode" size="small"> <el-radio-group v-model="form.liveEndedScriptMode" size="small">
<el-radio-button <el-radio-button
v-for="option in eventScriptModeOptions" v-for="option in eventScriptModeOptions"
@@ -1092,6 +1097,7 @@ onMounted(loadSettings);
<p class="event-script-section__subtitle">MP4 转码完成并且分片状态变成 completed 后触发</p> <p class="event-script-section__subtitle">MP4 转码完成并且分片状态变成 completed 后触发</p>
</div> </div>
<div class="event-script-section__actions"> <div class="event-script-section__actions">
<el-switch v-model="form.enableSegmentCompletedScript" inline-prompt active-text="开" inactive-text="关" />
<el-radio-group v-model="form.segmentCompletedScriptMode" size="small"> <el-radio-group v-model="form.segmentCompletedScriptMode" size="small">
<el-radio-button <el-radio-button
v-for="option in eventScriptModeOptions" v-for="option in eventScriptModeOptions"
@@ -1150,6 +1156,10 @@ onMounted(loadSettings);
如果脚本想把自定义内容写进系统日志请把文本写入 <code>LIVE_RECORDER_SCRIPT_LOG_PATH</code> 指向的临时文件 如果脚本想把自定义内容写进系统日志请把文本写入 <code>LIVE_RECORDER_SCRIPT_LOG_PATH</code> 指向的临时文件
</div> </div>
<div class="event-script-help__intro">
官方 Docker 镜像默认内置 <code>curl</code> <code>jq</code>如果你使用宿主机直跑或自定义镜像实际可用命令以运行环境为准
</div>
<div class="event-script-example"> <div class="event-script-example">
<div class="event-script-example__label">环境变量示例值</div> <div class="event-script-example__label">环境变量示例值</div>
<div class="event-script-example__grid"> <div class="event-script-example__grid">
@@ -116,18 +116,24 @@ public sealed class SystemSettingsDto
public bool EnableEventScripts { get; set; } = false; public bool EnableEventScripts { get; set; } = false;
public bool EnableLiveStartedScript { get; set; } = false;
public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path; public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string LiveStartedScriptPath { get; set; } = string.Empty; public string LiveStartedScriptPath { get; set; } = string.Empty;
public string LiveStartedScriptContent { get; set; } = string.Empty; public string LiveStartedScriptContent { get; set; } = string.Empty;
public bool EnableLiveEndedScript { get; set; } = false;
public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path; public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string LiveEndedScriptPath { get; set; } = string.Empty; public string LiveEndedScriptPath { get; set; } = string.Empty;
public string LiveEndedScriptContent { get; set; } = string.Empty; public string LiveEndedScriptContent { get; set; } = string.Empty;
public bool EnableSegmentCompletedScript { get; set; } = false;
public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path; public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string SegmentCompletedScriptPath { get; set; } = string.Empty; public string SegmentCompletedScriptPath { get; set; } = string.Empty;
@@ -291,18 +297,24 @@ public sealed class UpdateSystemSettingsRequest
public bool EnableEventScripts { get; set; } = false; public bool EnableEventScripts { get; set; } = false;
public bool EnableLiveStartedScript { get; set; } = false;
public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path; public string LiveStartedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string LiveStartedScriptPath { get; set; } = string.Empty; public string LiveStartedScriptPath { get; set; } = string.Empty;
public string LiveStartedScriptContent { get; set; } = string.Empty; public string LiveStartedScriptContent { get; set; } = string.Empty;
public bool EnableLiveEndedScript { get; set; } = false;
public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path; public string LiveEndedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string LiveEndedScriptPath { get; set; } = string.Empty; public string LiveEndedScriptPath { get; set; } = string.Empty;
public string LiveEndedScriptContent { get; set; } = string.Empty; public string LiveEndedScriptContent { get; set; } = string.Empty;
public bool EnableSegmentCompletedScript { get; set; } = false;
public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path; public string SegmentCompletedScriptMode { get; set; } = EventScriptSourceModes.Path;
public string SegmentCompletedScriptPath { get; set; } = string.Empty; public string SegmentCompletedScriptPath { get; set; } = string.Empty;
@@ -55,12 +55,15 @@ public sealed class SystemSettingsService : ISystemSettingsService
private const string HuyaProxyEnabledKey = "platform_proxy.huya.enabled"; private const string HuyaProxyEnabledKey = "platform_proxy.huya.enabled";
private const string HuyaProxyUrlKey = "platform_proxy.huya.url"; private const string HuyaProxyUrlKey = "platform_proxy.huya.url";
private const string EnableEventScriptsKey = "event_scripts.enabled"; private const string EnableEventScriptsKey = "event_scripts.enabled";
private const string EnableLiveStartedScriptKey = "event_scripts.live_started.enabled";
private const string LiveStartedScriptModeKey = "event_scripts.live_started.mode"; private const string LiveStartedScriptModeKey = "event_scripts.live_started.mode";
private const string LiveStartedScriptPathKey = "event_scripts.live_started.path"; private const string LiveStartedScriptPathKey = "event_scripts.live_started.path";
private const string LiveStartedScriptContentKey = "event_scripts.live_started.content"; private const string LiveStartedScriptContentKey = "event_scripts.live_started.content";
private const string EnableLiveEndedScriptKey = "event_scripts.live_ended.enabled";
private const string LiveEndedScriptModeKey = "event_scripts.live_ended.mode"; private const string LiveEndedScriptModeKey = "event_scripts.live_ended.mode";
private const string LiveEndedScriptPathKey = "event_scripts.live_ended.path"; private const string LiveEndedScriptPathKey = "event_scripts.live_ended.path";
private const string LiveEndedScriptContentKey = "event_scripts.live_ended.content"; private const string LiveEndedScriptContentKey = "event_scripts.live_ended.content";
private const string EnableSegmentCompletedScriptKey = "event_scripts.segment_completed.enabled";
private const string SegmentCompletedScriptModeKey = "event_scripts.segment_completed.mode"; private const string SegmentCompletedScriptModeKey = "event_scripts.segment_completed.mode";
private const string SegmentCompletedScriptPathKey = "event_scripts.segment_completed.path"; private const string SegmentCompletedScriptPathKey = "event_scripts.segment_completed.path";
private const string SegmentCompletedScriptContentKey = "event_scripts.segment_completed.content"; private const string SegmentCompletedScriptContentKey = "event_scripts.segment_completed.content";
@@ -179,12 +182,30 @@ public sealed class SystemSettingsService : ISystemSettingsService
ForcePathStyle = bool.TryParse(GetValue(lookup, S3ForcePathStyleKey, "false"), out var s3ForcePathStyle) && s3ForcePathStyle ForcePathStyle = bool.TryParse(GetValue(lookup, S3ForcePathStyleKey, "false"), out var s3ForcePathStyle) && s3ForcePathStyle
}, },
EnableEventScripts = bool.TryParse(GetValue(lookup, EnableEventScriptsKey, "false"), out var enableEventScripts) && enableEventScripts, EnableEventScripts = bool.TryParse(GetValue(lookup, EnableEventScriptsKey, "false"), out var enableEventScripts) && enableEventScripts,
EnableLiveStartedScript = GetEventScriptEnabled(
lookup,
EnableLiveStartedScriptKey,
enableEventScripts,
LiveStartedScriptPathKey,
LiveStartedScriptContentKey),
LiveStartedScriptMode = GetEventScriptMode(lookup, LiveStartedScriptModeKey, LiveStartedScriptPathKey, LiveStartedScriptContentKey), LiveStartedScriptMode = GetEventScriptMode(lookup, LiveStartedScriptModeKey, LiveStartedScriptPathKey, LiveStartedScriptContentKey),
LiveStartedScriptPath = GetValue(lookup, LiveStartedScriptPathKey, string.Empty), LiveStartedScriptPath = GetValue(lookup, LiveStartedScriptPathKey, string.Empty),
LiveStartedScriptContent = GetValue(lookup, LiveStartedScriptContentKey, string.Empty), LiveStartedScriptContent = GetValue(lookup, LiveStartedScriptContentKey, string.Empty),
EnableLiveEndedScript = GetEventScriptEnabled(
lookup,
EnableLiveEndedScriptKey,
enableEventScripts,
LiveEndedScriptPathKey,
LiveEndedScriptContentKey),
LiveEndedScriptMode = GetEventScriptMode(lookup, LiveEndedScriptModeKey, LiveEndedScriptPathKey, LiveEndedScriptContentKey), LiveEndedScriptMode = GetEventScriptMode(lookup, LiveEndedScriptModeKey, LiveEndedScriptPathKey, LiveEndedScriptContentKey),
LiveEndedScriptPath = GetValue(lookup, LiveEndedScriptPathKey, string.Empty), LiveEndedScriptPath = GetValue(lookup, LiveEndedScriptPathKey, string.Empty),
LiveEndedScriptContent = GetValue(lookup, LiveEndedScriptContentKey, string.Empty), LiveEndedScriptContent = GetValue(lookup, LiveEndedScriptContentKey, string.Empty),
EnableSegmentCompletedScript = GetEventScriptEnabled(
lookup,
EnableSegmentCompletedScriptKey,
enableEventScripts,
SegmentCompletedScriptPathKey,
SegmentCompletedScriptContentKey),
SegmentCompletedScriptMode = GetEventScriptMode(lookup, SegmentCompletedScriptModeKey, SegmentCompletedScriptPathKey, SegmentCompletedScriptContentKey), SegmentCompletedScriptMode = GetEventScriptMode(lookup, SegmentCompletedScriptModeKey, SegmentCompletedScriptPathKey, SegmentCompletedScriptContentKey),
SegmentCompletedScriptPath = GetValue(lookup, SegmentCompletedScriptPathKey, string.Empty), SegmentCompletedScriptPath = GetValue(lookup, SegmentCompletedScriptPathKey, string.Empty),
SegmentCompletedScriptContent = GetValue(lookup, SegmentCompletedScriptContentKey, string.Empty), SegmentCompletedScriptContent = GetValue(lookup, SegmentCompletedScriptContentKey, string.Empty),
@@ -329,12 +350,15 @@ public sealed class SystemSettingsService : ISystemSettingsService
await UpsertAsync(HuyaProxyEnabledKey, huyaProxy.Enabled.ToString(), now, cancellationToken); await UpsertAsync(HuyaProxyEnabledKey, huyaProxy.Enabled.ToString(), now, cancellationToken);
await UpsertAsync(HuyaProxyUrlKey, huyaProxy.ProxyUrl.Trim(), now, cancellationToken); await UpsertAsync(HuyaProxyUrlKey, huyaProxy.ProxyUrl.Trim(), now, cancellationToken);
await UpsertAsync(EnableEventScriptsKey, request.EnableEventScripts.ToString(), now, cancellationToken); await UpsertAsync(EnableEventScriptsKey, request.EnableEventScripts.ToString(), now, cancellationToken);
await UpsertAsync(EnableLiveStartedScriptKey, request.EnableLiveStartedScript.ToString(), now, cancellationToken);
await UpsertAsync(LiveStartedScriptModeKey, NormalizeEventScriptMode(request.LiveStartedScriptMode), now, cancellationToken); await UpsertAsync(LiveStartedScriptModeKey, NormalizeEventScriptMode(request.LiveStartedScriptMode), now, cancellationToken);
await UpsertAsync(LiveStartedScriptPathKey, request.LiveStartedScriptPath.Trim(), now, cancellationToken); await UpsertAsync(LiveStartedScriptPathKey, request.LiveStartedScriptPath.Trim(), now, cancellationToken);
await UpsertAsync(LiveStartedScriptContentKey, request.LiveStartedScriptContent, now, cancellationToken); await UpsertAsync(LiveStartedScriptContentKey, request.LiveStartedScriptContent, now, cancellationToken);
await UpsertAsync(EnableLiveEndedScriptKey, request.EnableLiveEndedScript.ToString(), now, cancellationToken);
await UpsertAsync(LiveEndedScriptModeKey, NormalizeEventScriptMode(request.LiveEndedScriptMode), now, cancellationToken); await UpsertAsync(LiveEndedScriptModeKey, NormalizeEventScriptMode(request.LiveEndedScriptMode), now, cancellationToken);
await UpsertAsync(LiveEndedScriptPathKey, request.LiveEndedScriptPath.Trim(), now, cancellationToken); await UpsertAsync(LiveEndedScriptPathKey, request.LiveEndedScriptPath.Trim(), now, cancellationToken);
await UpsertAsync(LiveEndedScriptContentKey, request.LiveEndedScriptContent, now, cancellationToken); await UpsertAsync(LiveEndedScriptContentKey, request.LiveEndedScriptContent, now, cancellationToken);
await UpsertAsync(EnableSegmentCompletedScriptKey, request.EnableSegmentCompletedScript.ToString(), now, cancellationToken);
await UpsertAsync(SegmentCompletedScriptModeKey, NormalizeEventScriptMode(request.SegmentCompletedScriptMode), now, cancellationToken); await UpsertAsync(SegmentCompletedScriptModeKey, NormalizeEventScriptMode(request.SegmentCompletedScriptMode), now, cancellationToken);
await UpsertAsync(SegmentCompletedScriptPathKey, request.SegmentCompletedScriptPath.Trim(), now, cancellationToken); await UpsertAsync(SegmentCompletedScriptPathKey, request.SegmentCompletedScriptPath.Trim(), now, cancellationToken);
await UpsertAsync(SegmentCompletedScriptContentKey, request.SegmentCompletedScriptContent, now, cancellationToken); await UpsertAsync(SegmentCompletedScriptContentKey, request.SegmentCompletedScriptContent, now, cancellationToken);
@@ -425,4 +449,28 @@ public sealed class SystemSettingsService : ISystemSettingsService
existing.Update(value, updatedAt); existing.Update(value, updatedAt);
_appSettingRepository.Update(existing); _appSettingRepository.Update(existing);
} }
private static bool GetEventScriptEnabled(
IReadOnlyDictionary<string, string> lookup,
string enabledKey,
bool legacyGlobalEnabled,
string pathKey,
string contentKey)
{
if (lookup.TryGetValue(enabledKey, out var configured) && bool.TryParse(configured, out var enabled))
{
return enabled;
}
return legacyGlobalEnabled && HasAnyConfiguredValue(lookup, pathKey, contentKey);
}
private static bool HasAnyConfiguredValue(
IReadOnlyDictionary<string, string> lookup,
string pathKey,
string contentKey)
{
return !string.IsNullOrWhiteSpace(GetValue(lookup, pathKey, string.Empty))
|| !string.IsNullOrWhiteSpace(GetValue(lookup, contentKey, string.Empty));
}
} }
@@ -35,7 +35,7 @@ public sealed class EventScriptService : IEventScriptService
var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt); var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt);
environment["LIVE_RECORDER_EVENT"] = "live_started"; environment["LIVE_RECORDER_EVENT"] = "live_started";
await RunAsync( await RunAsync(
settings.EnableEventScripts, settings.EnableEventScripts && settings.EnableLiveStartedScript,
settings.LiveStartedScriptMode, settings.LiveStartedScriptMode,
settings.LiveStartedScriptPath, settings.LiveStartedScriptPath,
settings.LiveStartedScriptContent, settings.LiveStartedScriptContent,
@@ -55,7 +55,7 @@ public sealed class EventScriptService : IEventScriptService
var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt); var environment = BuildLiveRoomEnvironment(liveRoom, occurredAt);
environment["LIVE_RECORDER_EVENT"] = "live_ended"; environment["LIVE_RECORDER_EVENT"] = "live_ended";
await RunAsync( await RunAsync(
settings.EnableEventScripts, settings.EnableEventScripts && settings.EnableLiveEndedScript,
settings.LiveEndedScriptMode, settings.LiveEndedScriptMode,
settings.LiveEndedScriptPath, settings.LiveEndedScriptPath,
settings.LiveEndedScriptContent, settings.LiveEndedScriptContent,
@@ -92,7 +92,7 @@ public sealed class EventScriptService : IEventScriptService
environment["LIVE_RECORDER_SESSION_STATUS"] = recordSession.Status.ToString(); environment["LIVE_RECORDER_SESSION_STATUS"] = recordSession.Status.ToString();
await RunAsync( await RunAsync(
settings.EnableEventScripts, settings.EnableEventScripts && settings.EnableSegmentCompletedScript,
settings.SegmentCompletedScriptMode, settings.SegmentCompletedScriptMode,
settings.SegmentCompletedScriptPath, settings.SegmentCompletedScriptPath,
settings.SegmentCompletedScriptContent, settings.SegmentCompletedScriptContent,
+1 -1
View File
@@ -19,7 +19,7 @@ FROM ${DOTNET_RUNTIME_IMAGE} AS runtime
RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources RUN sed -i 's/deb.debian.org/mirrors.tuna.tsinghua.edu.cn/g' /etc/apt/sources.list.d/debian.sources
RUN apt-get update -o Acquire::ForceIPv4=true \ RUN apt-get update -o Acquire::ForceIPv4=true \
&& apt-get install -o Acquire::ForceIPv4=true -y --no-install-recommends ffmpeg nodejs ca-certificates \ && apt-get install -o Acquire::ForceIPv4=true -y --no-install-recommends ffmpeg nodejs ca-certificates curl jq \
&& rm -rf /var/lib/apt/lists/* && rm -rf /var/lib/apt/lists/*
WORKDIR /app WORKDIR /app