package main import ( "encoding/json" "fmt" "io" "net/http" "strings" "unicode" "codex-pool/internal/api" "codex-pool/internal/database" ) type uploadValidateRequest struct { Content string `json:"content"` Accounts []accountRecord `json:"accounts"` } type accountRecord struct { Account string `json:"account"` Email string `json:"email"` Password string `json:"password"` Token string `json:"token"` AccessTok string `json:"access_token"` AccountID string `json:"account_id"` } func handleUploadValidate(w http.ResponseWriter, r *http.Request) { if r.Method != http.MethodPost { api.Error(w, http.StatusMethodNotAllowed, "仅支持 POST") return } if database.Instance == nil { api.Error(w, http.StatusInternalServerError, "数据库未初始化") return } body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 10<<20)) if err != nil { api.Error(w, http.StatusBadRequest, "读取请求失败") return } var req uploadValidateRequest if err := json.Unmarshal(body, &req); err != nil { // 如果不是 JSON,直接把 body 当作原始内容 req.Content = string(body) } var records []accountRecord switch { case len(req.Accounts) > 0: records = req.Accounts case strings.TrimSpace(req.Content) != "": parsed, parseErr := parseAccountsFlexible(req.Content) if parseErr != nil { api.Error(w, http.StatusBadRequest, parseErr.Error()) return } records = parsed default: api.Error(w, http.StatusBadRequest, "未提供账号内容") return } owners := make([]database.TeamOwner, 0, len(records)) for i, rec := range records { owner, err := normalizeOwner(rec, i+1) if err != nil { api.Error(w, http.StatusBadRequest, err.Error()) return } owners = append(owners, owner) } if len(owners) == 0 { api.Error(w, http.StatusBadRequest, "未解析到有效账号") return } inserted, err := database.Instance.AddTeamOwners(owners) if err != nil { api.Error(w, http.StatusInternalServerError, fmt.Sprintf("写入数据库失败: %v", err)) return } stats := database.Instance.GetOwnerStats() api.Success(w, map[string]interface{}{ "imported": inserted, "total": len(owners), "stats": stats, }) } func normalizeOwner(rec accountRecord, index int) (database.TeamOwner, error) { email := strings.TrimSpace(rec.Email) if email == "" { email = strings.TrimSpace(rec.Account) } if email == "" { return database.TeamOwner{}, fmt.Errorf("第 %d 条记录缺少 account/email 字段", index) } password := strings.TrimSpace(rec.Password) if password == "" { return database.TeamOwner{}, fmt.Errorf("第 %d 条记录缺少 password 字段", index) } token := strings.TrimSpace(rec.Token) if token == "" { token = strings.TrimSpace(rec.AccessTok) } if token == "" { return database.TeamOwner{}, fmt.Errorf("第 %d 条记录缺少 token 字段", index) } accountID := strings.TrimSpace(rec.AccountID) return database.TeamOwner{ Email: email, Password: password, Token: token, AccountID: accountID, }, nil } func parseAccountsFlexible(raw string) ([]accountRecord, error) { raw = strings.TrimSpace(strings.TrimPrefix(raw, "\uFEFF")) if raw == "" { return nil, fmt.Errorf("内容为空") } cleaned := stripJSONComments(raw) cleaned = removeTrailingCommas(cleaned) trimmed := strings.TrimSpace(cleaned) if trimmed == "" { return nil, fmt.Errorf("内容为空") } if strings.HasPrefix(trimmed, "[") { var arr []accountRecord if err := json.Unmarshal([]byte(trimmed), &arr); err == nil { return arr, nil } } else if strings.HasPrefix(trimmed, "{") { var single accountRecord if err := json.Unmarshal([]byte(trimmed), &single); err == nil { return []accountRecord{single}, nil } } // JSONL 回退 lines := strings.Split(raw, "\n") records := make([]accountRecord, 0, len(lines)) for i, line := range lines { line = strings.TrimSpace(stripJSONComments(line)) if line == "" { continue } if strings.HasPrefix(line, "#") { continue } line = strings.TrimSpace(removeTrailingCommas(line)) if line == "" { continue } var rec accountRecord if err := json.Unmarshal([]byte(line), &rec); err != nil { return nil, fmt.Errorf("第 %d 行解析失败: %v", i+1, err) } records = append(records, rec) } if len(records) == 0 { return nil, fmt.Errorf("未解析到有效账号") } return records, nil } func stripJSONComments(input string) string { var b strings.Builder b.Grow(len(input)) inString := false escaped := false for i := 0; i < len(input); i++ { ch := input[i] if inString { b.WriteByte(ch) if escaped { escaped = false continue } if ch == '\\' { escaped = true } else if ch == '"' { inString = false } continue } if ch == '"' { inString = true b.WriteByte(ch) continue } if ch == '/' && i+1 < len(input) && input[i+1] == '/' { for i+1 < len(input) && input[i+1] != '\n' { i++ } continue } if ch == '/' && i+1 < len(input) && input[i+1] == '*' { i += 2 for i+1 < len(input) { if input[i] == '*' && input[i+1] == '/' { i++ break } i++ } continue } b.WriteByte(ch) } return b.String() } func removeTrailingCommas(input string) string { var b strings.Builder b.Grow(len(input)) inString := false escaped := false for i := 0; i < len(input); i++ { ch := input[i] if inString { b.WriteByte(ch) if escaped { escaped = false continue } if ch == '\\' { escaped = true } else if ch == '"' { inString = false } continue } if ch == '"' { inString = true b.WriteByte(ch) continue } if ch == ',' { j := i + 1 for j < len(input) && unicode.IsSpace(rune(input[j])) { j++ } if j < len(input) && (input[j] == ']' || input[j] == '}') { continue } } b.WriteByte(ch) } return b.String() }