first commit

This commit is contained in:
2026-10-25 20:27:59 +08:00
commit 332b7dcd08
27 changed files with 1708 additions and 0 deletions
+43
View File
@@ -0,0 +1,43 @@
package middleware
import (
"strings"
"github.com/gin-gonic/gin"
"portal-mini-go/internal/pkg/errors"
"portal-mini-go/internal/pkg/token"
)
const adminIDCtxKey = "admin_id"
// AdminAuth 校验后台请求的 Bearer JWT,校验通过后将 admin_id 注入 context。
func AdminAuth(jwtSecret string) gin.HandlerFunc {
return func(c *gin.Context) {
auth := c.GetHeader("Authorization")
const prefix = "Bearer "
if !strings.HasPrefix(auth, prefix) {
c.Error(errors.ErrUnauthorized)
c.Abort()
return
}
claims, err := token.Parse(jwtSecret, strings.TrimPrefix(auth, prefix))
if err != nil {
c.Error(errors.ErrUnauthorized)
c.Abort()
return
}
c.Set(adminIDCtxKey, claims.AdminID)
c.Next()
}
}
// AdminID 从 gin.Context 取出已认证的后台管理员 ID。
func AdminID(c *gin.Context) string {
if v, ok := c.Get(adminIDCtxKey); ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
+60
View File
@@ -0,0 +1,60 @@
// Package middleware 提供 HTTP 中间件。
package middleware
import (
"github.com/gin-gonic/gin"
"portal-mini-go/internal/pkg/errors"
"portal-mini-go/internal/repository/miniapp"
)
const (
appIDCtxKey = "app_id"
miniIDCtxKey = "mini_id"
)
// headerAppID 是小程序在请求头中携带的微信 AppID 字段名。
const HeaderAppID = "X-App-ID"
// MiniAppAuth 校验请求头中的 app_id 是否对应一个已启用且未删除的被纳管小程序,
// 通过则将 app_id 与小程序的数据库主键注入 context,供后续 handler/service 使用。
// 用户身份最终由 (mini_id, open_id) 唯一确定。
func MiniAppAuth(repo *miniapp.Repository) gin.HandlerFunc {
return func(c *gin.Context) {
appID := c.GetHeader(HeaderAppID)
if appID == "" {
c.Error(errors.ErrBadRequest)
c.Abort()
return
}
mp, err := repo.GetActiveByAppID(c.Request.Context(), appID)
if err != nil {
c.Error(errors.ErrNotFound)
c.Abort()
return
}
c.Set(appIDCtxKey, appID)
c.Set(miniIDCtxKey, mp.ID)
c.Next()
}
}
// AppID 从 gin.Context 取出已校验的 appID。
func AppID(c *gin.Context) string {
if v, ok := c.Get(appIDCtxKey); ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// MiniID 从 gin.Context 取出已校验的小程序数据库主键。
func MiniID(c *gin.Context) uint64 {
if v, ok := c.Get(miniIDCtxKey); ok {
if id, ok := v.(uint64); ok {
return id
}
}
return 0
}
+22
View File
@@ -0,0 +1,22 @@
package middleware
import (
"github.com/gin-gonic/gin"
"portal-mini-go/internal/pkg/response"
)
// ErrorHandler 统一把 handler 通过 c.Error(err) 挂载的错误转换为响应。
// 规范要求:handler 内不直接散落 c.JSON 处理错误,集中在此转换。
func ErrorHandler() gin.HandlerFunc {
return func(c *gin.Context) {
c.Next()
if len(c.Errors) == 0 {
return
}
// 取最后一个(最外层)错误转换。
last := c.Errors[len(c.Errors)-1].Err
response.Error(c, last)
c.Abort()
}
}
+31
View File
@@ -0,0 +1,31 @@
package middleware
import (
"log/slog"
"net/http"
"runtime/debug"
"github.com/gin-gonic/gin"
)
// Recovery 捕获 handler 中的 panic,记录结构化日志并以 500 响应,
// 符合规范:recover 仅允许用于框架中间件。
func Recovery() gin.HandlerFunc {
return func(c *gin.Context) {
defer func() {
if r := recover(); r != nil {
slog.Error("panic recovered",
slog.String("path", c.Request.URL.Path),
slog.Any("err", r),
slog.String("stack", string(debug.Stack())),
)
c.AbortWithStatusJSON(http.StatusInternalServerError, gin.H{
"code": 50000,
"msg": "internal server error",
"data": nil,
})
}
}()
c.Next()
}
}
+53
View File
@@ -0,0 +1,53 @@
package middleware
import (
"crypto/rand"
"encoding/hex"
"log/slog"
"github.com/gin-gonic/gin"
)
const traceIDKey = "trace_id"
// Trace 透传或生成 trace_id,并注入可携带 trace_id 的 slog 日志句柄,
// 供后续日志统一携带链路标识。
func Trace() gin.HandlerFunc {
return func(c *gin.Context) {
tid := c.GetHeader("X-Trace-ID")
if tid == "" {
tid = newTraceID()
}
c.Set(traceIDKey, tid)
c.Set("slog_ctx", slog.With(slog.String("trace_id", tid)))
c.Next()
}
}
func newTraceID() string {
b := make([]byte, 16)
if _, err := rand.Read(b); err != nil {
return "unknown"
}
return hex.EncodeToString(b)
}
// TraceID 从 gin.Context 取出 trace_id。
func TraceID(c *gin.Context) string {
if v, ok := c.Get(traceIDKey); ok {
if s, ok := v.(string); ok {
return s
}
}
return ""
}
// SlogCtx 返回携带 trace_id 的 slog 日志句柄(无则回退默认)。
func SlogCtx(c *gin.Context) *slog.Logger {
if v, ok := c.Get("slog_ctx"); ok {
if l, ok := v.(*slog.Logger); ok {
return l
}
}
return slog.Default()
}