feat: 补齐第一批已知缺口(关闭握手 / 定时备份 / Origin 白名单 / DTO 收敛)

- httpapi:新增 POST /api/shutdown(admin 会话或 X-Shutdown-Token 常量时间比对)
  与 GET /api/rules/switches(recorder 可读,只回布尔开关);settings/standings 的
  重复 DTO 收敛到 internal/dto;standings.showIndividualMedals 贯通到设置读写与前端
- export:抽出 export.Service,新增自动备份定时任务(backup.intervalMinutes/keep),
  自动、手动、发布前三类备份共用同一把互斥锁与滚动清理
- realtime:/ws 去掉 InsecureSkipVerify,改用 OriginPatterns(同源与无 Origin 放行,
  跨站需命中 server.allowedOrigins)
- console:停止服务改走 POST /api/shutdown 优雅关闭(5 秒宽限后回退强杀),
  服务管理器新增「已连接的大屏实例」卡片
- deploy:build.bat 改用 for /f 读内部署名——set /p 在 chcp 65001 下返回空,
  导致最近这次构建的 exe 里根本没注入署名
- docs:备份策略、关闭超时、Origin 配置的说明与实现对齐
This commit is contained in:
AskaEth committed 2026-09-09 21:41:08 +08:00
1 parent 4d56c73ae6
commit 2c9ff65fc4
42 files changed
+1271 -275

No files matched your search

+4
View File
@@ -32,6 +32,7 @@ import type {
RulePack,
RulePackImportPreview,
RulePackImportResult,
RuleSwitches,
SaveDraftPayload,
ScoreChannel,
ScoreChannelInput,
@@ -194,6 +195,8 @@ export interface AuditApi {
export interface RulesApi {
/** GET /api/rules/export,返回当前规则包。 */
get(): Promise<RulePack>
/** GET /api/rules/switches,recorder 可读的布尔开关(不含规则内容)。 */
switches(): Promise<RuleSwitches>
/** 先预览差异,再决定是否落地(02 第 7.3 节硬性要求)。 */
preview(pack: RulePack): Promise<RulePackImportPreview>
apply(pack: RulePack): Promise<RulePackImportResult>
@@ -295,6 +298,7 @@ function createRulesApi(http: Http): RulesApi {
}
return pack
},
switches: () => http.request<RuleSwitches>('/rules/switches'),
preview: (pack) =>
http.request<RulePackImportPreview>('/rules/import', {
method: 'POST',
+5
View File
@@ -84,6 +84,11 @@ export function normalizeSettings(raw: unknown): EventSettings {
isFlat ? source['standings.show_individual_medals'] : undefined,
nestedStandings?.showIndividualBoard,
),
showIndividualMedals: firstBool(
true,
isFlat ? source['standings.show_individual_medals'] : undefined,
nestedStandings?.showIndividualMedals,
),
},
// 扁平 map 里只有模板编码(scoring.default_profile),解析不出数据库 id,只能置空。
defaultProfileId: isFlat ? null : firstNumberOrNull(source.defaultProfileId),
+15
View File
@@ -372,6 +372,8 @@ export interface EventSettings {
/** 排名模式;文档未枚举取值,用 string 保留后端扩展空间。 */
rankingMode: string
showIndividualBoard: boolean
/** 个人榜奖牌列开关;旧服务端不下发时按 true 处理。 */
showIndividualMedals: boolean
}
defaultProfileId: number | null
requireApproval: boolean
@@ -468,6 +470,19 @@ export interface RulePackManualAdjustment {
reasonRequired: boolean
}
/**
* GET /api/rules/switches:规则包里布尔开关的只读视图。
* 录入员读不到完整规则包(export 是 admin-only),页面用这些布尔值判断功能是否启用。
*/
export interface RuleSwitches {
recordBonusEnabled: boolean
adjustmentsEnabled: boolean
adjustmentReasonRequired: boolean
medalCountsTeamEvents: boolean
showIndividualBoard: boolean
showIndividualMedals: boolean
}
/** 规则包里的通道用 profile code 引用模板,而不是数据库 id,便于跨赛事复用。 */
export interface RuleChannel {
scope: ChannelScope