File size: 1,994 Bytes
6d60378
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
package server

import (
	"sync"
	"time"
)

// degradeGate 降级状态机:passthrough 模式下请求被上游内容策略拦截时,
// 切换到 Degraded 中性提示词直到次日 00:00 CST 重置。
//
// 定位:误报处理(与 internal/upstream/sanitize.go 同哲学)——内容拦截多为
// system 来源的指纹误报,换最小中性提示词即可绕开;不是对抗框架。
//
// 状态机:进程内存、重启清零(可接受:重启极少触发,且 custom 模式根本
// 不进降级路径)。降级期内的请求直达 Degraded,不再先撞 400。
type degradeGate struct {
	mu    sync.Mutex
	until time.Time
}

// Active 当前是否处于降级期(now < until)。
func (g *degradeGate) Active() bool {
	g.mu.Lock()
	defer g.mu.Unlock()
	return time.Now().Before(g.until)
}

// Trigger 触发降级,直到次日 00:00 CST(Asia/Shanghai)。
// 已在降级期内则不续期(保持最早触发点的 00:00 重置语义)。
func (g *degradeGate) Trigger() {
	g.mu.Lock()
	defer g.mu.Unlock()
	if !time.Now().Before(g.until) {
		// 已过期/未触发 → 设新的 until;未过期则保持原 until(不续期)。
		g.until = nextMidnightCST(time.Now())
	}
}

// nextMidnightCST 返回 now 之后最近的 Asia/Shanghai 00:00 时刻(纯函数,方便单测)。
//
// 边界语义:
//   - 23:59 → 次日 00:00(几秒后)
//   - 00:00 → 次日 00:00(刚过 00:00,下个零点是次日)
//
// 用固定 +08:00 偏移计算,避免依赖系统时区配置(容器/宿主机时区不确定)。
func nextMidnightCST(now time.Time) time.Time {
	cst := time.FixedZone("CST", 8*60*60)
	// 把 now 转到 CST 视角取当天 00:00,再加一天;若该 00:00 <= now 则再加一天。
	y, m, d := now.In(cst).Date()
	midnight := time.Date(y, m, d, 0, 0, 0, 0, cst)
	for !midnight.After(now) {
		midnight = midnight.Add(24 * time.Hour)
	}
	return midnight
}