feat: Implement core backend services for team owner management, SQLite persistence, and logging, alongside frontend monitoring and record views.
This commit is contained in:
@@ -67,6 +67,22 @@ func (d *DB) createTables() error {
|
||||
value TEXT NOT NULL,
|
||||
updated_at DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
);
|
||||
|
||||
-- 批次运行记录表
|
||||
CREATE TABLE IF NOT EXISTS batch_runs (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
started_at DATETIME NOT NULL,
|
||||
finished_at DATETIME,
|
||||
total_owners INTEGER DEFAULT 0,
|
||||
total_registered INTEGER DEFAULT 0,
|
||||
total_added_to_s2a INTEGER DEFAULT 0,
|
||||
success_rate REAL DEFAULT 0,
|
||||
duration_seconds INTEGER DEFAULT 0,
|
||||
status TEXT DEFAULT 'running',
|
||||
errors TEXT
|
||||
);
|
||||
|
||||
CREATE INDEX IF NOT EXISTS idx_batch_runs_started_at ON batch_runs(started_at);
|
||||
`)
|
||||
return err
|
||||
}
|
||||
@@ -195,7 +211,7 @@ func (d *DB) GetTeamOwners(status string, limit, offset int) ([]TeamOwner, int,
|
||||
return owners, total, nil
|
||||
}
|
||||
|
||||
// GetPendingOwners 获取待处理
|
||||
// GetPendingOwners 获取待处理(排除已使用和处理中的)
|
||||
func (d *DB) GetPendingOwners() ([]TeamOwner, error) {
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, email, password, token, account_id, status, created_at
|
||||
@@ -219,6 +235,24 @@ func (d *DB) GetPendingOwners() ([]TeamOwner, error) {
|
||||
return owners, nil
|
||||
}
|
||||
|
||||
// MarkOwnerAsUsed 标记 Owner 为已使用
|
||||
func (d *DB) MarkOwnerAsUsed(email string) error {
|
||||
_, err := d.db.Exec("UPDATE team_owners SET status = 'used' WHERE email = ?", email)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarkOwnerAsProcessing 标记 Owner 为处理中
|
||||
func (d *DB) MarkOwnerAsProcessing(email string) error {
|
||||
_, err := d.db.Exec("UPDATE team_owners SET status = 'processing' WHERE email = ?", email)
|
||||
return err
|
||||
}
|
||||
|
||||
// MarkOwnerAsFailed 标记 Owner 为失败(可重试)
|
||||
func (d *DB) MarkOwnerAsFailed(email string) error {
|
||||
_, err := d.db.Exec("UPDATE team_owners SET status = 'valid' WHERE email = ?", email)
|
||||
return err
|
||||
}
|
||||
|
||||
// UpdateOwnerStatus 更新状态
|
||||
func (d *DB) UpdateOwnerStatus(id int64, status string) error {
|
||||
_, err := d.db.Exec("UPDATE team_owners SET status = ? WHERE id = ?", status, id)
|
||||
@@ -293,6 +327,135 @@ func (d *DB) GetOwnerStats() map[string]int {
|
||||
return stats
|
||||
}
|
||||
|
||||
// BatchRun 批次运行记录
|
||||
type BatchRun struct {
|
||||
ID int64 `json:"id"`
|
||||
StartedAt time.Time `json:"started_at"`
|
||||
FinishedAt time.Time `json:"finished_at,omitempty"`
|
||||
TotalOwners int `json:"total_owners"`
|
||||
TotalRegistered int `json:"total_registered"`
|
||||
TotalAddedToS2A int `json:"total_added_to_s2a"`
|
||||
SuccessRate float64 `json:"success_rate"`
|
||||
DurationSeconds int `json:"duration_seconds"`
|
||||
Status string `json:"status"`
|
||||
Errors string `json:"errors,omitempty"`
|
||||
}
|
||||
|
||||
// CreateBatchRun 创建批次记录
|
||||
func (d *DB) CreateBatchRun(totalOwners int) (int64, error) {
|
||||
result, err := d.db.Exec(
|
||||
`INSERT INTO batch_runs (started_at, total_owners, status) VALUES (?, ?, 'running')`,
|
||||
time.Now(), totalOwners,
|
||||
)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.LastInsertId()
|
||||
}
|
||||
|
||||
// UpdateBatchRun 更新批次记录
|
||||
func (d *DB) UpdateBatchRun(id int64, registered, addedToS2A int, errors string) error {
|
||||
finishedAt := time.Now()
|
||||
|
||||
// 获取开始时间计算耗时
|
||||
var startedAt time.Time
|
||||
var totalOwners int
|
||||
d.db.QueryRow("SELECT started_at, total_owners FROM batch_runs WHERE id = ?", id).Scan(&startedAt, &totalOwners)
|
||||
|
||||
duration := int(finishedAt.Sub(startedAt).Seconds())
|
||||
|
||||
// 计算成功率(以入库数为准)
|
||||
var successRate float64
|
||||
expectedTotal := totalOwners * 4 // 每个 owner 应产生 4 个成员
|
||||
if expectedTotal > 0 {
|
||||
successRate = float64(addedToS2A) / float64(expectedTotal) * 100
|
||||
}
|
||||
|
||||
_, err := d.db.Exec(
|
||||
`UPDATE batch_runs SET
|
||||
finished_at = ?,
|
||||
total_registered = ?,
|
||||
total_added_to_s2a = ?,
|
||||
success_rate = ?,
|
||||
duration_seconds = ?,
|
||||
status = 'completed',
|
||||
errors = ?
|
||||
WHERE id = ?`,
|
||||
finishedAt, registered, addedToS2A, successRate, duration, errors, id,
|
||||
)
|
||||
return err
|
||||
}
|
||||
|
||||
// GetBatchRuns 获取批次记录列表
|
||||
func (d *DB) GetBatchRuns(limit int) ([]BatchRun, error) {
|
||||
if limit <= 0 {
|
||||
limit = 50
|
||||
}
|
||||
|
||||
rows, err := d.db.Query(`
|
||||
SELECT id, started_at, finished_at, total_owners, total_registered, total_added_to_s2a,
|
||||
success_rate, duration_seconds, status, COALESCE(errors, '')
|
||||
FROM batch_runs
|
||||
ORDER BY started_at DESC
|
||||
LIMIT ?
|
||||
`, limit)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var runs []BatchRun
|
||||
for rows.Next() {
|
||||
var run BatchRun
|
||||
var finishedAt sql.NullTime
|
||||
err := rows.Scan(
|
||||
&run.ID, &run.StartedAt, &finishedAt, &run.TotalOwners, &run.TotalRegistered,
|
||||
&run.TotalAddedToS2A, &run.SuccessRate, &run.DurationSeconds, &run.Status, &run.Errors,
|
||||
)
|
||||
if err != nil {
|
||||
continue
|
||||
}
|
||||
if finishedAt.Valid {
|
||||
run.FinishedAt = finishedAt.Time
|
||||
}
|
||||
runs = append(runs, run)
|
||||
}
|
||||
return runs, nil
|
||||
}
|
||||
|
||||
// GetBatchRunStats 获取批次统计
|
||||
func (d *DB) GetBatchRunStats() map[string]interface{} {
|
||||
stats := make(map[string]interface{})
|
||||
|
||||
var totalAdded, todayAdded int
|
||||
var avgRate float64
|
||||
|
||||
// 总入库数
|
||||
d.db.QueryRow("SELECT COALESCE(SUM(total_added_to_s2a), 0) FROM batch_runs").Scan(&totalAdded)
|
||||
stats["total_added"] = totalAdded
|
||||
|
||||
// 今日入库数
|
||||
d.db.QueryRow(`
|
||||
SELECT COALESCE(SUM(total_added_to_s2a), 0) FROM batch_runs
|
||||
WHERE DATE(started_at) = DATE('now', 'localtime')
|
||||
`).Scan(&todayAdded)
|
||||
stats["today_added"] = todayAdded
|
||||
|
||||
// 平均成功率
|
||||
d.db.QueryRow("SELECT COALESCE(AVG(success_rate), 0) FROM batch_runs WHERE status = 'completed'").Scan(&avgRate)
|
||||
stats["avg_success_rate"] = avgRate
|
||||
|
||||
// 本周入库数
|
||||
var weekAdded int
|
||||
d.db.QueryRow(`
|
||||
SELECT COALESCE(SUM(total_added_to_s2a), 0) FROM batch_runs
|
||||
WHERE started_at >= datetime('now', '-7 days')
|
||||
`).Scan(&weekAdded)
|
||||
stats["week_added"] = weekAdded
|
||||
|
||||
return stats
|
||||
}
|
||||
|
||||
// Close 关闭数据库
|
||||
func (d *DB) Close() error {
|
||||
if d.db != nil {
|
||||
|
||||
Reference in New Issue
Block a user