Files
backend-go/middleware/auth.go
T
Frudrax Cheng 0d82260fd9 Add aftersales work order feature
- AftersalesOrder model with state machine (created/pending_confirmation/closed/rejected)
- Public scan-to-confirm flow with phone last-4 verification and rate limiting
- Technician role and middleware for ownership-scoped operations
- QR code generation pointing to /aftersales/{serialNumber}
- Admin overrides: reassign, force-close, delete

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-26 10:39:49 +08:00

136 lines
3.1 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
package middleware
import (
"errors"
"net/http"
"strings"
"time"
"git.beifan.cn/trace-system/backend-go/config"
"git.beifan.cn/trace-system/backend-go/database"
"git.beifan.cn/trace-system/backend-go/models"
"github.com/gin-gonic/gin"
"github.com/golang-jwt/jwt/v5"
)
// JWTAuthMiddleware JWT 认证中间件
func JWTAuthMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
authHeader := c.GetHeader("Authorization")
if authHeader == "" {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "缺少授权头部",
})
return
}
parts := strings.SplitN(authHeader, " ", 2)
if !(len(parts) == 2 && parts[0] == "Bearer") {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "授权头部格式错误",
})
return
}
tokenStr := parts[1]
// 解析 JWT token
token, err := jwt.Parse(tokenStr, func(token *jwt.Token) (interface{}, error) {
if _, ok := token.Method.(*jwt.SigningMethodHMAC); !ok {
return nil, errors.New("不支持的签名方法")
}
cfg := config.GetAppConfig()
return []byte(cfg.JWT.Secret), nil
})
if err != nil || !token.Valid {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "无效的访问令牌",
})
return
}
// 验证 claims
if claims, ok := token.Claims.(jwt.MapClaims); ok && token.Valid {
// 检查过期时间
if exp, ok := claims["exp"].(float64); ok {
if time.Now().Unix() > int64(exp) {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "令牌已过期",
})
return
}
}
// 获取用户 ID
if userId, ok := claims["userId"].(float64); ok {
// 验证用户是否存在
var user models.User
result := database.DB.First(&user, uint(userId))
if result.Error != nil {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "用户不存在",
})
return
}
// 将用户信息存储到上下文
c.Set("user", user)
} else {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "令牌无效",
})
return
}
}
c.Next()
}
}
// AdminMiddleware 管理员权限中间件
func AdminMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
user, exists := c.Get("user")
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "未认证",
})
return
}
userModel := user.(models.User)
if userModel.Role != "admin" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"message": "无权限访问此资源",
})
return
}
c.Next()
}
}
// TechnicianMiddleware 技术员权限中间件(放行 admin 和 technician
func TechnicianMiddleware() gin.HandlerFunc {
return func(c *gin.Context) {
user, exists := c.Get("user")
if !exists {
c.AbortWithStatusJSON(http.StatusUnauthorized, gin.H{
"message": "未认证",
})
return
}
userModel := user.(models.User)
if userModel.Role != "admin" && userModel.Role != "technician" {
c.AbortWithStatusJSON(http.StatusForbidden, gin.H{
"message": "无权限访问此资源",
})
return
}
c.Next()
}
}