✨ feat(server): 新增每 IP 请求限流
- 新增 RateLimit 中间件:golang.org/x/time/rate 令牌桶,超出返回 429 - server.rate_limit_qps 配置(默认 0 不限制,突发 10),公网部署建议开启 - 业务路由统一接入 限流 → 鉴权 中间件链;/healthz 与静态资源不限流 - X-Forwarded-For 优先取真实客户端 IP(反代场景),visitor 表定时清理防泄漏 - config.yaml 补齐 protect_all 与 rate_limit_qps 配置项(此前仅存在于代码)
This commit is contained in:
1 parent
822d3fe444
commit
f89bee8ddc
5 files changed
+112
-38
No files matched your search
+31
-28
@@ -40,10 +40,10 @@ func main() {
|
||||
os.Exit(1)
|
||||
}
|
||||
defer fqapi.CloseRedis()
|
||||
|
||||
|
||||
// 加载统计数据
|
||||
handlers.LoadStatistics()
|
||||
|
||||
|
||||
// 启动统计数据定期保存
|
||||
handlers.StartStatisticsSaver()
|
||||
defer handlers.StopStatisticsSaver() // 程序退出时保存统计数据
|
||||
@@ -99,9 +99,9 @@ func initAlgorithm() {
|
||||
if config.GetAppConfig() != nil {
|
||||
algos.GlobalAlgorithmSwitcher.SwitchAlgorithm(config.GetAppConfig().Fanqie.Algorithm)
|
||||
logger.Info(fmt.Sprintf("当前算法: %s", config.GetAppConfig().Fanqie.Algorithm))
|
||||
|
||||
|
||||
algos.GlobalSignatureSwitcher.SwitchSignature(config.GetAppConfig().Fanqie.Signature)
|
||||
|
||||
|
||||
// 根据签名方法显示详细信息
|
||||
var signatureDetails string
|
||||
if config.GetAppConfig().Fanqie.Signature == "sign4" {
|
||||
@@ -121,7 +121,7 @@ func initDeviceKeyPool() error {
|
||||
pool := fqapi.GetDeviceKeyPool()
|
||||
if err := pool.Initialize(); err != nil {
|
||||
return fmt.Errorf("初始化设备密钥池失败: %w", err)
|
||||
}
|
||||
}
|
||||
|
||||
// 显示池状态
|
||||
stats := pool.GetStats()
|
||||
@@ -140,29 +140,32 @@ func setupRoutes() {
|
||||
|
||||
http.HandleFunc("/healthz", handlers.HandleHealth)
|
||||
|
||||
// API端点(protect_all 开启时要求访问密钥;代理路由不包,URL 由服务端生成不含 key)
|
||||
http.HandleFunc("/search", handlers.OptionalAccessKey(handlers.HandleSearch))
|
||||
http.HandleFunc("/detail", handlers.OptionalAccessKey(handlers.HandleDetail))
|
||||
http.HandleFunc("/info", handlers.OptionalAccessKey(handlers.HandleDetail))
|
||||
http.HandleFunc("/catalog", handlers.OptionalAccessKey(handlers.HandleCatalog))
|
||||
http.HandleFunc("/directory/all_items", handlers.OptionalAccessKey(handlers.HandleCatalog))
|
||||
http.HandleFunc("/multi-detail", handlers.OptionalAccessKey(handlers.HandleMultiDetail))
|
||||
http.HandleFunc("/multi-content", handlers.OptionalAccessKey(handlers.HandleMultiContent))
|
||||
http.HandleFunc("/content", handlers.OptionalAccessKey(handlers.HandleContent))
|
||||
http.HandleFunc("/item-summary", handlers.OptionalAccessKey(handlers.HandleItemSummary))
|
||||
http.HandleFunc("/category-front", handlers.OptionalAccessKey(handlers.HandleCategoryFront))
|
||||
http.HandleFunc("/category-landing", handlers.OptionalAccessKey(handlers.HandleCategoryLanding))
|
||||
http.HandleFunc("/audio", handlers.OptionalAccessKey(handlers.HandleAudio))
|
||||
http.HandleFunc("/audio/proxy/", handlers.HandleAudioProxy)
|
||||
http.HandleFunc("/video", handlers.OptionalAccessKey(handlers.HandleVideo))
|
||||
http.HandleFunc("/manga", handlers.OptionalAccessKey(handlers.HandleManga))
|
||||
http.HandleFunc("/manga/image/", handlers.HandleMangaImageProxy)
|
||||
// API端点:限流 + protect_all 鉴权链(代理路由不鉴权,URL 由服务端生成不含 key)
|
||||
wrap := func(h http.HandlerFunc) http.HandlerFunc {
|
||||
return handlers.RateLimit(handlers.OptionalAccessKey(h))
|
||||
}
|
||||
http.HandleFunc("/search", wrap(handlers.HandleSearch))
|
||||
http.HandleFunc("/detail", wrap(handlers.HandleDetail))
|
||||
http.HandleFunc("/info", wrap(handlers.HandleDetail))
|
||||
http.HandleFunc("/catalog", wrap(handlers.HandleCatalog))
|
||||
http.HandleFunc("/directory/all_items", wrap(handlers.HandleCatalog))
|
||||
http.HandleFunc("/multi-detail", wrap(handlers.HandleMultiDetail))
|
||||
http.HandleFunc("/multi-content", wrap(handlers.HandleMultiContent))
|
||||
http.HandleFunc("/content", wrap(handlers.HandleContent))
|
||||
http.HandleFunc("/item-summary", wrap(handlers.HandleItemSummary))
|
||||
http.HandleFunc("/category-front", wrap(handlers.HandleCategoryFront))
|
||||
http.HandleFunc("/category-landing", wrap(handlers.HandleCategoryLanding))
|
||||
http.HandleFunc("/audio", wrap(handlers.HandleAudio))
|
||||
http.HandleFunc("/audio/proxy/", wrap(handlers.HandleAudioProxy))
|
||||
http.HandleFunc("/video", wrap(handlers.HandleVideo))
|
||||
http.HandleFunc("/manga", wrap(handlers.HandleManga))
|
||||
http.HandleFunc("/manga/image/", wrap(handlers.HandleMangaImageProxy))
|
||||
|
||||
// 评论相关端点
|
||||
http.HandleFunc("/chapter_review", handlers.OptionalAccessKey(handlers.HandleChapterReview))
|
||||
http.HandleFunc("/review", handlers.OptionalAccessKey(handlers.HandleReview))
|
||||
http.HandleFunc("/review_html", handlers.OptionalAccessKey(handlers.HandleReviewHTML))
|
||||
|
||||
http.HandleFunc("/chapter_review", wrap(handlers.HandleChapterReview))
|
||||
http.HandleFunc("/review", wrap(handlers.HandleReview))
|
||||
http.HandleFunc("/review_html", wrap(handlers.HandleReviewHTML))
|
||||
|
||||
// 表情图片静态文件服务
|
||||
http.Handle("/emoji/", http.StripPrefix("/emoji/", http.FileServer(http.Dir("emoji"))))
|
||||
}
|
||||
@@ -171,7 +174,7 @@ func setupRoutes() {
|
||||
func startServer() {
|
||||
host := "0.0.0.0"
|
||||
port := "40180"
|
||||
|
||||
|
||||
if config.GetAppConfig() != nil {
|
||||
if config.GetAppConfig().Server.Host != "" {
|
||||
host = config.GetAppConfig().Server.Host
|
||||
@@ -180,7 +183,7 @@ func startServer() {
|
||||
port = config.GetAppConfig().Server.Port
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
addr := host + ":" + port
|
||||
server := &http.Server{
|
||||
Addr: addr,
|
||||
|
||||
Reference in new issue
Block a user