@@ -139,6 +139,8 @@ func (h *Handler) RegisterRoutes(r *gin.Engine) {
139139 api .DELETE ("/keys/:id" , h .DeleteAPIKey )
140140 api .GET ("/health" , h .GetHealth )
141141 api .GET ("/ops/overview" , h .GetOpsOverview )
142+ api .GET ("/ops/errors" , h .GetOpsErrorLogs )
143+ api .GET ("/ops/errors/summary" , h .GetOpsErrorSummary )
142144 api .GET ("/settings" , h .GetSettings )
143145 api .PUT ("/settings" , h .UpdateSettings )
144146 api .POST ("/settings/image-storage/test" , h .TestImageStorageConnection )
@@ -1970,6 +1972,140 @@ func (h *Handler) GetChartData(c *gin.Context) {
19701972 c .JSON (http .StatusOK , result )
19711973}
19721974
1975+ func parseOpsErrorPositiveInt64 (c * gin.Context , name string ) (* int64 , bool ) {
1976+ raw := strings .TrimSpace (c .Query (name ))
1977+ if raw == "" {
1978+ return nil , true
1979+ }
1980+ parsed , err := strconv .ParseInt (raw , 10 , 64 )
1981+ if err != nil || parsed <= 0 {
1982+ writeError (c , http .StatusBadRequest , fmt .Sprintf ("%s 参数无效,需要正整数" , name ))
1983+ return nil , false
1984+ }
1985+ return & parsed , true
1986+ }
1987+
1988+ func parseOpsErrorLogFilter (c * gin.Context , withPaging bool ) (database.UsageLogFilter , bool ) {
1989+ endTime := time .Now ()
1990+ startTime := endTime .Add (- 1 * time .Hour )
1991+ startStr := strings .TrimSpace (c .Query ("start" ))
1992+ endStr := strings .TrimSpace (c .Query ("end" ))
1993+ if startStr != "" || endStr != "" {
1994+ if startStr == "" || endStr == "" {
1995+ writeError (c , http .StatusBadRequest , "start/end 参数需要同时提供" )
1996+ return database.UsageLogFilter {}, false
1997+ }
1998+ parsedStart , e1 := time .Parse (time .RFC3339 , startStr )
1999+ parsedEnd , e2 := time .Parse (time .RFC3339 , endStr )
2000+ if e1 != nil || e2 != nil {
2001+ writeError (c , http .StatusBadRequest , "start/end 参数格式错误,需要 RFC3339 格式" )
2002+ return database.UsageLogFilter {}, false
2003+ }
2004+ startTime = parsedStart
2005+ endTime = parsedEnd
2006+ }
2007+
2008+ apiKeyID , ok := parseOpsErrorPositiveInt64 (c , "api_key_id" )
2009+ if ! ok {
2010+ return database.UsageLogFilter {}, false
2011+ }
2012+ accountID , ok := parseOpsErrorPositiveInt64 (c , "account_id" )
2013+ if ! ok {
2014+ return database.UsageLogFilter {}, false
2015+ }
2016+
2017+ filter := database.UsageLogFilter {
2018+ Start : startTime ,
2019+ End : endTime ,
2020+ Page : 1 ,
2021+ PageSize : 20 ,
2022+ Email : strings .TrimSpace (c .Query ("email" )),
2023+ Model : strings .TrimSpace (c .Query ("model" )),
2024+ Endpoint : strings .TrimSpace (c .Query ("endpoint" )),
2025+ APIKeyID : apiKeyID ,
2026+ AccountID : accountID ,
2027+ ErrorOnly : true ,
2028+ IncludeCanceled : true ,
2029+ ErrorKind : strings .TrimSpace (c .Query ("error_kind" )),
2030+ Query : strings .TrimSpace (c .Query ("q" )),
2031+ }
2032+
2033+ status := strings .TrimSpace (c .Query ("status" ))
2034+ if status == "" {
2035+ status = strings .TrimSpace (c .Query ("status_code" ))
2036+ }
2037+ switch strings .ToLower (status ) {
2038+ case "" , "all" :
2039+ case "4xx" , "5xx" :
2040+ filter .StatusFamily = strings .ToLower (status )
2041+ default :
2042+ statusCode , err := strconv .Atoi (status )
2043+ if err != nil || statusCode < 100 || statusCode > 599 {
2044+ writeError (c , http .StatusBadRequest , "status/status_code 参数无效" )
2045+ return database.UsageLogFilter {}, false
2046+ }
2047+ filter .StatusCode = statusCode
2048+ }
2049+
2050+ if fastStr := c .Query ("fast" ); fastStr != "" {
2051+ v := fastStr == "true"
2052+ filter .FastOnly = & v
2053+ }
2054+ if streamStr := c .Query ("stream" ); streamStr != "" {
2055+ v := streamStr == "true"
2056+ filter .StreamOnly = & v
2057+ }
2058+
2059+ if withPaging {
2060+ if pageStr := c .Query ("page" ); pageStr != "" {
2061+ if page , err := strconv .Atoi (pageStr ); err == nil && page > 0 {
2062+ filter .Page = page
2063+ }
2064+ }
2065+ if ps := c .Query ("page_size" ); ps != "" {
2066+ if n , err := strconv .Atoi (ps ); err == nil && n > 0 && n <= 200 {
2067+ filter .PageSize = n
2068+ }
2069+ }
2070+ }
2071+
2072+ return filter , true
2073+ }
2074+
2075+ // GetOpsErrorLogs 获取运维错误日志
2076+ func (h * Handler ) GetOpsErrorLogs (c * gin.Context ) {
2077+ ctx , cancel := context .WithTimeout (c .Request .Context (), 15 * time .Second )
2078+ defer cancel ()
2079+
2080+ filter , ok := parseOpsErrorLogFilter (c , true )
2081+ if ! ok {
2082+ return
2083+ }
2084+ result , err := h .db .ListUsageLogsByTimeRangePaged (ctx , filter )
2085+ if err != nil {
2086+ writeInternalError (c , err )
2087+ return
2088+ }
2089+ c .JSON (http .StatusOK , result )
2090+ }
2091+
2092+ // GetOpsErrorSummary 获取运维错误日志概览
2093+ func (h * Handler ) GetOpsErrorSummary (c * gin.Context ) {
2094+ ctx , cancel := context .WithTimeout (c .Request .Context (), 10 * time .Second )
2095+ defer cancel ()
2096+
2097+ filter , ok := parseOpsErrorLogFilter (c , false )
2098+ if ! ok {
2099+ return
2100+ }
2101+ result , err := h .db .GetUsageErrorSummary (ctx , filter )
2102+ if err != nil {
2103+ writeInternalError (c , err )
2104+ return
2105+ }
2106+ c .JSON (http .StatusOK , result )
2107+ }
2108+
19732109// GetUsageLogs 获取使用日志
19742110func (h * Handler ) GetUsageLogs (c * gin.Context ) {
19752111 ctx , cancel := context .WithTimeout (c .Request .Context (), 15 * time .Second )
0 commit comments