|
8 | 8 | "net/http" |
9 | 9 | "os" |
10 | 10 | "path/filepath" |
| 11 | + "runtime" |
11 | 12 | "strconv" |
12 | 13 | "strings" |
13 | 14 | "time" |
@@ -63,6 +64,7 @@ func corsMiddleware(c *gin.Context) { |
63 | 64 | c.Next() |
64 | 65 | } |
65 | 66 |
|
| 67 | +// ServeWeb 启动Web服务器并配置路由 |
66 | 68 | func ServeWeb(port uint) *http.Server { |
67 | 69 | gin.SetMode(gin.ReleaseMode) |
68 | 70 |
|
@@ -97,62 +99,155 @@ func ServeWeb(port uint) *http.Server { |
97 | 99 |
|
98 | 100 | // 添加Recovery中间件,过滤网络连接错误 |
99 | 101 | r.Use(gin.CustomRecovery(func(c *gin.Context, recovered interface{}) { |
100 | | - if err, ok := recovered.(string); ok { |
101 | | - errLower := strings.ToLower(err) |
102 | | - if strings.Contains(errLower, "broken pipe") || |
103 | | - strings.Contains(errLower, "connection reset") || |
104 | | - strings.Contains(errLower, "use of closed network connection") { |
105 | | - // 静默处理网络连接错误 |
106 | | - c.AbortWithStatus(http.StatusInternalServerError) |
107 | | - return |
108 | | - } |
109 | | - } |
110 | | - // 其他错误正常处理 |
| 102 | + // 检查是否为broken pipe错误 |
| 103 | + if errStr := fmt.Sprintf("%v", recovered); strings.Contains(errStr, "broken pipe") || |
| 104 | + strings.Contains(errStr, "connection reset") || |
| 105 | + strings.Contains(errStr, "use of closed network connection") { |
| 106 | + // 静默处理broken pipe错误,不中断其他请求处理 |
| 107 | + c.AbortWithStatus(499) // 使用499表示客户端关闭连接 |
| 108 | + return |
| 109 | + } |
| 110 | + |
| 111 | + // 其他panic正常处理 |
| 112 | + log.Printf("服务器Panic: %v", recovered) |
111 | 113 | c.AbortWithStatus(http.StatusInternalServerError) |
112 | 114 | })) |
113 | | - |
114 | | - if singleton.Conf.Debug { |
115 | | - gin.SetMode(gin.DebugMode) |
| 115 | + |
| 116 | + // 添加handleBrokenPipe中间件 |
| 117 | + r.Use(handleBrokenPipe) |
| 118 | + |
| 119 | + // 添加CORS中间件 |
| 120 | + r.Use(corsMiddleware) |
| 121 | + |
| 122 | + // 添加模板渲染保护中间件 - 这是防止前端页面无法访问的关键 |
| 123 | + r.Use(TemplateRenderingSafeguard()) |
| 124 | + |
| 125 | + // 添加健康检查中间件 |
| 126 | + r.Use(HealthCheckMiddleware()) |
| 127 | + |
| 128 | + // 添加pprof性能分析 |
| 129 | + if singleton.Conf != nil && singleton.Conf.Debug { |
116 | 130 | pprof.Register(r) |
117 | 131 | } |
118 | | - r.Use(natGateway) |
119 | | - r.Use(handleBrokenPipe) // 添加broken pipe错误处理中间件 |
120 | | - r.Use(corsMiddleware) // 添加CORS中间件处理OPTIONS请求 |
121 | | - tmpl := template.New("").Funcs(funcMap) |
122 | | - var err error |
123 | | - // 直接用本地模板目录 |
124 | | - tmpl, err = tmpl.ParseGlob("resource/template/**/*.html") |
125 | | - if err != nil { |
126 | | - panic(err) |
127 | | - } |
128 | | - tmpl = loadThirdPartyTemplates(tmpl) |
129 | | - r.SetHTMLTemplate(tmpl) |
130 | | - r.Use(mygin.RecordPath) |
131 | | - // 直接用本地静态资源目录 |
132 | | - r.Static("/static", "resource/static") |
133 | | - |
134 | | - routers(r) |
135 | | - page404 := func(c *gin.Context) { |
136 | | - mygin.ShowErrorPage(c, mygin.ErrInfo{ |
137 | | - Code: http.StatusNotFound, |
138 | | - Title: "该页面不存在", |
139 | | - Msg: "该页面内容可能已着陆火星", |
140 | | - Link: "/", |
141 | | - Btn: "返回首页", |
142 | | - }, true) |
143 | | - } |
144 | | - r.NoRoute(page404) |
145 | | - r.NoMethod(page404) |
146 | | - |
147 | | - srv := &http.Server{ |
148 | | - Addr: fmt.Sprintf(":%d", port), |
149 | | - ReadHeaderTimeout: time.Second * 5, |
150 | | - Handler: r, |
| 132 | + |
| 133 | + // 设置HTML模板和静态资源 |
| 134 | + r.SetFuncMap(template.FuncMap{ |
| 135 | + "tf": func(t time.Time) string { |
| 136 | + return t.Format("2006-01-02 15:04:05") |
| 137 | + }, |
| 138 | + "fisher": func(t time.Time) string { |
| 139 | + return t.Format("2006/01/02 15:04") |
| 140 | + }, |
| 141 | + "tf2": func(t time.Time) string { |
| 142 | + return t.In(singleton.Loc).Format("01/02 15:04") |
| 143 | + }, |
| 144 | + "markdown": func(text string) template.HTML { |
| 145 | + // 简单地将markdown转为HTML |
| 146 | + return template.HTML(text) |
| 147 | + }, |
| 148 | + "tag": func(text string) template.HTML { |
| 149 | + return template.HTML(`<` + text + `>`) |
| 150 | + }, |
| 151 | + "trim": func(text string) string { |
| 152 | + return strings.TrimSpace(text) |
| 153 | + }, |
| 154 | + "recent": func(t time.Time) bool { |
| 155 | + return time.Since(t).Seconds() <= 60 |
| 156 | + }, |
| 157 | + "find": func(slice []string, val string) int { |
| 158 | + for i, item := range slice { |
| 159 | + if item == val { |
| 160 | + return i |
| 161 | + } |
| 162 | + } |
| 163 | + return -1 |
| 164 | + }, |
| 165 | + "next": func(pos, length int) int { |
| 166 | + pos++ |
| 167 | + if pos >= length { |
| 168 | + pos = 0 |
| 169 | + } |
| 170 | + return pos |
| 171 | + }, |
| 172 | + "prev": func(pos, length int) int { |
| 173 | + pos-- |
| 174 | + if pos < 0 { |
| 175 | + pos = length - 1 |
| 176 | + } |
| 177 | + return pos |
| 178 | + }, |
| 179 | + "fs": func(bytes uint64) string { |
| 180 | + return bytefmt.ByteSize(bytes) |
| 181 | + }, |
| 182 | + "tf_rfc3339": func(t time.Time) string { |
| 183 | + return t.Format(time.RFC3339) |
| 184 | + }, |
| 185 | + "pb": func(p float64) string { |
| 186 | + return fmt.Sprintf("%.2f", p*100) |
| 187 | + }, |
| 188 | + "isPositive": func(a float64) bool { |
| 189 | + return a > 0 |
| 190 | + }, |
| 191 | + "isNegative": func(a float64) bool { |
| 192 | + return a < 0 |
| 193 | + }, |
| 194 | + "abs": func(a float64) float64 { |
| 195 | + if a < 0 { |
| 196 | + return -a |
| 197 | + } |
| 198 | + return a |
| 199 | + }, |
| 200 | + "roundTo": func(decimals int, num float64) float64 { |
| 201 | + f := math.Pow10(decimals) |
| 202 | + // 四舍五入 |
| 203 | + return math.Round(num*f) / f |
| 204 | + }, |
| 205 | + }) |
| 206 | + |
| 207 | + // 静态资源 |
| 208 | + r.StaticFS("/static", http.Dir("resource/static")) |
| 209 | + |
| 210 | + // 健康检查路由 |
| 211 | + r.GET("/health", HealthCheckHandler) |
| 212 | + r.GET("/system-health", SystemHealthHandler) |
| 213 | + |
| 214 | + // 创建路由分组和控制器 |
| 215 | + commonController := CreateControllers(r) |
| 216 | + |
| 217 | + // 创建HTTP服务器 |
| 218 | + server := &http.Server{ |
| 219 | + Addr: fmt.Sprintf(":%d", port), |
| 220 | + Handler: r, |
151 | 221 | } |
152 | | - return srv |
| 222 | + |
| 223 | + return server |
153 | 224 | } |
154 | 225 |
|
155 | 226 | func routers(r *gin.Engine) { |
| 227 | + // 添加健康检查接口 |
| 228 | + r.GET("/health", func(c *gin.Context) { |
| 229 | + // 简单版本,直接返回200 |
| 230 | + c.JSON(http.StatusOK, gin.H{ |
| 231 | + "status": "ok", |
| 232 | + "time": time.Now().Format(time.RFC3339), |
| 233 | + }) |
| 234 | + }) |
| 235 | + |
| 236 | + // 系统监控端点 (需要权限才能访问) |
| 237 | + r.GET("/system-health", func(c *gin.Context) { |
| 238 | + // 返回系统健康信息 |
| 239 | + memStats := runtime.MemStats{} |
| 240 | + runtime.ReadMemStats(&memStats) |
| 241 | + |
| 242 | + c.JSON(http.StatusOK, gin.H{ |
| 243 | + "status": "ok", |
| 244 | + "goroutines": runtime.NumGoroutine(), |
| 245 | + "memoryUsageMB": float64(memStats.Alloc) / (1024 * 1024), |
| 246 | + "time": time.Now().Format(time.RFC3339), |
| 247 | + "templateHealthy": GlobalTemplateSafeguard.IsHealthy(), |
| 248 | + }) |
| 249 | + }) |
| 250 | + |
156 | 251 | // 通用页面 |
157 | 252 | cp := commonPage{r: r} |
158 | 253 | cp.serve() |
@@ -396,6 +491,50 @@ var funcMap = template.FuncMap{ |
396 | 491 | }, |
397 | 492 | } |
398 | 493 |
|
| 494 | +// CreateControllers 创建并初始化所有控制器 |
| 495 | +func CreateControllers(engine *gin.Engine) *commonPage { |
| 496 | + // 创建通用页面控制器 |
| 497 | + cp := &commonPage{ |
| 498 | + r: engine, |
| 499 | + } |
| 500 | + cp.serve() |
| 501 | + |
| 502 | + // 创建访客页面控制器 |
| 503 | + gp := &guestPage{ |
| 504 | + r: engine, |
| 505 | + } |
| 506 | + gp.serve() |
| 507 | + |
| 508 | + // 创建OAuth2控制器 |
| 509 | + oa := &oauth2controller{ |
| 510 | + r: engine, |
| 511 | + } |
| 512 | + oa.serve() |
| 513 | + |
| 514 | + // 创建会员页面控制器 |
| 515 | + mp := &memberPage{ |
| 516 | + r: engine, |
| 517 | + } |
| 518 | + mp.serve() |
| 519 | + |
| 520 | + // 创建会员API控制器 |
| 521 | + ma := &memberAPI{ |
| 522 | + r: engine, |
| 523 | + } |
| 524 | + ma.serve() |
| 525 | + |
| 526 | + // 创建APIv1控制器 |
| 527 | + api := &apiV1{ |
| 528 | + r: engine, |
| 529 | + } |
| 530 | + api.serve() |
| 531 | + |
| 532 | + // 添加NAT网关中间件 |
| 533 | + engine.NoRoute(natGateway) |
| 534 | + |
| 535 | + return cp |
| 536 | +} |
| 537 | + |
399 | 538 | func natGateway(c *gin.Context) { |
400 | 539 | natConfig := singleton.GetNATConfigByDomain(c.Request.Host) |
401 | 540 | if natConfig == nil { |
|
0 commit comments