- feat: 新增续传表 scan_log,重扫跳过已归类文件(尺寸/头尾指纹校验) - fix: 色情识别记录批量入库超 SQLite 999 变量上限,改每 100 条一批 - perf: NudeNet 检测改为多线程推理池(每线程独立模型实例) - perf: 明暗分析 JPEG(YCbCr) 快路径;SQLite 读并发放开;加阶段耗时统计 - fix: 前端色情识别状态由后端如实上报(配置开关+检测服务是否起来) - feat: ffprobe 内嵌按平台拆分,Linux 构建回退系统 ffprobe,支持交叉编译 - docs: README 补充断点续传与 CentOS 交叉编译步骤 - chore: organize.workers 默认 8
88 lines
3.1 KiB
Go
88 lines
3.1 KiB
Go
// 色情识别相关的两个接口:查记录、看图片预览。
|
|
// 前端「色情识别」页面用它展示每次扫描判出的色情图片(检测分 + 路径 + 预览),
|
|
// 方便用户核对识别结果准不准。只读数据库 + 读本地图片文件,不干别的活。
|
|
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)
|
|
}
|