feat: 目录层级变更

This commit is contained in:
2025-09-24 21:19:55 +08:00
parent 7792f9281c
commit 72c9d67d9b
12 changed files with 156 additions and 317 deletions
+15 -27
View File
@@ -45,7 +45,6 @@ const requestTimeout = 60 * time.Second
// Detector 封装一个本地 python 服务的客户端。
type Detector struct {
url string
threshold float64
workers int
client *http.Client
cmd *exec.Cmd
@@ -60,10 +59,7 @@ type Detector struct {
}
// Start 拉起本地 python 检测服务并等它就绪。
func Start(pythonPath string, threshold float64, workers int) (*Detector, error) {
if threshold <= 0 || threshold >= 1 {
threshold = 0.5
}
func Start(pythonPath string, workers int) (*Detector, error) {
if workers <= 0 {
workers = 2 // 默认 2 个 worker
}
@@ -88,7 +84,6 @@ func Start(pythonPath string, threshold float64, workers int) (*Detector, error)
}
d := &Detector{
threshold: threshold,
workers: workers,
client: &http.Client{Timeout: requestTimeout},
python: py,
@@ -189,33 +184,32 @@ type AnimeRatings struct {
Explicit float64 `json:"explicit"`
}
// Detect 检测一张动漫图片是否色情。返回:
// Detect 检测一张动漫图片的色情评分。返回:
// - score:max(explicit, questionable) 的 sigmoid 分数(若 G 最高则为 0)
// - nsfw:是否判定为色情(分数 >= 阈值)
// - ratings:四个 rating 的原始分数
// - err:检测失败
func (d *Detector) Detect(path string) (score float64, nsfw bool, ratings AnimeRatings, err error) {
score, nsfw, ratings, err = d.detectOnce(path)
func (d *Detector) Detect(path string) (score float64, ratings AnimeRatings, err error) {
score, ratings, err = d.detectOnce(path)
if err == nil {
return score, nsfw, ratings, nil
return score, ratings, nil
}
var ce *connError
if !errors.As(err, &ce) {
return score, nsfw, ratings, err
return score, ratings, err
}
d.restartMu.Lock()
defer d.restartMu.Unlock()
if d.isStopped() {
return 0, false, ratings, err
return 0, ratings, err
}
if d.healthy() {
return d.detectOnce(path)
}
fmt.Printf("=== 动漫检测服务失联(%v), 正在重启…\n", ce.err)
if rerr := d.launch(); rerr != nil {
return 0, false, ratings, fmt.Errorf("动漫检测服务重启失败: %v", rerr)
return 0, ratings, fmt.Errorf("动漫检测服务重启失败: %v", rerr)
}
fmt.Println("=== 动漫检测服务已重启,继续识别")
return d.detectOnce(path)
@@ -242,24 +236,22 @@ func (d *Detector) isStopped() bool {
return d.stopped
}
func (d *Detector) detectOnce(path string) (score float64, nsfw bool, ratings AnimeRatings, err error) {
func (d *Detector) detectOnce(path string) (score float64, ratings AnimeRatings, err error) {
body, _ := json.Marshal(map[string]interface{}{
"path": path,
"threshold": d.threshold,
"path": path,
})
resp, err := d.client.Post(d.url+"/detect", "application/json", bytes.NewReader(body))
if err != nil {
return 0, false, ratings, &connError{err}
return 0, ratings, &connError{err}
}
defer resp.Body.Close()
data, err := io.ReadAll(resp.Body)
if err != nil {
return 0, false, ratings, err
return 0, ratings, err
}
var out struct {
Ok bool `json:"ok"`
Error string `json:"error"`
Nsfw bool `json:"nsfw"`
Score float64 `json:"score"`
Ratings struct {
General float64 `json:"general"`
@@ -270,10 +262,10 @@ func (d *Detector) detectOnce(path string) (score float64, nsfw bool, ratings An
TopTags []map[string]interface{} `json:"top_tags"`
}
if err := json.Unmarshal(data, &out); err != nil {
return 0, false, ratings, fmt.Errorf("解析检测结果失败: %v", err)
return 0, ratings, fmt.Errorf("解析检测结果失败: %v", err)
}
if !out.Ok {
return 0, false, ratings, fmt.Errorf("检测服务返回错误: %s", out.Error)
return 0, ratings, fmt.Errorf("检测服务返回错误: %s", out.Error)
}
ratings = AnimeRatings{
General: out.Ratings.General,
@@ -284,7 +276,7 @@ func (d *Detector) detectOnce(path string) (score float64, nsfw bool, ratings An
// 打印 rating 详情(调试用)
fmt.Printf("=== AnimeNSFW 检测详情 %s: [G=%.3f S=%.3f Q=%.3f E=%.3f]\n",
path, ratings.General, ratings.Sensitive, ratings.Questionable, ratings.Explicit)
return out.Score, out.Nsfw, ratings, nil
return out.Score, ratings, nil
}
type connError struct{ err error }
@@ -305,10 +297,6 @@ func (d *Detector) Stop() {
}
}
func (d *Detector) Threshold() float64 {
return d.threshold
}
func resolvePython(configured string) string {
candidates := []string{}
if strings.TrimSpace(configured) != "" {