Files
YMhut-box-C-/server/unified-management/internal/db/database_sync_jobs.go
T
QWQLwToo 7745e7a2d4
build-winui / winui (push) Has been cancelled
服务端媒体源导入/保存/客户端输出链路修复:支持 snake/camel、subcategories/sources,默认客户端可见,保存后发布兼容 media-types.json。
新增数据库同步 Job API、持久化状态、实时输出、最新任务恢复,以及系统日志聚合接口。
管理端优化:日志中心、运维实时状态框、同步输出自动滚动、仪表盘“输出”列、真实延迟空态、本地 favicon/avatar。
新增 server/unified-management/assets/favicon.ico 和 developer-avatar.png,并接好 /favicon.ico、/admin/favicon.ico、/setup/favicon.ico、/assets/*。
WinUI 随机放映室卡片优先显示子接口原始 Description。
Inno 安装器输出框改为选区末尾 + SendMessage 滚动到底部。
2026-06-29 22:28:58 +08:00

142 lines
4.3 KiB
Go

package db
import (
"database/sql"
"encoding/json"
"errors"
"strings"
)
type syncJobMessage struct {
Output []string `json:"output"`
Warnings []string `json:"warnings"`
Errors []string `json:"errors"`
}
type syncJobScanner interface {
Scan(dest ...any) error
}
func (s *Store) insertDatabaseSyncJob(direction, status string, output, warnings, jobErrors []string, tables map[string]int, finishedAt string) (DatabaseSyncJob, error) {
startedAt := Now()
message, tablesJSON := encodeDatabaseSyncJob(output, warnings, jobErrors, tables)
id, err := s.insertID(`INSERT INTO database_sync_jobs (direction, status, message, tables_json, started_at, finished_at) VALUES (?, ?, ?, ?, ?, ?)`,
direction, status, message, tablesJSON, startedAt, finishedAt)
if err != nil {
return DatabaseSyncJob{}, err
}
return s.GetDatabaseSyncJob(id)
}
func (s *Store) updateDatabaseSyncJob(id int64, status string, output, warnings, jobErrors []string, tables map[string]int, finishedAt string) error {
message, tablesJSON := encodeDatabaseSyncJob(output, warnings, jobErrors, tables)
if _, err := s.exec(`UPDATE database_sync_jobs SET status = ?, message = ?, tables_json = ?, finished_at = ? WHERE id = ?`,
status, message, tablesJSON, finishedAt, id); err != nil {
return err
}
if job, err := s.GetDatabaseSyncJob(id); err == nil {
s.mu.Lock()
if s.status.CurrentSyncJob != nil && s.status.CurrentSyncJob.ID == id {
s.status.CurrentSyncJob = &job
}
s.mu.Unlock()
}
return nil
}
func (s *Store) GetDatabaseSyncJob(id int64) (DatabaseSyncJob, error) {
job, err := scanDatabaseSyncJob(s.queryRow(`SELECT id, direction, status, message, tables_json, started_at, finished_at FROM database_sync_jobs WHERE id = ?`, id))
if errors.Is(err, sql.ErrNoRows) {
return DatabaseSyncJob{}, errors.New("database sync job not found")
}
return job, err
}
func (s *Store) LatestDatabaseSyncJob() (DatabaseSyncJob, error) {
job, err := scanDatabaseSyncJob(s.queryRow(`SELECT id, direction, status, message, tables_json, started_at, finished_at FROM database_sync_jobs ORDER BY id DESC LIMIT 1`))
if errors.Is(err, sql.ErrNoRows) {
return DatabaseSyncJob{}, errors.New("database sync job not found")
}
return job, err
}
func (s *Store) restoreDatabaseSyncStatus() {
job, err := s.LatestDatabaseSyncJob()
if err != nil {
return
}
s.mu.Lock()
defer s.mu.Unlock()
if job.Status == "running" {
s.status.CurrentSyncJob = &job
} else {
s.status.CurrentSyncJob = nil
}
if job.FinishedAt != "" {
s.status.LastSyncAt = job.FinishedAt
} else {
s.status.LastSyncAt = job.StartedAt
}
if job.Status == "failed" {
s.status.LastSyncError = strings.Join(job.Errors, "; ")
} else {
s.status.LastSyncError = ""
}
}
func scanDatabaseSyncJob(scanner syncJobScanner) (DatabaseSyncJob, error) {
var job DatabaseSyncJob
var message, tablesJSON string
if err := scanner.Scan(&job.ID, &job.Direction, &job.Status, &message, &tablesJSON, &job.StartedAt, &job.FinishedAt); err != nil {
return DatabaseSyncJob{}, err
}
decodeDatabaseSyncJob(message, tablesJSON, &job)
return job, nil
}
func encodeDatabaseSyncJob(output, warnings, jobErrors []string, tables map[string]int) (string, string) {
if output == nil {
output = []string{}
}
if warnings == nil {
warnings = []string{}
}
if jobErrors == nil {
jobErrors = []string{}
}
if tables == nil {
tables = map[string]int{}
}
message, _ := json.Marshal(syncJobMessage{Output: output, Warnings: warnings, Errors: jobErrors})
tableData, _ := json.Marshal(tables)
return string(message), string(tableData)
}
func decodeDatabaseSyncJob(message, tablesJSON string, job *DatabaseSyncJob) {
job.Output = []string{}
job.Warnings = []string{}
job.Errors = []string{}
job.Tables = map[string]int{}
var payload syncJobMessage
if strings.TrimSpace(message) != "" && json.Unmarshal([]byte(message), &payload) == nil {
job.Output = payload.Output
job.Warnings = payload.Warnings
job.Errors = payload.Errors
} else if strings.TrimSpace(message) != "" {
job.Output = []string{message}
}
_ = json.Unmarshal([]byte(tablesJSON), &job.Tables)
if job.Output == nil {
job.Output = []string{}
}
if job.Warnings == nil {
job.Warnings = []string{}
}
if job.Errors == nil {
job.Errors = []string{}
}
if job.Tables == nil {
job.Tables = map[string]int{}
}
}