多平台账号批量注册系统,支持 OpenAI / Grok / Kiro / Gemini 平台的全自动注册。 Features: - 用户名/密码注册登录(首个用户自动管理员) - JWT 认证 + bcrypt 密码加密 - 积分系统与多用户管理 - HuggingFace Space 弹性 Worker 节点管理 - Cloudflare Worker 负载均衡 - 实时日志推送(SSE/WebSocket) - 管理后台(用户管理、系统设置、代理池、卡池) Tech Stack: Go 1.25 / Gin / GORM / PostgreSQL / Vue 3 / Vite / TailwindCSS
330 lines
9.9 KiB
Go
330 lines
9.9 KiB
Go
package handler
|
||
|
||
import (
|
||
"fmt"
|
||
"net/http"
|
||
"strconv"
|
||
|
||
"github.com/gin-gonic/gin"
|
||
"github.com/xiaolajiaoyyds/regplatformm/internal/dto"
|
||
"github.com/xiaolajiaoyyds/regplatformm/internal/service"
|
||
)
|
||
|
||
// HFSpaceHandler HF Space 管理处理器
|
||
type HFSpaceHandler struct {
|
||
svc *service.HFSpaceService
|
||
}
|
||
|
||
// NewHFSpaceHandler 创建 HF Space 管理处理器
|
||
func NewHFSpaceHandler(svc *service.HFSpaceService) *HFSpaceHandler {
|
||
return &HFSpaceHandler{svc: svc}
|
||
}
|
||
|
||
// ListTokens 列出所有 Token(GET /api/admin/hf/tokens)
|
||
func (h *HFSpaceHandler) ListTokens(c *gin.Context) {
|
||
tokens, err := h.svc.ListTokens()
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, tokens)
|
||
}
|
||
|
||
// CreateToken 创建 Token(POST /api/admin/hf/tokens)
|
||
func (h *HFSpaceHandler) CreateToken(c *gin.Context) {
|
||
var req dto.CreateHFTokenReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
token, err := h.svc.CreateToken(req.Label, req.Token)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
// 脱敏返回
|
||
if len(token.Token) > 8 {
|
||
token.Token = token.Token[:4] + "****" + token.Token[len(token.Token)-4:]
|
||
}
|
||
c.JSON(http.StatusOK, token)
|
||
}
|
||
|
||
// DeleteToken 删除 Token(DELETE /api/admin/hf/tokens/:id)
|
||
func (h *HFSpaceHandler) DeleteToken(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": "无效的 Token ID"})
|
||
return
|
||
}
|
||
if err := h.svc.DeleteToken(uint(id)); err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
|
||
}
|
||
|
||
// ValidateToken 验证 Token(POST /api/admin/hf/tokens/:id/validate)
|
||
func (h *HFSpaceHandler) ValidateToken(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": "无效的 Token ID"})
|
||
return
|
||
}
|
||
token, err := h.svc.ValidateToken(uint(id))
|
||
if err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
// 脱敏返回
|
||
if len(token.Token) > 8 {
|
||
token.Token = token.Token[:4] + "****" + token.Token[len(token.Token)-4:]
|
||
}
|
||
c.JSON(http.StatusOK, token)
|
||
}
|
||
|
||
// ValidateAllTokens 批量验证所有 Token(POST /api/admin/hf/tokens/validate-all)
|
||
func (h *HFSpaceHandler) ValidateAllTokens(c *gin.Context) {
|
||
tokens, err := h.svc.ValidateAllTokens()
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
// 脱敏
|
||
for i := range tokens {
|
||
if len(tokens[i].Token) > 8 {
|
||
tokens[i].Token = tokens[i].Token[:4] + "****" + tokens[i].Token[len(tokens[i].Token)-4:]
|
||
}
|
||
}
|
||
valid := 0
|
||
for _, t := range tokens {
|
||
if t.IsValid {
|
||
valid++
|
||
}
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"tokens": tokens, "total": len(tokens), "valid": valid, "invalid": len(tokens) - valid})
|
||
}
|
||
|
||
// PurgeBannedSpaces 批量清理被封 Space(POST /api/admin/hf/spaces/purge?service=xxx)
|
||
func (h *HFSpaceHandler) PurgeBannedSpaces(c *gin.Context) {
|
||
svc := c.Query("service")
|
||
deleted, checked, err := h.svc.PurgeBannedSpaces(svc)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"message": fmt.Sprintf("检查 %d 个 Space,删除 %d 个被封/失效的", checked, deleted),
|
||
"checked": checked,
|
||
"deleted": deleted,
|
||
})
|
||
}
|
||
|
||
// ListSpaces 列出 Space(GET /api/admin/hf/spaces?service=xxx&status=xxx&page=1&page_size=20)
|
||
func (h *HFSpaceHandler) ListSpaces(c *gin.Context) {
|
||
svc := c.Query("service")
|
||
status := c.Query("status")
|
||
page, _ := strconv.Atoi(c.DefaultQuery("page", "1"))
|
||
pageSize, _ := strconv.Atoi(c.DefaultQuery("page_size", "20"))
|
||
if page < 1 {
|
||
page = 1
|
||
}
|
||
if pageSize < 1 || pageSize > 100 {
|
||
pageSize = 20
|
||
}
|
||
result, err := h.svc.ListSpaces(svc, page, pageSize, status)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, result)
|
||
}
|
||
|
||
// AddSpace 手动添加 Space(POST /api/admin/hf/spaces)
|
||
func (h *HFSpaceHandler) AddSpace(c *gin.Context) {
|
||
var req dto.AddHFSpaceManualReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
space, err := h.svc.AddSpaceManual(req.Service, req.URL, req.RepoID, req.TokenID)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, space)
|
||
}
|
||
|
||
// DeleteSpace 删除 Space(DELETE /api/admin/hf/spaces/:id)
|
||
func (h *HFSpaceHandler) DeleteSpace(c *gin.Context) {
|
||
id, err := strconv.ParseUint(c.Param("id"), 10, 32)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": "无效的 Space ID"})
|
||
return
|
||
}
|
||
if err := h.svc.DeleteSpace(uint(id)); err != nil {
|
||
c.JSON(http.StatusNotFound, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"message": "已删除"})
|
||
}
|
||
|
||
// CheckHealth 触发健康检查(POST /api/admin/hf/spaces/health?service=xxx)
|
||
func (h *HFSpaceHandler) CheckHealth(c *gin.Context) {
|
||
svc := c.Query("service")
|
||
results, err := h.svc.CheckHealth(svc)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{"results": results, "total": len(results)})
|
||
}
|
||
|
||
// DeploySpaces 批量部署新 Space(POST /api/admin/hf/spaces/deploy)
|
||
func (h *HFSpaceHandler) DeploySpaces(c *gin.Context) {
|
||
var req dto.DeployHFSpaceReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
deployed, errors, err := h.svc.DeploySpaces(req.Service, req.Count, req.ReleaseURL, req.TokenID, req.Secrets)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, gin.H{
|
||
"deployed": deployed,
|
||
"errors": errors,
|
||
"success": len(deployed),
|
||
"failed": len(errors),
|
||
})
|
||
}
|
||
|
||
// Autoscale 触发弹性管理(POST /api/admin/hf/autoscale)
|
||
// service=all 时遍历所有服务,聚合结果为单个对象返回
|
||
func (h *HFSpaceHandler) Autoscale(c *gin.Context) {
|
||
var req dto.HFAutoscaleReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
|
||
services := []string{req.Service}
|
||
if req.Service == "all" {
|
||
services = []string{"openai", "grok", "kiro", "gemini", "ts"}
|
||
}
|
||
|
||
// 聚合结果
|
||
merged := &service.AutoscaleResult{
|
||
Service: req.Service,
|
||
DryRun: req.DryRun,
|
||
Logs: make([]service.AutoscaleLog, 0),
|
||
}
|
||
|
||
for _, svc := range services {
|
||
result, err := h.svc.Autoscale(svc, req.Target, req.DryRun)
|
||
if err != nil {
|
||
merged.Logs = append(merged.Logs, service.AutoscaleLog{
|
||
Step: svc, Message: "失败: " + err.Error(),
|
||
})
|
||
continue
|
||
}
|
||
merged.Before += result.Before
|
||
merged.After += result.After
|
||
merged.Created += result.Created
|
||
merged.Deleted += result.Deleted
|
||
merged.HealthyNow += result.HealthyNow
|
||
// 各服务日志加前缀
|
||
for _, l := range result.Logs {
|
||
if len(services) > 1 {
|
||
l.Step = svc + "/" + l.Step
|
||
}
|
||
merged.Logs = append(merged.Logs, l)
|
||
}
|
||
}
|
||
|
||
c.JSON(http.StatusOK, merged)
|
||
}
|
||
|
||
// SyncCF 手动同步 CF Worker(POST /api/admin/hf/sync-cf?service=xxx)
|
||
func (h *HFSpaceHandler) SyncCF(c *gin.Context) {
|
||
svc := c.Query("service")
|
||
if svc == "" {
|
||
// 同步所有服务
|
||
errors := make([]string, 0)
|
||
results := make([]service.SyncCFResult, 0)
|
||
for _, s := range []string{"openai", "grok", "kiro", "gemini", "ts"} {
|
||
r, err := h.svc.SyncCFWorker(s)
|
||
if err != nil {
|
||
errors = append(errors, s+": "+err.Error())
|
||
} else if r != nil {
|
||
results = append(results, *r)
|
||
}
|
||
}
|
||
if len(errors) > 0 {
|
||
c.JSON(http.StatusOK, gin.H{"message": "部分同步失败", "errors": errors, "results": results})
|
||
return
|
||
}
|
||
// CF 同步完成后自动调优调度参数
|
||
h.svc.SyncPoolSize()
|
||
c.JSON(http.StatusOK, gin.H{"message": "所有服务 CF 环境变量已同步,调度参数已自动调优", "results": results})
|
||
return
|
||
}
|
||
|
||
r, err := h.svc.SyncCFWorker(svc)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
// CF 同步完成后自动调优调度参数
|
||
h.svc.SyncPoolSize()
|
||
c.JSON(http.StatusOK, gin.H{"message": svc + " CF 环境变量已同步,调度参数已自动调优", "results": []service.SyncCFResult{*r}})
|
||
}
|
||
|
||
// Overview 各服务汇总统计(GET /api/admin/hf/overview)
|
||
func (h *HFSpaceHandler) Overview(c *gin.Context) {
|
||
items, err := h.svc.Overview()
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, items)
|
||
}
|
||
|
||
// Discover 自动发现 Space(POST /api/admin/hf/discover?default_service=openai)
|
||
// 遍历所有有效 Token,调 HF API 拉取账号下的 Space,自动入库
|
||
func (h *HFSpaceHandler) Discover(c *gin.Context) {
|
||
defaultService := c.DefaultQuery("default_service", "")
|
||
result, err := h.svc.DiscoverSpaces(defaultService)
|
||
if err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, result)
|
||
}
|
||
|
||
// UpdateSpaces 批量更新已有 Space(POST /api/admin/hf/spaces/update)
|
||
// 对所有 Space 推送最新模板文件,触发 HF 重建
|
||
func (h *HFSpaceHandler) UpdateSpaces(c *gin.Context) {
|
||
var req dto.UpdateHFSpacesReq
|
||
if err := c.ShouldBindJSON(&req); err != nil {
|
||
c.JSON(http.StatusBadRequest, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
result, err := h.svc.UpdateSpaces(req.Service)
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, result)
|
||
}
|
||
|
||
// Redetect 对 unknown 的 Space 重新识别服务类型(POST /api/admin/hf/redetect)
|
||
func (h *HFSpaceHandler) Redetect(c *gin.Context) {
|
||
result, err := h.svc.RedetectService()
|
||
if err != nil {
|
||
c.JSON(http.StatusInternalServerError, gin.H{"detail": err.Error()})
|
||
return
|
||
}
|
||
c.JSON(http.StatusOK, result)
|
||
}
|