EasyAudioEncode/internal/web/api/api.go
2025-12-31 11:29:58 +08:00

172 lines
4.8 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package api
import (
"easyaudioencode/internal/web/api/static"
"expvar"
statics "github.com/gin-contrib/static"
"log/slog"
"net/http"
"os"
"path/filepath"
"runtime"
"runtime/debug"
"sort"
"strings"
"time"
"easyaudioencode/domain/version/versionapi"
localweb "easyaudioencode/pkg/web"
"git.lnton.com/lnton/pkg/web"
"github.com/gin-gonic/gin"
)
var startRuntime = time.Now()
func setupRouter(r *gin.Engine, uc *Usecase) {
r.Use(
// 格式化输出到控制台,然后记录到日志
// 此处不做 recover底层 http.server 也会 recover但不会输出方便查看的格式
gin.CustomRecovery(func(c *gin.Context, err any) {
slog.Error("panic", "err", err, "stack", string(debug.Stack()))
c.AbortWithStatus(http.StatusInternalServerError)
}),
web.Metrics(),
web.Logger(),
// debug 环境中配合 debug 日志级别,记录请求体与响应体
web.LoggerWithBody(web.DefaultBodyLimit, func(_ *gin.Context) bool {
// true: 表示忽略记录日志
// !debug 表示非调试环境不记录
return !uc.Conf.Debug
}),
)
go web.CountGoroutines(10*time.Minute, 20)
auth := localweb.AuthMiddleware(uc.Conf.Server.HTTP.JwtSecret, uc.Conf.Plugin.HttpAPI+"/extensions/auth", "")
r.Any("/health", web.WrapH(uc.getHealth))
r.GET("/app/metrics/api", web.WrapH(uc.getMetricsAPI))
//快照
dir, _ := os.Getwd()
uploadsDir := filepath.Join(dir, "uploads")
r.Use(statics.Serve("/uploads", statics.LocalFile(uploadsDir, true)))
versionapi.Register(r, uc.Version, auth)
RegisterHostAPI(r, uc)
RegisterAudioEncode(r, uc.AudioEncodeAPI)
r.NoRoute(func(ctx *gin.Context) {
p := ctx.Request.URL.Path
if strings.HasPrefix(p, "/web/") {
q := ctx.Request.URL.RawQuery
target := "/web/"
if q != "" {
target = target + "?" + q
}
ctx.Redirect(http.StatusTemporaryRedirect, target)
return
}
if strings.HasPrefix(p, "/uploads/") {
q := ctx.Request.URL.RawQuery
target := "/uploads/"
if q != "" {
target = target + "?" + q
}
ctx.Redirect(http.StatusTemporaryRedirect, target)
return
}
if strings.HasPrefix(p, "/extensions/easyaudioencode") {
// 改为前缀替换并在当前请求内重新分发,而不是重定向
newPath := strings.TrimPrefix(p, "/extensions/easyaudioencode")
ctx.Request.URL.Path = newPath
r.HandleContext(ctx)
return
}
ctx.AbortWithStatus(http.StatusNotFound)
})
// 直接返回静态文件系统中的入口页
r.StaticFS("/web/", static.FileSystem())
}
type getHealthOutput struct {
Version string `json:"version"`
StartAt time.Time `json:"start_at"`
GitBranch string `json:"git_branch"`
GitHash string `json:"git_hash"`
}
func (uc *Usecase) getHealth(_ *gin.Context, _ *struct{}) (getHealthOutput, error) {
return getHealthOutput{
Version: uc.Conf.BuildVersion,
GitBranch: strings.Trim(expvar.Get("git_branch").String(), `"`),
GitHash: strings.Trim(expvar.Get("git_hash").String(), `"`),
StartAt: startRuntime,
}, nil
}
type getMetricsAPIOutput struct {
RealTimeRequests int64 `json:"real_time_requests"` // 实时请求数
TotalRequests int64 `json:"total_requests"` // 总请求数
TotalResponses int64 `json:"total_responses"` // 总响应数
RequestTop []KV `json:"request_top"` // 请求TOP
StatusCodeTop []KV `json:"status_code_top"` // 状态码TOP
Goroutines any `json:"goroutines"` // 协程数量
NumGC uint32 `json:"num_gc"` // gc 次数
SysAlloc uint64 `json:"sys_alloc"` // 内存占用
StartAt string `json:"start_at"` // 运行时间
}
func (uc *Usecase) getMetricsAPI(_ *gin.Context, _ *struct{}) (*getMetricsAPIOutput, error) {
req := expvar.Get("request").(*expvar.Int).Value()
reqs := expvar.Get("requests").(*expvar.Int).Value()
resps := expvar.Get("responses").(*expvar.Int).Value()
urls := expvar.Get(`requestURLs`).(*expvar.Map)
status := expvar.Get(`statusCodes`).(*expvar.Map)
u := sortExpvarMap(urls, 15)
s := sortExpvarMap(status, 15)
g := expvar.Get("goroutine_num").(expvar.Func)
var stats runtime.MemStats
runtime.ReadMemStats(&stats)
return &getMetricsAPIOutput{
RealTimeRequests: req,
TotalRequests: reqs,
TotalResponses: resps,
RequestTop: u,
StatusCodeTop: s,
Goroutines: g(),
NumGC: stats.NumGC,
SysAlloc: stats.Sys,
StartAt: startRuntime.Format(time.DateTime),
}, nil
}
type KV struct {
Key string
Value int64
}
func sortExpvarMap(data *expvar.Map, top int) []KV {
kvs := make([]KV, 0, 8)
data.Do(func(kv expvar.KeyValue) {
kvs = append(kvs, KV{
Key: kv.Key,
Value: kv.Value.(*expvar.Int).Value(),
})
})
sort.Slice(kvs, func(i, j int) bool {
return kvs[i].Value > kvs[j].Value
})
idx := top
if l := len(kvs); l < top {
idx = len(kvs)
}
return kvs[:idx]
}