feat: implement core backend for team owner management with SQLite, auto-add APIs, and a frontend owner list component.
This commit is contained in:
@@ -206,12 +206,21 @@ func getS2AAccountCount() (int, error) {
|
||||
}
|
||||
|
||||
var result struct {
|
||||
NormalAccounts int `json:"normal_accounts"`
|
||||
Code int `json:"code"`
|
||||
Message string `json:"message"`
|
||||
Data struct {
|
||||
NormalAccounts int `json:"normal_accounts"`
|
||||
} `json:"data"`
|
||||
}
|
||||
|
||||
if err := decodeJSON(resp.Body, &result); err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return result.NormalAccounts, nil
|
||||
// S2A 返回 code=0 表示成功
|
||||
if result.Code != 0 {
|
||||
return 0, fmt.Errorf("S2A 返回错误: %s", result.Message)
|
||||
}
|
||||
|
||||
return result.Data.NormalAccounts, nil
|
||||
}
|
||||
|
||||
@@ -292,7 +292,8 @@ func processSingleTeam(idx int, req TeamProcessRequest) TeamProcessResult {
|
||||
Errors: make([]string, 0),
|
||||
}
|
||||
|
||||
logPrefix := fmt.Sprintf("[Team %d]", idx+1)
|
||||
// 固定宽度的 Team 编号 (支持到 Team 99)
|
||||
logPrefix := fmt.Sprintf("[Team %2d]", idx+1)
|
||||
logger.Info(fmt.Sprintf("%s 开始处理 | 母号: %s", logPrefix, owner.Email), owner.Email, "team")
|
||||
|
||||
// 标记 owner 为处理中
|
||||
@@ -385,12 +386,23 @@ func processSingleTeam(idx int, req TeamProcessRequest) TeamProcessResult {
|
||||
// 重试时使用新邮箱
|
||||
currentEmail = mail.GenerateEmail()
|
||||
currentPassword = register.GeneratePassword()
|
||||
logger.Warning(fmt.Sprintf("%s [成员 %d] 重试,新邮箱: %s", logPrefix, memberIdx+1, currentEmail), currentEmail, "team")
|
||||
logger.Warning(fmt.Sprintf("%s [成员 %d] 重试, 新邮箱: %s", logPrefix, memberIdx+1, currentEmail), currentEmail, "team")
|
||||
}
|
||||
|
||||
// 发送邀请
|
||||
if err := inviter.SendInvites([]string{currentEmail}); err != nil {
|
||||
errStr := err.Error()
|
||||
logger.Error(fmt.Sprintf("%s [成员 %d] 邀请失败: %v", logPrefix, memberIdx+1, err), currentEmail, "team")
|
||||
|
||||
// 检测 Team 已达邀请上限
|
||||
if strings.Contains(errStr, "maximum number of seats") {
|
||||
logger.Warning(fmt.Sprintf("%s Team 邀请已满,标记母号为已使用", logPrefix), owner.Email, "team")
|
||||
if database.Instance != nil {
|
||||
database.Instance.MarkOwnerAsUsed(owner.Email)
|
||||
}
|
||||
// 跳出重试,该成员不再处理
|
||||
return false
|
||||
}
|
||||
continue
|
||||
}
|
||||
|
||||
@@ -538,7 +550,7 @@ func processSingleTeam(idx int, req TeamProcessRequest) TeamProcessResult {
|
||||
result.Errors = append(result.Errors, fmt.Sprintf("Owner S2A: %v", err))
|
||||
} else {
|
||||
result.AddedToS2A++
|
||||
logger.Success(fmt.Sprintf("%s [母号] ✓ 入库成功", logPrefix), owner.Email, "team")
|
||||
logger.Success(fmt.Sprintf("%s [母号 ] ✓ 入库成功", logPrefix), owner.Email, "team")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +189,8 @@ func (d *DB) GetTeamOwners(status string, limit, offset int) ([]TeamOwner, int,
|
||||
return nil, 0, err
|
||||
}
|
||||
|
||||
query += " ORDER BY created_at DESC LIMIT ? OFFSET ?"
|
||||
// 排序:已使用(used, pooled)排前面,其次按创建时间降序
|
||||
query += " ORDER BY CASE WHEN status IN ('used', 'pooled') THEN 0 ELSE 1 END, created_at DESC LIMIT ? OFFSET ?"
|
||||
args = append(args, limit, offset)
|
||||
|
||||
rows, err := d.db.Query(query, args...)
|
||||
@@ -277,6 +278,15 @@ func (d *DB) ClearTeamOwners() error {
|
||||
return err
|
||||
}
|
||||
|
||||
// ClearUsedOwners 清理已使用的母号(status = 'used' 或 'pooled')
|
||||
func (d *DB) ClearUsedOwners() (int64, error) {
|
||||
result, err := d.db.Exec("DELETE FROM team_owners WHERE status IN ('used', 'pooled')")
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return result.RowsAffected()
|
||||
}
|
||||
|
||||
// GetOwnersWithoutAccountID 获取缺少 account_id 的 owners
|
||||
func (d *DB) GetOwnersWithoutAccountID() ([]TeamOwner, error) {
|
||||
rows, err := d.db.Query(`
|
||||
|
||||
@@ -119,19 +119,27 @@ func log(level, message, email, module string) {
|
||||
color := ""
|
||||
switch level {
|
||||
case "info":
|
||||
prefix = "INFO"
|
||||
prefix = "INFO "
|
||||
color = colorCyan
|
||||
case "success":
|
||||
prefix = "SUCCESS"
|
||||
color = colorGreen
|
||||
case "error":
|
||||
prefix = "ERROR"
|
||||
prefix = "ERROR "
|
||||
color = colorRed
|
||||
case "warning":
|
||||
prefix = "WARN"
|
||||
prefix = "WARN "
|
||||
color = colorYellow
|
||||
}
|
||||
|
||||
// 模块名固定宽度(8字符)
|
||||
moduleStr := module
|
||||
if len(moduleStr) < 8 {
|
||||
moduleStr = moduleStr + strings.Repeat(" ", 8-len(moduleStr))
|
||||
} else if len(moduleStr) > 8 {
|
||||
moduleStr = moduleStr[:8]
|
||||
}
|
||||
|
||||
// 如果是 Team 相关日志,消息使用 Team 颜色
|
||||
msgColor := colorReset
|
||||
if teamColor != "" {
|
||||
@@ -139,15 +147,20 @@ func log(level, message, email, module string) {
|
||||
}
|
||||
|
||||
if email != "" {
|
||||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s - %s%s%s\n",
|
||||
// 截断长邮箱,保持对齐
|
||||
emailDisplay := email
|
||||
if len(emailDisplay) > 35 {
|
||||
emailDisplay = emailDisplay[:32] + "..."
|
||||
}
|
||||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s%s%s\n",
|
||||
colorGray, timestamp, colorReset,
|
||||
color, prefix, colorReset,
|
||||
module, email, msgColor, message, colorReset)
|
||||
moduleStr, msgColor, message, colorReset)
|
||||
} else {
|
||||
fmt.Printf("%s%s%s %s[%s]%s [%s] %s%s%s\n",
|
||||
colorGray, timestamp, colorReset,
|
||||
color, prefix, colorReset,
|
||||
module, msgColor, message, colorReset)
|
||||
moduleStr, msgColor, message, colorReset)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user