package handlers import ( "errors" "io" "log" "net" "net/http" "strings" "syscall" "github.com/gin-gonic/gin" "github.com/jackc/pgx/v5/pgconn" "github.com/jackc/puddle/v2" "booklib/internal/auth" "booklib/internal/config" "booklib/internal/redispkg" "booklib/internal/scanner" "booklib/internal/store" ) type H struct { cfg *config.Config st *store.Store rdb *redispkg.R sc *scanner.Scanner } func New(cfg *config.Config, st *store.Store, rdb *redispkg.R, sc *scanner.Scanner) *H { return &H{cfg: cfg, st: st, rdb: rdb, sc: sc} } func err(c *gin.Context, status int, code, msg string) { c.AbortWithStatusJSON(status, gin.H{"error": gin.H{"code": code, "message": msg}}) } // dbErr 统一处理 store 层失败:记日志;连接类错误 503(Service Unavailable),其余 500 // 注:brief 里的 pgxpool.ErrClosedPool 在 pgx v5 不存在,实际由 puddle 原样透出,用它替代; // PG 停机时池内连接先收到 SQLSTATE 57P01(administrator shutdown),故把 08xx/57Pxx 也归为 503 func dbErr(c *gin.Context, e error) { log.Printf("db: %v", e) status, code := http.StatusInternalServerError, "internal" var pgErr *pgconn.PgError connClass := errors.As(e, &pgErr) && (strings.HasPrefix(pgErr.Code, "08") || strings.HasPrefix(pgErr.Code, "57P")) if connClass || errors.Is(e, syscall.ECONNREFUSED) || errors.Is(e, io.ErrUnexpectedEOF) || errors.Is(e, net.ErrClosed) || errors.Is(e, puddle.ErrClosedPool) { status, code = http.StatusServiceUnavailable, "unavailable" } err(c, status, code, "db error") } func (h *H) AuthMw() gin.HandlerFunc { return func(c *gin.Context) { hdr := c.GetHeader("Authorization") tok, ok := strings.CutPrefix(hdr, "Bearer ") if !ok { err(c, http.StatusUnauthorized, "unauthorized", "missing bearer token") return } cl, perr := auth.Parse(h.cfg.JWTSecret, tok) if perr != nil { err(c, http.StatusUnauthorized, "unauthorized", "invalid token") return } c.Set("uid", cl.UID) c.Set("role", cl.Role) c.Next() } } func (h *H) AdminOnly() gin.HandlerFunc { return func(c *gin.Context) { if c.GetString("role") != "admin" { err(c, http.StatusForbidden, "forbidden", "admin only") return } c.Next() } } func uid(c *gin.Context) int64 { return c.GetInt64("uid") } func isAdmin(c *gin.Context) bool { return c.GetString("role") == "admin" }