// Package api 配置相关接口。 package api import ( "net/http" "strconv" "github.com/gin-gonic/gin" "kenaito-media-picker/internal/store" "kenaito-media-picker/internal/sysinfo" ) // 配置键名(存储在 AppConfig 表中) const ( ConfigKeyNsfwType = "nsfw.type" // 检测类型:"" / "human" / "anime" ConfigKeyNsfwThreshold = "nsfw.threshold" // 人类检测阈值 ConfigKeyAnimeNsfwThreshold = "anime.threshold" // 二次元检测阈值 ConfigKeyNsfwWorkers = "nsfw.workers" // 人类检测 worker 数量 ConfigKeyAnimeNsfwWorkers = "anime.workers" // 二次元检测 worker 数量 ConfigKeyOrganizeWorkers = "organize.workers" // Go 端探测/MIME/移动 worker 数量 ConfigKeyScanDir = "scan.scanDir" // 扫描目录 ConfigKeyDestDir = "scan.destDir" // 存放目录 ) // ConfigController 配置相关接口。 type ConfigController struct { store *store.Store } // NewConfigController 创建配置控制器。 func NewConfigController(st *store.Store) *ConfigController { return &ConfigController{store: st} } // NsfwConfig NSFW 配置响应。 type NsfwConfig struct { Type string `json:"type"` // 检测类型:"" / "human" / "anime" HumanThreshold float64 `json:"humanThreshold"` // 人类检测阈值 AnimeThreshold float64 `json:"animeThreshold"` // 二次元检测阈值 HumanWorkers int `json:"humanWorkers"` // 人类检测 worker 数量 AnimeWorkers int `json:"animeWorkers"` // 二次元检测 worker 数量 GoWorkers int `json:"goWorkers"` // Go 端 worker 数量 } // GetNsfwConfig 处理 GET /api/config/nsfw:获取 NSFW 配置。 func (c *ConfigController) GetNsfwConfig(ctx *gin.Context) { nsfwType := c.store.GetConfig(ConfigKeyNsfwType) humanThreshold := 0.5 if v := c.store.GetConfig(ConfigKeyNsfwThreshold); v != "" { if t, err := strconv.ParseFloat(v, 64); err == nil && t > 0 && t < 1 { humanThreshold = t } } animeThreshold := 0.5 if v := c.store.GetConfig(ConfigKeyAnimeNsfwThreshold); v != "" { if t, err := strconv.ParseFloat(v, 64); err == nil && t > 0 && t < 1 { animeThreshold = t } } humanWorkers := 4 if v := c.store.GetConfig(ConfigKeyNsfwWorkers); v != "" { if w, err := strconv.Atoi(v); err == nil && w > 0 { humanWorkers = w } } animeWorkers := 2 if v := c.store.GetConfig(ConfigKeyAnimeNsfwWorkers); v != "" { if w, err := strconv.Atoi(v); err == nil && w > 0 { animeWorkers = w } } goWorkers := 0 // 0 表示使用文件配置或自动计算 if v := c.store.GetConfig(ConfigKeyOrganizeWorkers); v != "" { if w, err := strconv.Atoi(v); err == nil && w >= 0 { goWorkers = w } } Success(ctx, NsfwConfig{ Type: nsfwType, HumanThreshold: humanThreshold, AnimeThreshold: animeThreshold, HumanWorkers: humanWorkers, AnimeWorkers: animeWorkers, GoWorkers: goWorkers, }) } // UpdateNsfwConfigReq 更新 NSFW 配置请求。 type UpdateNsfwConfigReq struct { Type string `json:"type"` // 检测类型:"" / "human" / "anime" HumanThreshold float64 `json:"humanThreshold"` // 人类检测阈值 AnimeThreshold float64 `json:"animeThreshold"` // 二次元检测阈值 HumanWorkers int `json:"humanWorkers"` // 人类检测 worker 数量 AnimeWorkers int `json:"animeWorkers"` // 二次元检测 worker 数量 GoWorkers int `json:"goWorkers"` // Go 端 worker 数量(0 表示使用文件配置) } // UpdateNsfwConfig 处理 PUT /api/config/nsfw:更新 NSFW 配置。 func (c *ConfigController) UpdateNsfwConfig(ctx *gin.Context) { var req UpdateNsfwConfigReq if err := ctx.ShouldBindJSON(&req); err != nil { Error(ctx, http.StatusBadRequest, "请求参数错误:"+err.Error()) return } // 校验检测类型 if req.Type != "" && req.Type != "human" && req.Type != "anime" { Error(ctx, http.StatusBadRequest, "检测类型必须是空、human 或 anime") return } // 校验阈值范围 if req.HumanThreshold <= 0 || req.HumanThreshold >= 1 { req.HumanThreshold = 0.5 } if req.AnimeThreshold <= 0 || req.AnimeThreshold >= 1 { req.AnimeThreshold = 0.5 } // 校验 worker 数量 if req.HumanWorkers <= 0 { req.HumanWorkers = 4 } if req.AnimeWorkers <= 0 { req.AnimeWorkers = 2 } if req.GoWorkers < 0 { req.GoWorkers = 0 } // 保存配置 if err := c.store.SaveConfig(ConfigKeyNsfwType, req.Type); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } if err := c.store.SaveConfig(ConfigKeyNsfwThreshold, strconv.FormatFloat(req.HumanThreshold, 'f', 2, 64)); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } if err := c.store.SaveConfig(ConfigKeyAnimeNsfwThreshold, strconv.FormatFloat(req.AnimeThreshold, 'f', 2, 64)); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } if err := c.store.SaveConfig(ConfigKeyNsfwWorkers, strconv.Itoa(req.HumanWorkers)); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } if err := c.store.SaveConfig(ConfigKeyAnimeNsfwWorkers, strconv.Itoa(req.AnimeWorkers)); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } if err := c.store.SaveConfig(ConfigKeyOrganizeWorkers, strconv.Itoa(req.GoWorkers)); err != nil { Error(ctx, http.StatusInternalServerError, "保存配置失败:"+err.Error()) return } Success(ctx, nil) } // SystemInfo 系统信息响应。 type SystemInfo struct { CPUCores int `json:"cpuCores"` // CPU 逻辑核心数 MemoryGB float64 `json:"memoryGB"` // 内存大小(GB) RecommendedGo int `json:"recommendedGo"` // 推荐的 Go 端 worker 数 RecommendedNsfw int `json:"recommendedNsfw"` // 推荐的人类检测 worker 数 RecommendedAnime int `json:"recommendedAnime"` // 推荐的二次元检测 worker 数 } // GetSystemInfo 处理 GET /api/config/system:获取系统信息和推荐配置。 func (c *ConfigController) GetSystemInfo(ctx *gin.Context) { info := sysinfo.GetSystemInfo() workers := sysinfo.CalculateWorkerConfig(info) Success(ctx, SystemInfo{ CPUCores: info.CPUCores, MemoryGB: info.MemoryGB, RecommendedGo: workers.GoWorkers, RecommendedNsfw: workers.NsfwWorkers, RecommendedAnime: workers.AnimeNsfwWorkers, }) } // UpdateScanDirReq 更新扫描目录配置请求。 type UpdateScanDirReq struct { ScanDir string `json:"scanDir"` // 扫描目录 DestDir string `json:"destDir"` // 存放目录 } // UpdateScanDir 处理 PUT /api/config/scan-dir:更新扫描目录配置。 func (c *ConfigController) UpdateScanDir(ctx *gin.Context) { var req UpdateScanDirReq if err := ctx.ShouldBindJSON(&req); err != nil { Error(ctx, http.StatusBadRequest, "请求参数错误:"+err.Error()) return } // 保存扫描目录 if err := c.store.SaveConfig(ConfigKeyScanDir, req.ScanDir); err != nil { Error(ctx, http.StatusInternalServerError, "保存扫描目录失败:"+err.Error()) return } // 保存存放目录 if err := c.store.SaveConfig(ConfigKeyDestDir, req.DestDir); err != nil { Error(ctx, http.StatusInternalServerError, "保存存放目录失败:"+err.Error()) return } Success(ctx, nil) }