🐛 fix(server): 增加优雅退出与请求超时保护

- 新增 SIGINT/SIGTERM 信号处理与 http.Server.Shutdown,退出前确保统计数据保存、数据库正常关闭
- 为 http.Server 设置 ReadHeaderTimeout/ReadTimeout/WriteTimeout/IdleTimeout,防止慢客户端长期占用连接
- 全局配置指针改为 atomic.Pointer 并提供 GetAppConfig 访问器,消除配置热更新时的数据竞争,同时删除冗余的 manager.config 字段
- 请求统计九组计数器重构为 map[string]*atomic.Int64,统一 incrementStat/getStatsSnapshot 访问,精简约 40 行样板代码
- go mod tidy 清理 go.mod 与 go.sum 中的冗余依赖条目
This commit is contained in:
chenxin committed 2026-09-29 19:47:04 +08:00
1 parent 6981148727
commit d710d382ee
12 files changed
+125 -305

No files matched your search

+42 -15
View File
@@ -1,6 +1,7 @@
package main
import (
"context"
"fanqie/internal/algos"
"fanqie/internal/common"
"fanqie/internal/config"
@@ -10,6 +11,9 @@ import (
"log"
"net/http"
"os"
"os/signal"
"syscall"
"time"
)
var (
@@ -85,15 +89,15 @@ func initConfig() {
// initAlgorithm 初始化算法
func initAlgorithm() {
if config.AppConfig != nil {
algos.GlobalAlgorithmSwitcher.SwitchAlgorithm(config.AppConfig.Fanqie.Algorithm)
logger.Info(fmt.Sprintf("当前算法: %s", config.AppConfig.Fanqie.Algorithm))
if config.GetAppConfig() != nil {
algos.GlobalAlgorithmSwitcher.SwitchAlgorithm(config.GetAppConfig().Fanqie.Algorithm)
logger.Info(fmt.Sprintf("当前算法: %s", config.GetAppConfig().Fanqie.Algorithm))
algos.GlobalSignatureSwitcher.SwitchSignature(config.AppConfig.Fanqie.Signature)
algos.GlobalSignatureSwitcher.SwitchSignature(config.GetAppConfig().Fanqie.Signature)
// 根据签名方法显示详细信息
var signatureDetails string
if config.AppConfig.Fanqie.Signature == "sign4" {
if config.GetAppConfig().Fanqie.Signature == "sign4" {
signatureDetails = "X-Gorgon + X-Ladon + X-Argus + X-Khronos"
} else {
signatureDetails = "X-Gorgon + X-Khronos"
@@ -159,22 +163,45 @@ func startServer() {
host := "0.0.0.0"
port := "40180"
if config.AppConfig != nil {
if config.AppConfig.Server.Host != "" {
host = config.AppConfig.Server.Host
if config.GetAppConfig() != nil {
if config.GetAppConfig().Server.Host != "" {
host = config.GetAppConfig().Server.Host
}
if config.AppConfig.Server.Port != "" {
port = config.AppConfig.Server.Port
if config.GetAppConfig().Server.Port != "" {
port = config.GetAppConfig().Server.Port
}
}
addr := host + ":" + port
logger.Info(fmt.Sprintf("服务器启动在 %s", addr))
if err := http.ListenAndServe(addr, nil); err != nil {
logger.Error(fmt.Sprintf("服务器启动失败: %v", err))
os.Exit(1)
server := &http.Server{
Addr: addr,
// 超时保护:防止慢客户端长期占用连接
ReadHeaderTimeout: 10 * time.Second,
ReadTimeout: 60 * time.Second,
WriteTimeout: 10 * time.Minute, // 代理响应整体缓冲后写出,慢客户端下载大音频/图片需要余量
IdleTimeout: 120 * time.Second,
}
go func() {
logger.Info(fmt.Sprintf("服务器启动在 %s", addr))
if err := server.ListenAndServe(); err != nil && err != http.ErrServerClosed {
logger.Error(fmt.Sprintf("服务器启动失败: %v", err))
os.Exit(1)
}
}()
// 等待退出信号,优雅关闭,让 main 中的 defer(保存统计、关闭数据库)得以执行
sigCh := make(chan os.Signal, 1)
signal.Notify(sigCh, os.Interrupt, syscall.SIGTERM)
<-sigCh
logger.Info("收到退出信号,正在关闭服务器...")
ctx, cancel := context.WithTimeout(context.Background(), 30*time.Second)
defer cancel()
if err := server.Shutdown(ctx); err != nil {
logger.Warning(fmt.Sprintf("服务器关闭超时(30秒内未完成的连接被强制断开): %v", err))
}
logger.Info("服务器已关闭")
}
func init() {