Files
workspace/code/go-project/base-farmework/pkg/middleware/error.go
T
2025-08-16 17:29:46 +08:00

178 lines
4.2 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 middleware
import (
"base-framework/pkg/logx"
"base-framework/pkg/reponse"
"base-framework/pkg/router"
"base-framework/pkg/routinex"
"base-framework/pkg/utils/strutil"
"os"
"path/filepath"
"runtime"
"strings"
"sync"
)
// 仅初始化一次的项目信息
var (
initOnce sync.Once
projectBase string // 例:base-farmework
projectAbsPrefix string // 例:/abs/path/to/base-farmework/
projectAbsPrefixLower string // 小写版,用于不区分大小写前缀比较(Windows 安全)
)
// Error 中间件:捕获 c.Errors() 并返回统一的 ErrorInfo
func Error() router.HandlerFunc {
// 注册时预热(只做一次)
initOnce.Do(initProjectInfo)
return func(c *router.Context) {
c.Next()
errs := c.Errors()
if len(errs) == 0 {
return
}
lastErr := errs[len(errs)-1]
stack := lastErr.StackTrace()
location := extractBusinessLocation(stack)
trackId := routinex.Get(logx.TrackID)
var trackIDStr string
if trackId != nil {
trackIDStr = strutil.ToString(trackId) // 用你之前写的工具类
}
var info = reponse.ErrorInfo{
Method: c.Request.Method,
Path: c.Request.URL.Path,
Message: lastErr.Error(),
//Stack: stack,
Location: location,
TrackId: trackIDStr,
}
logx.Logger.Error(
"系统异常",
logx.String("trackID", trackIDStr),
logx.String("method", c.Request.Method),
logx.String("path", c.Request.URL.Path),
logx.String("location", location),
logx.String("stack", stack),
)
reponse.Error(c).ErrorInfo(info).Send()
c.Abort()
}
}
// 仅在注册时执行:确定 go.mod 根目录并缓存前缀
func initProjectInfo() {
// 以当前源文件为起点向上找 go.mod
_, file, _, _ := runtime.Caller(0)
if root, ok := findGoModRoot(file); ok {
projectBase = filepath.Base(root)
p := filepath.ToSlash(root)
if !strings.HasSuffix(p, "/") {
p += "/"
}
projectAbsPrefix = p
projectAbsPrefixLower = strings.ToLower(p)
}
}
// extractBusinessLocation:获取 AddError 后的业务 Handler 文件行(仅返回 <project>/<subpath>.go:<line>)
func extractBusinessLocation(stack string) string {
lines := strings.Split(stack, "\n")
foundAddError := false
countGo := 0
for _, raw := range lines {
line := strings.TrimSpace(raw)
if strings.Contains(line, "AddError") {
foundAddError = true
continue
}
if !foundAddError {
continue
}
if strings.Contains(line, ".go:") {
countGo++
if countGo == 2 {
// 去掉 " +0x..." 偏移
if i := strings.Index(line, " +"); i != -1 {
line = line[:i]
}
return trimToProject(line)
}
}
}
return ""
}
// 将绝对路径裁剪为 "<project>/<subpath>.go:<line>"
func trimToProject(goLine string) string {
// 统一斜杠
line := strings.ReplaceAll(goLine, "\\", "/")
// 用最后一个冒号分离(避免 Windows 盘符)
idx := strings.LastIndex(line, ":")
if idx == -1 || idx+1 >= len(line) {
return line
}
filePath, lineNo := line[:idx], line[idx+1:]
// 优先:前缀裁剪(O(1)),比 filepath.Rel 更快
if projectAbsPrefix != "" {
fp := filepath.ToSlash(filePath)
fpl := strings.ToLower(fp)
if strings.HasPrefix(fpl, projectAbsPrefixLower) {
rel := fp[len(projectAbsPrefix):]
return projectBase + "/" + rel + ":" + lineNo
}
}
// 其次:internal/cmd/pkg 作为锚点
segs := strings.Split(filePath, "/")
for i, s := range segs {
if s == "internal" || s == "cmd" || s == "pkg" {
if i > 0 {
return strings.Join(segs[i-1:], "/") + ":" + lineNo
}
}
}
// 启发式:目录名包含 '-' 或 '_',当作仓库名
for i, s := range segs {
if strings.ContainsAny(s, "-_") {
return strings.Join(segs[i:], "/") + ":" + lineNo
}
}
// 兜底:末尾 4 级
start := len(segs) - 4
if start < 0 {
start = 0
}
return strings.Join(segs[start:], "/") + ":" + lineNo
}
// 从给定文件路径向上查找包含 go.mod 的目录(仅注册时调用一次)
func findGoModRoot(filePath string) (string, bool) {
dir := filepath.Clean(filepath.Dir(filepath.FromSlash(filePath)))
for {
if _, err := os.Stat(filepath.Join(dir, "go.mod")); err == nil {
return dir, true
}
parent := filepath.Dir(dir)
if parent == dir {
return "", false
}
dir = parent
}
}