Files
Scoreboard/server/internal/realtime/changes.go
T
AskaEth bc8b1ee6c0 feat: M3-M7 全模块落地 + 集成接线
- M3 认证/权限/审计/限流 + 全部 REST 接口
- M4 管理面板(MIUIx + 成绩录入/积分榜下钻/大屏控制)
- M5a 大屏页面(六页 + 明细覆盖层 + 缩放适配)
- M6 WebSocket 实时同步中心
- M7 xlsx 导出 + 赛事包 zip + 备份
- 规则包 YAML 导入导出(差异预览)
- 字体 vendor 子集化(4 套 x2 字重)
- 集成:realtime/export/rules 接入 main+httpapi;backup 子命令
- 修复:中间件包装 ResponseWriter 丢失 Hijacker,导致 WS 握手 501
2026-09-09 19:02:24 +08:00

108 lines
3.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package realtime
import (
"context"
"database/sql"
"encoding/json"
"fmt"
)
// currentVersion 返回服务端版本号,即 change_log 的最大 id;空表为 0。
func (h *Hub) currentVersion(ctx context.Context) (int64, error) {
var v sql.NullInt64
if err := h.db.QueryRowContext(ctx, `SELECT MAX(id) FROM change_log`).Scan(&v); err != nil {
return 0, fmt.Errorf("读取 change_log 版本号: %w", err)
}
if !v.Valid {
return 0, nil
}
return v.Int64, nil
}
// currentVersionCached 在数据库不可用时退回内存里的版本,保证心跳与广播不会因一次查询失败而中断。
func (h *Hub) currentVersionCached(ctx context.Context) int64 {
if h.db == nil {
return h.Version()
}
v, err := h.currentVersion(ctx)
if err != nil {
h.log.Warn("读取版本号失败,沿用内存版本", "err", err, "version", h.Version())
return h.Version()
}
h.setVersion(v)
return v
}
func (h *Hub) countChangesSince(ctx context.Context, from int64) (int, error) {
var n int
err := h.db.QueryRowContext(ctx, `SELECT COUNT(*) FROM change_log WHERE id > ?`, from).Scan(&n)
if err != nil {
return 0, fmt.Errorf("统计增量变更: %w", err)
}
return n, nil
}
// changesSince 按版本升序取增量;limit <= 0 表示不限量。
func (h *Hub) changesSince(ctx context.Context, from int64, limit int) ([]Change, error) {
q := `SELECT id, entity_type, entity_id, action, payload_json
FROM change_log WHERE id > ? ORDER BY id`
args := []any{from}
if limit > 0 {
q += ` LIMIT ?`
args = append(args, limit)
}
rows, err := h.db.QueryContext(ctx, q, args...)
if err != nil {
return nil, fmt.Errorf("读取增量变更: %w", err)
}
defer func() { _ = rows.Close() }()
out := make([]Change, 0, 16)
for rows.Next() {
var (
ch Change
entity string
entityID sql.NullInt64
action string
payload string
)
if err := rows.Scan(&ch.Version, &entity, &entityID, &action, &payload); err != nil {
return nil, fmt.Errorf("解析变更行: %w", err)
}
ch.Entity = entity
ch.ID = entityID.Int64
ch.Action = action
ch.Data = payloadJSON(payload)
out = append(out, ch)
}
if err := rows.Err(); err != nil {
return nil, fmt.Errorf("遍历变更行: %w", err)
}
return out, nil
}
// LoadChanges 供写操作完成后读取「自 sinceVersion 起的变更」,再交给 Broadcast 推送。
func (h *Hub) LoadChanges(ctx context.Context, sinceVersion int64, limit int) ([]Change, error) {
if h.db == nil {
return nil, nil
}
return h.changesSince(ctx, sinceVersion, limit)
}
// payloadJSON 保证 data 永远是合法 JSON:change_log 允许写入任意文本,
// 空串或坏 JSON 一律降级成空对象,避免把坏数据传播给客户端。
func payloadJSON(payload string) json.RawMessage {
if payload == "" {
return json.RawMessage(`{}`)
}
if !json.Valid([]byte(payload)) {
quoted, err := json.Marshal(payload)
if err != nil {
return json.RawMessage(`{}`)
}
return quoted
}
return json.RawMessage(payload)
}