218 lines
7.3 KiB
Go
218 lines
7.3 KiB
Go
// 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)
|
||
}
|