新增 Web 浏览端(Go+Vue 报表系统)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
73
web/backend/internal/middleware/auth.go
Normal file
73
web/backend/internal/middleware/auth.go
Normal file
@@ -0,0 +1,73 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"context"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
"trade/web/internal/auth"
|
||||
"trade/web/internal/store"
|
||||
)
|
||||
|
||||
type ctxKey string
|
||||
|
||||
const userKey ctxKey = "user"
|
||||
|
||||
type CtxUser struct {
|
||||
ID int64
|
||||
Username string
|
||||
Role string
|
||||
}
|
||||
|
||||
func FromContext(ctx context.Context) (CtxUser, bool) {
|
||||
u, ok := ctx.Value(userKey).(CtxUser)
|
||||
return u, ok
|
||||
}
|
||||
|
||||
// RequireUser 校验 Authorization Bearer JWT,通过后把 CtxUser 写入 context。
|
||||
// 同时校验数据库里的 disabled 状态,被禁用的账户即使持有 token 也会被拒。
|
||||
func RequireUser(mgr *auth.Manager, s *store.AuthStore) func(http.Handler) http.Handler {
|
||||
return func(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
tok := bearer(r)
|
||||
if tok == "" {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "missing token"})
|
||||
return
|
||||
}
|
||||
claims, err := mgr.Parse(tok)
|
||||
if err != nil {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "invalid token"})
|
||||
return
|
||||
}
|
||||
u, err := s.GetByID(claims.UserID)
|
||||
if err != nil || u.Disabled {
|
||||
writeJSON(w, http.StatusUnauthorized, map[string]string{"error": "account disabled or removed"})
|
||||
return
|
||||
}
|
||||
ctx := context.WithValue(r.Context(), userKey, CtxUser{
|
||||
ID: u.ID, Username: u.Username, Role: u.Role,
|
||||
})
|
||||
next.ServeHTTP(w, r.WithContext(ctx))
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func RequireAdmin(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
u, ok := FromContext(r.Context())
|
||||
if !ok || u.Role != store.RoleAdmin {
|
||||
writeJSON(w, http.StatusForbidden, map[string]string{"error": "admin only"})
|
||||
return
|
||||
}
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
func bearer(r *http.Request) string {
|
||||
h := r.Header.Get("Authorization")
|
||||
const p = "Bearer "
|
||||
if strings.HasPrefix(h, p) {
|
||||
return strings.TrimSpace(h[len(p):])
|
||||
}
|
||||
return ""
|
||||
}
|
||||
46
web/backend/internal/middleware/logger.go
Normal file
46
web/backend/internal/middleware/logger.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package middleware
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
"log"
|
||||
"net/http"
|
||||
"runtime/debug"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Logger(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
start := time.Now()
|
||||
rw := &statusRecorder{ResponseWriter: w, status: http.StatusOK}
|
||||
next.ServeHTTP(rw, r)
|
||||
log.Printf("%s %s %d %s", r.Method, r.URL.Path, rw.status, time.Since(start))
|
||||
})
|
||||
}
|
||||
|
||||
func Recover(next http.Handler) http.Handler {
|
||||
return http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
defer func() {
|
||||
if rec := recover(); rec != nil {
|
||||
log.Printf("[panic] %v\n%s", rec, debug.Stack())
|
||||
writeJSON(w, http.StatusInternalServerError, map[string]string{"error": "internal error"})
|
||||
}
|
||||
}()
|
||||
next.ServeHTTP(w, r)
|
||||
})
|
||||
}
|
||||
|
||||
type statusRecorder struct {
|
||||
http.ResponseWriter
|
||||
status int
|
||||
}
|
||||
|
||||
func (r *statusRecorder) WriteHeader(code int) {
|
||||
r.status = code
|
||||
r.ResponseWriter.WriteHeader(code)
|
||||
}
|
||||
|
||||
func writeJSON(w http.ResponseWriter, status int, body any) {
|
||||
w.Header().Set("Content-Type", "application/json")
|
||||
w.WriteHeader(status)
|
||||
_ = json.NewEncoder(w).Encode(body)
|
||||
}
|
||||
Reference in New Issue
Block a user