feat: Implement core backend infrastructure including configuration management, SQLite database with team owners and app settings, and initial owner-related APIs and frontend components.

This commit is contained in:
2026-01-31 03:16:24 +08:00
parent 634b493524
commit f590fe0c7a
6 changed files with 810 additions and 20 deletions

View File

@@ -10,13 +10,14 @@ import (
// TeamOwner 账号结构
type TeamOwner struct {
ID int64 `json:"id"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
AccountID string `json:"account_id"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
ID int64 `json:"id"`
Email string `json:"email"`
Password string `json:"password,omitempty"`
Token string `json:"token,omitempty"`
AccountID string `json:"account_id"`
Status string `json:"status"`
CreatedAt time.Time `json:"created_at"`
LastCheckedAt *time.Time `json:"last_checked_at,omitempty"`
}
// DB 数据库管理器
@@ -40,10 +41,31 @@ func Init(dbPath string) error {
return fmt.Errorf("创建表失败: %w", err)
}
// 执行数据库迁移
if err := Instance.migrate(); err != nil {
fmt.Printf("[数据库] 迁移警告: %v\n", err)
}
fmt.Printf("[数据库] SQLite 已连接: %s\n", dbPath)
return nil
}
// migrate 数据库迁移
func (d *DB) migrate() error {
// 添加 last_checked_at 列(如果不存在)
_, err := d.db.Exec(`ALTER TABLE team_owners ADD COLUMN last_checked_at DATETIME`)
if err != nil && !isColumnExistsError(err) {
return err
}
return nil
}
// isColumnExistsError 判断是否是列已存在的错误
func isColumnExistsError(err error) bool {
return err != nil && (err.Error() == "duplicate column name: last_checked_at" ||
err.Error() == "table team_owners already has a column named last_checked_at")
}
// createTables 创建表
func (d *DB) createTables() error {
_, err := d.db.Exec(`
@@ -54,9 +76,10 @@ func (d *DB) createTables() error {
token TEXT,
account_id TEXT NOT NULL,
status TEXT DEFAULT 'valid',
created_at DATETIME DEFAULT CURRENT_TIMESTAMP
created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
last_checked_at DATETIME
);
CREATE INDEX IF NOT EXISTS idx_team_owners_email ON team_owners(email);
CREATE INDEX IF NOT EXISTS idx_team_owners_status ON team_owners(status);
CREATE INDEX IF NOT EXISTS idx_team_owners_account_id ON team_owners(account_id);
@@ -173,7 +196,7 @@ func (d *DB) AddTeamOwners(owners []TeamOwner) (int, error) {
// GetTeamOwners 获取列表
func (d *DB) GetTeamOwners(status string, limit, offset int) ([]TeamOwner, int, error) {
query := "SELECT id, email, password, token, account_id, status, created_at FROM team_owners WHERE 1=1"
query := "SELECT id, email, password, token, account_id, status, created_at, last_checked_at FROM team_owners WHERE 1=1"
countQuery := "SELECT COUNT(*) FROM team_owners WHERE 1=1"
args := []interface{}{}
@@ -202,10 +225,14 @@ func (d *DB) GetTeamOwners(status string, limit, offset int) ([]TeamOwner, int,
var owners []TeamOwner
for rows.Next() {
var owner TeamOwner
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt)
var lastCheckedAt sql.NullTime
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt, &lastCheckedAt)
if err != nil {
continue
}
if lastCheckedAt.Valid {
owner.LastCheckedAt = &lastCheckedAt.Time
}
owners = append(owners, owner)
}
@@ -215,7 +242,7 @@ func (d *DB) GetTeamOwners(status string, limit, offset int) ([]TeamOwner, int,
// GetPendingOwners 获取待处理(排除已使用和处理中的)
func (d *DB) GetPendingOwners() ([]TeamOwner, error) {
rows, err := d.db.Query(`
SELECT id, email, password, token, account_id, status, created_at
SELECT id, email, password, token, account_id, status, created_at, last_checked_at
FROM team_owners WHERE status = 'valid'
ORDER BY created_at ASC
`)
@@ -227,15 +254,62 @@ func (d *DB) GetPendingOwners() ([]TeamOwner, error) {
var owners []TeamOwner
for rows.Next() {
var owner TeamOwner
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt)
var lastCheckedAt sql.NullTime
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt, &lastCheckedAt)
if err != nil {
continue
}
if lastCheckedAt.Valid {
owner.LastCheckedAt = &lastCheckedAt.Time
}
owners = append(owners, owner)
}
return owners, nil
}
// GetOwnersForBanCheck 获取需要检查封禁状态的母号
func (d *DB) GetOwnersForBanCheck(checkIntervalHours int) ([]TeamOwner, error) {
// 获取 valid 状态且 超过检查间隔 或 从未检查过 的母号
rows, err := d.db.Query(`
SELECT id, email, password, token, account_id, status, created_at, last_checked_at
FROM team_owners
WHERE status = 'valid'
AND (last_checked_at IS NULL OR last_checked_at < datetime('now', ?))
ORDER BY last_checked_at ASC NULLS FIRST, created_at ASC
`, fmt.Sprintf("-%d hours", checkIntervalHours))
if err != nil {
return nil, err
}
defer rows.Close()
var owners []TeamOwner
for rows.Next() {
var owner TeamOwner
var lastCheckedAt sql.NullTime
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt, &lastCheckedAt)
if err != nil {
continue
}
if lastCheckedAt.Valid {
owner.LastCheckedAt = &lastCheckedAt.Time
}
owners = append(owners, owner)
}
return owners, nil
}
// UpdateOwnerLastCheckedAt 更新母号的最后检查时间
func (d *DB) UpdateOwnerLastCheckedAt(id int64) error {
_, err := d.db.Exec("UPDATE team_owners SET last_checked_at = CURRENT_TIMESTAMP WHERE id = ?", id)
return err
}
// UpdateOwnerLastCheckedAtByEmail 通过邮箱更新母号的最后检查时间
func (d *DB) UpdateOwnerLastCheckedAtByEmail(email string) error {
_, err := d.db.Exec("UPDATE team_owners SET last_checked_at = CURRENT_TIMESTAMP WHERE email = ?", email)
return err
}
// MarkOwnerAsUsed 标记 Owner 为已使用
func (d *DB) MarkOwnerAsUsed(email string) error {
_, err := d.db.Exec("UPDATE team_owners SET status = 'used' WHERE email = ?", email)
@@ -290,7 +364,7 @@ func (d *DB) ClearUsedOwners() (int64, error) {
// GetOwnersWithoutAccountID 获取缺少 account_id 的 owners
func (d *DB) GetOwnersWithoutAccountID() ([]TeamOwner, error) {
rows, err := d.db.Query(`
SELECT id, email, password, token, account_id, status, created_at
SELECT id, email, password, token, account_id, status, created_at, last_checked_at
FROM team_owners WHERE account_id = '' OR account_id IS NULL
ORDER BY created_at DESC
`)
@@ -302,10 +376,14 @@ func (d *DB) GetOwnersWithoutAccountID() ([]TeamOwner, error) {
var owners []TeamOwner
for rows.Next() {
var owner TeamOwner
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt)
var lastCheckedAt sql.NullTime
err := rows.Scan(&owner.ID, &owner.Email, &owner.Password, &owner.Token, &owner.AccountID, &owner.Status, &owner.CreatedAt, &lastCheckedAt)
if err != nil {
continue
}
if lastCheckedAt.Valid {
owner.LastCheckedAt = &lastCheckedAt.Time
}
owners = append(owners, owner)
}
return owners, nil