This commit is contained in:
oneao committed 2025-08-25 17:22:54 +08:00
1 parent 76a6c81af2
commit b19d083031
68 files changed
+3889 -343

No files matched your search

@@ -0,0 +1,80 @@
package middleware
import (
"base-framework/pkg/config"
"base-framework/pkg/reponse"
"base-framework/pkg/router"
"base-framework/pkg/utils/jwt"
"errors"
"strings"
)
func Auth() router.HandlerFunc {
return func(c *router.Context) {
tokenHeader := c.Header("Authorization")
userIdHeader := c.Header("userId")
orgIDHeader := c.Header("orgId")
// 缺少登录信息
if tokenHeader == "" || userIdHeader == "" || orgIDHeader == "" {
reponse.Error(c).Code(reponse.CodeNoLogin).Send()
c.Abort()
return
}
// Bearer token 格式校验
parts := strings.Fields(tokenHeader)
if len(parts) != 2 || strings.ToLower(parts[0]) != "bearer" {
reponse.Error(c).Code(reponse.CodeInvalidToken).Send()
c.Abort()
return
}
// 解析 token
tokenStr := parts[1]
claims, err := jwt.VerifyToken(tokenStr)
if err != nil {
switch {
case errors.Is(err, jwt.ErrTokenExpired):
reponse.Error(c).Code(reponse.CodeLoginExpired).Send()
default:
reponse.Error(c).Code(reponse.CodeInvalidToken).Send()
}
c.Abort()
return
}
// user_id 校验
if claims.UserID != userIdHeader {
reponse.Error(c).Code(reponse.CodeInvalidToken).Send()
c.Abort()
return
}
// org_id 校验
if claims.OrgID != orgIDHeader {
reponse.Error(c).Code(reponse.CodeInvalidToken).Send()
c.Abort()
return
}
// 检查是否有该机构号
hasOrg := false
for k := range config.GetDBConfigs() {
if k == orgIDHeader {
hasOrg = true
break
}
}
if !hasOrg {
reponse.Error(c).Code(reponse.CodeInvalidOrgCode).Send()
return
}
c.Set("orgId", orgIDHeader)
c.Next()
}
}
@@ -0,0 +1,177 @@
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
}
}
@@ -0,0 +1,16 @@
package middleware
import (
"base-framework/pkg/logx"
"base-framework/pkg/router"
"base-framework/pkg/routinex"
"base-framework/pkg/utils/uid"
)
func Logger() router.HandlerFunc {
return func(c *router.Context) {
routinex.Set(logx.TrackID, uid.NextID())
c.Next()
}
}
@@ -0,0 +1,24 @@
package middleware
import (
"base-framework/pkg/reponse"
"base-framework/pkg/router"
"log"
"runtime/debug"
)
func Recovery() router.HandlerFunc {
return func(c *router.Context) {
defer func() {
if err := recover(); err != nil {
// 打印 panic 信息和堆栈
log.Printf("[Recovery] panic recovered: %v\n%s", err, debug.Stack())
reponse.Error(c).Send()
}
}()
// 调用后续 handler
c.Next()
}
}
@@ -0,0 +1,13 @@
package middleware
import (
"base-framework/pkg/router"
"base-framework/pkg/routinex"
)
func Routinex() router.HandlerFunc {
return func(c *router.Context) {
defer routinex.Clear()
c.Next()
}
}