u
This commit is contained in:
1 parent
d60be4a75b
commit
be8eaae7c5
24 files changed
+595
-232
No files matched your search
@@ -60,6 +60,7 @@ func loginDefault(c *router.Context) error {
|
||||
// 获取请求体并校验必填字段
|
||||
bodyData, err := c.GetBodyWithRequired("account", "password")
|
||||
if err != nil {
|
||||
c.AddError(err)
|
||||
return err
|
||||
}
|
||||
|
||||
@@ -73,6 +74,7 @@ func loginDefault(c *router.Context) error {
|
||||
)
|
||||
user, err := db.FindOne("user_info", userQuery)
|
||||
if err != nil {
|
||||
c.AddError(err)
|
||||
return response.Success(c).Message("查询用户失败,请稍后重试").Send()
|
||||
}
|
||||
|
||||
@@ -124,7 +126,7 @@ func registerDefault(c *router.Context) error {
|
||||
|
||||
existingUsers, err := db.Find("user_info", accountQuery)
|
||||
if err != nil {
|
||||
return err
|
||||
return c.AddError(err)
|
||||
}
|
||||
|
||||
if len(existingUsers) > 0 {
|
||||
@@ -141,7 +143,7 @@ func registerDefault(c *router.Context) error {
|
||||
// 插入新用户
|
||||
insertedRows, err := db.InsertOne("user_info", newUserData)
|
||||
if err != nil {
|
||||
return err
|
||||
return c.AddError(err)
|
||||
}
|
||||
if insertedRows != 1 {
|
||||
return response.Fail(c).Message("注册失败").Send()
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"base-go-v2/internal/errs"
|
||||
"base-go-v2/internal/logx"
|
||||
"base-go-v2/internal/reponse"
|
||||
"base-go-v2/internal/router"
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"runtime"
|
||||
"strings"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 仅初始化一次的项目信息
|
||||
var (
|
||||
initOnce sync.Once
|
||||
projectBase string // 例:base-farmework
|
||||
projectAbsPrefix string // 例:/abs/path/to/base-farmework/
|
||||
projectAbsPrefixLower string // 小写版,用于不区分大小写前缀比较(Windows 安全)
|
||||
)
|
||||
|
||||
func ErrorMiddleware() router.HandlerFunc {
|
||||
// 可扩展的业务错误类型列表
|
||||
businessErrors := []interface{}{
|
||||
(*errs.ValidationError)(nil),
|
||||
// (*errs.BusinessError)(nil),
|
||||
// (*errs.AuthError)(nil),
|
||||
}
|
||||
initOnce.Do(initProjectInfo)
|
||||
|
||||
return func(c *router.Context) error {
|
||||
err := c.Next()
|
||||
@@ -23,23 +28,132 @@ func ErrorMiddleware() router.HandlerFunc {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 判断是否属于业务错误
|
||||
for _, be := range businessErrors {
|
||||
// be 是 nil 指针类型
|
||||
if errors.As(err, &be) {
|
||||
// 业务错误,不记录日志
|
||||
return response.Fail(c).Message(err.Error()).Send()
|
||||
}
|
||||
errs := c.Errors()
|
||||
if len(errs) == 0 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 系统错误,记录日志
|
||||
stack := string(debug.Stack())
|
||||
lastErr := errs[len(errs)-1]
|
||||
stack := lastErr.StackTrace()
|
||||
location := extractBusinessLocation(stack)
|
||||
|
||||
logx.Logger.Error("请求错误",
|
||||
logx.String("path", string(c.RequestCtx.Path())),
|
||||
logx.String("method", string(c.RequestCtx.Method())),
|
||||
logx.String("location", location),
|
||||
logx.String("error", err.Error()),
|
||||
logx.String("stack", stack),
|
||||
)
|
||||
|
||||
return response.Fail(c).Message("服务器内部错误").Send()
|
||||
}
|
||||
}
|
||||
|
||||
// 仅在注册时执行:确定 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
|
||||
}
|
||||
}
|
||||
@@ -60,7 +60,7 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([
|
||||
|
||||
query := fmt.Sprintf("SELECT * FROM %s WHERE %s", table, strings.Join(whereParts, " AND "))
|
||||
|
||||
order := "b_id DESC"
|
||||
order := "id DESC"
|
||||
if len(orderBy) > 0 && orderBy[0] != "" {
|
||||
order = orderBy[0]
|
||||
}
|
||||
@@ -79,7 +79,7 @@ func Find(table string, conditions map[string]interface{}, orderBy ...string) ([
|
||||
func FindAll(table string, orderBy ...string) ([]mapx.M, error) {
|
||||
query := fmt.Sprintf("SELECT * FROM %s", table)
|
||||
|
||||
order := "b_id DESC"
|
||||
order := "id DESC"
|
||||
if len(orderBy) > 0 && orderBy[0] != "" {
|
||||
order = orderBy[0]
|
||||
}
|
||||
@@ -111,11 +111,11 @@ func FindOne(table string, conditions map[string]interface{}, orderBy ...string)
|
||||
|
||||
query := fmt.Sprintf("SELECT * FROM %s WHERE %s", table, strings.Join(whereParts, " AND "))
|
||||
|
||||
order := "b_id DESC"
|
||||
order := "id DESC"
|
||||
if len(orderBy) > 0 && orderBy[0] != "" {
|
||||
order = orderBy[0]
|
||||
}
|
||||
query += " ORDER BY " + order + " LIMIT 1" // 只取一条记录
|
||||
query += " ORDER BY " + order + " LIMIT 1"
|
||||
|
||||
row := mapx.New()
|
||||
err := DB.Get(&row, query, values...)
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package errs
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"runtime/debug"
|
||||
)
|
||||
|
||||
// StackError 保存原始错误和堆栈
|
||||
type StackError struct {
|
||||
err error
|
||||
stack string
|
||||
}
|
||||
|
||||
// 实现 error 接口
|
||||
func (s *StackError) Error() string {
|
||||
if s.err == nil {
|
||||
return ""
|
||||
}
|
||||
return s.err.Error()
|
||||
}
|
||||
|
||||
// StackTrace 返回堆栈信息
|
||||
func (s *StackError) StackTrace() string {
|
||||
return s.stack
|
||||
}
|
||||
|
||||
// WrapWithStack 给错误附加堆栈,如果已经是 StackError 则直接返回
|
||||
func WrapWithStack(err error) *StackError {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
var se *StackError
|
||||
if errors.As(err, &se) {
|
||||
return se
|
||||
}
|
||||
|
||||
return &StackError{
|
||||
err: err,
|
||||
stack: string(debug.Stack()),
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
package router
|
||||
|
||||
import (
|
||||
"base-go-v2/internal/errs"
|
||||
"base-go-v2/internal/utils/mapx"
|
||||
"base-go-v2/internal/utils/validate"
|
||||
"encoding/json"
|
||||
@@ -154,3 +155,35 @@ func (c *Context) Text(statusCode int, msg string) error {
|
||||
c.Abort()
|
||||
return nil
|
||||
}
|
||||
|
||||
// AddError 记录错误,自动包装 StackError
|
||||
func (c *Context) AddError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
if c.keys == nil {
|
||||
c.keys = make(map[string]interface{})
|
||||
}
|
||||
|
||||
if _, exists := c.keys["errors"]; !exists {
|
||||
c.keys["errors"] = []*errs.StackError{}
|
||||
}
|
||||
|
||||
// 统一生成堆栈
|
||||
se := errs.WrapWithStack(err)
|
||||
c.keys["errors"] = append(c.keys["errors"].([]*errs.StackError), se)
|
||||
|
||||
return err
|
||||
}
|
||||
|
||||
// Errors 返回 []*utils.StackError
|
||||
func (c *Context) Errors() []*errs.StackError {
|
||||
if c.keys == nil {
|
||||
return nil
|
||||
}
|
||||
if err, exists := c.keys["errors"]; exists {
|
||||
return err.([]*errs.StackError)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@@ -74,6 +74,7 @@ type middlewareEntry struct {
|
||||
|
||||
// 判断路径是否被排除
|
||||
func (m *middlewareEntry) IsExcluded(path string) bool {
|
||||
path = strings.TrimSuffix(path, "/") // 去掉尾部斜杠
|
||||
if m.excludePaths != nil {
|
||||
if _, ok := m.excludePaths[path]; ok {
|
||||
return true
|
||||
@@ -123,23 +124,36 @@ func (r *Router) Use(handler HandlerFunc) *MiddlewareHandle {
|
||||
}
|
||||
|
||||
// ExcludePaths 支持 /** 前缀匹配
|
||||
func (mh *MiddlewareHandle) ExcludePaths(paths ...string) *Router {
|
||||
excludeMap := make(map[string]struct{}, 0)
|
||||
// ExcludePaths 支持 /** 前缀匹配,并自动带上 basePath
|
||||
func (mh *MiddlewareHandle) ExcludePaths(paths ...string) *MiddlewareHandle {
|
||||
excludeMap := make(map[string]struct{})
|
||||
excludePrefix := make([]string, 0)
|
||||
|
||||
basePath := strings.TrimSuffix(mh.router.basePath, "/") // 去掉尾部斜杠
|
||||
|
||||
for _, p := range paths {
|
||||
if strings.HasSuffix(p, "/**") {
|
||||
base := strings.TrimSuffix(p, "/**")
|
||||
fullPath := p
|
||||
if !strings.HasPrefix(p, "/") {
|
||||
fullPath = "/" + p
|
||||
}
|
||||
|
||||
// 自动加上 basePath
|
||||
if basePath != "/" {
|
||||
fullPath = basePath + fullPath
|
||||
}
|
||||
|
||||
if strings.HasSuffix(fullPath, "/**") {
|
||||
base := strings.TrimSuffix(fullPath, "/**")
|
||||
excludePrefix = append(excludePrefix, base)
|
||||
} else {
|
||||
excludeMap[p] = struct{}{}
|
||||
excludeMap[fullPath] = struct{}{}
|
||||
}
|
||||
}
|
||||
|
||||
mh.router.middleware[mh.entryIdx].excludePaths = excludeMap
|
||||
mh.router.middleware[mh.entryIdx].excludePrefix = excludePrefix
|
||||
|
||||
return mh.router
|
||||
return mh
|
||||
}
|
||||
|
||||
// Group 创建子路由组
|
||||
|
||||
Reference in new issue
Block a user