// 色情识别相关的两个接口:查记录、看图片预览。 // 前端「色情识别」页面用它展示每次扫描判出的色情图片(检测分 + 路径 + 预览), // 方便用户核对识别结果准不准。只读数据库 + 读本地图片文件,不干别的活。 package api import ( "net/http" "os" "strings" "github.com/gin-gonic/gin" "kenaito-media-picker/internal/config" "kenaito-media-picker/internal/store" ) // nsfwDetectorReadyKey 记录"最近一次扫描时色情检测服务是否真的起来了"(存 AppConfig)。 // 配置开了但检测器没起来(没装 python/模型下载失败等)也能区分出来,方便前端提示。 // 由 scan 流程在服务启动成功/失败时写 1/0。 const nsfwDetectorReadyKey = "nsfw.detectorReady" // NsfwController 色情识别相关接口的"窗口"。 type NsfwController struct { store *store.Store // 数据库(只读) } // NewNsfwController 创建色情识别控制器,注入数据库。 func NewNsfwController(st *store.Store) *NsfwController { return &NsfwController{store: st} } // nsfwView 列表里展示的一条记录:路径用"整理后位置",没有就用源路径。 type nsfwView struct { Id int64 `json:"id"` // 主键 FileName string `json:"fileName"` // 文件名 Score float64 `json:"score"` // 检测分 0~1 Threshold float64 `json:"threshold"` // 判定阈值 Path string `json:"path"` // 预览/打开用的完整路径 CreatedAt string `json:"createdAt"` // 检测时间 } // List 处理 GET /api/nsfw/list:查全部色情识别记录(按检测分从高到低)。 func (c *NsfwController) List(ctx *gin.Context) { list, err := c.store.ListNsfw() if err != nil { Error(ctx, http.StatusInternalServerError, "查询色情识别记录失败:"+err.Error()) return } views := make([]nsfwView, 0, len(list)) for _, r := range list { path := r.MovedPath if path == "" { path = r.FilePath } views = append(views, nsfwView{ Id: r.Id, FileName: r.FileName, Score: r.Score, Threshold: r.Threshold, Path: path, CreatedAt: r.CreatedAt.Format("2006-01-02 15:04:05"), }) } Success(ctx, map[string]interface{}{ "list": views, "total": len(views), "nsfwEnabled": config.GetAppConfig().NSFWEnabled, // 没扫描过(空)或上次成功(1)都算可用;只有明确记过失败(0)才提示检测服务没起来 "nsfwDetector": c.store == nil || c.store.GetConfig(nsfwDetectorReadyKey) != "0", }) } // Preview 处理 GET /api/nsfw/preview?path=xxx:把指定图片文件返回给浏览器做预览。 // 这是本地单机工具,直接按路径读文件;文件不存在/已被移动时返回 404。 func (c *NsfwController) Preview(ctx *gin.Context) { path := strings.TrimSpace(ctx.Query("path")) if path == "" { Error(ctx, http.StatusBadRequest, "缺少 path 参数") return } if _, err := os.Stat(path); err != nil { Error(ctx, http.StatusNotFound, "图片不存在或已被移动: "+path) return } // 交给 gin 读文件并带上正确的 Content-Type / 断点续传 ctx.File(path) }