From f9077dafcfa4609f9b23faaf70a3884a090d40a4 Mon Sep 17 00:00:00 2001 From: laoboli <1293528695@qq.com> Date: Tue, 28 Apr 2026 19:06:42 +0800 Subject: [PATCH] feat: user migration --- main.go | 3 ++ middleware/jwt_for_user.go | 29 +++++++++++++-- middleware/user_permission.go | 55 +++++++++++++++++++++++++++++ models/user.go | 4 +++ models/user_permission_migration.go | 25 +++++++++++++ routes/routes.go | 2 +- 6 files changed, 114 insertions(+), 4 deletions(-) create mode 100644 middleware/user_permission.go create mode 100644 models/user_permission_migration.go diff --git a/main.go b/main.go index 58409cb..e89264f 100644 --- a/main.go +++ b/main.go @@ -33,6 +33,9 @@ func main() { &models.MqttGatewayStatusRecord{}, &models.MqttTrainingSessionRecord{}, ) + if err := models.BackfillLegacyUserPermissions(config.DB); err != nil { + log.Printf("legacy user permission backfill failed: %v", err) + } if err := mqtt.Start(config.DB, config.App.MQTT); err != nil { log.Printf("mqtt listener start failed: %v", err) diff --git a/middleware/jwt_for_user.go b/middleware/jwt_for_user.go index b8cc67b..e863c53 100644 --- a/middleware/jwt_for_user.go +++ b/middleware/jwt_for_user.go @@ -1,6 +1,8 @@ package middleware import ( + "hr_receiver/config" + "hr_receiver/models" "hr_receiver/util" "net/http" "strings" @@ -33,12 +35,33 @@ func JWTAuth() gin.HandlerFunc { return } + role := claims.Role + flavorType := claims.FlavorType + regionIDs := claims.RegionIDs + + if role == "" || flavorType == "" { + var user models.User + if err := config.DB.Preload("Regions").First(&user, claims.UserID).Error; err != nil { + c.JSON(http.StatusUnauthorized, gin.H{"error": "User not found"}) + c.Abort() + return + } + if !user.IsActive { + c.JSON(http.StatusForbidden, gin.H{"error": "User is disabled"}) + c.Abort() + return + } + role = user.Role + flavorType = user.FlavorType + regionIDs = user.RegionIDs() + } + // 将用户信息存入上下文 c.Set("userID", claims.UserID) c.Set("username", claims.Username) - c.Set("role", claims.Role) - c.Set("flavorType", claims.FlavorType) - c.Set("regionIDs", claims.RegionIDs) + c.Set("role", role) + c.Set("flavorType", flavorType) + c.Set("regionIDs", regionIDs) c.Next() } diff --git a/middleware/user_permission.go b/middleware/user_permission.go new file mode 100644 index 0000000..c0216ad --- /dev/null +++ b/middleware/user_permission.go @@ -0,0 +1,55 @@ +package middleware + +import ( + "hr_receiver/models" + "net/http" + + "github.com/gin-gonic/gin" +) + +func RequireStepTrainingAccess() gin.HandlerFunc { + return func(c *gin.Context) { + roleValue, exists := c.Get("role") + if !exists { + c.JSON(http.StatusForbidden, gin.H{"error": "missing user role"}) + c.Abort() + return + } + role, ok := roleValue.(models.UserRole) + if !ok { + c.JSON(http.StatusForbidden, gin.H{"error": "invalid user role"}) + c.Abort() + return + } + + if role != models.UserRoleSuperAdmin && + role != models.UserRoleRegionAdmin && + role != models.UserRoleOperator { + c.JSON(http.StatusForbidden, gin.H{"error": "insufficient role permissions"}) + c.Abort() + return + } + + flavorValue, exists := c.Get("flavorType") + if !exists { + c.JSON(http.StatusForbidden, gin.H{"error": "missing user flavor"}) + c.Abort() + return + } + flavorType, ok := flavorValue.(models.UserFlavorType) + if !ok { + c.JSON(http.StatusForbidden, gin.H{"error": "invalid user flavor"}) + c.Abort() + return + } + + if flavorType != models.UserFlavorAll && + flavorType != models.UserFlavorFlink { + c.JSON(http.StatusForbidden, gin.H{"error": "insufficient flavor permissions"}) + c.Abort() + return + } + + c.Next() + } +} diff --git a/models/user.go b/models/user.go index 459884d..14497e0 100644 --- a/models/user.go +++ b/models/user.go @@ -19,6 +19,7 @@ type UserFlavorType string const ( UserFlavorAll UserFlavorType = "all" + UserFlavorFlink UserFlavorType = "flink" UserFlavorFull UserFlavorType = "full" UserFlavorLight UserFlavorType = "light" UserFlavorHeartRate UserFlavorType = "heartrate" @@ -87,6 +88,9 @@ func (u *User) SupportsFlavor(flavor string) bool { if u.FlavorType == UserFlavorAll { return true } + if u.FlavorType == UserFlavorFlink { + return flavor == string(UserFlavorFlink) || flavor == string(UserFlavorFull) || flavor == string(UserFlavorLight) + } return string(u.FlavorType) == flavor } diff --git a/models/user_permission_migration.go b/models/user_permission_migration.go new file mode 100644 index 0000000..5d1c787 --- /dev/null +++ b/models/user_permission_migration.go @@ -0,0 +1,25 @@ +package models + +import "gorm.io/gorm" + +func BackfillLegacyUserPermissions(db *gorm.DB) error { + if err := db.Model(&User{}). + Where("role IS NULL OR role = '' OR role = ?", UserRoleViewer). + Update("role", UserRoleOperator).Error; err != nil { + return err + } + + if err := db.Model(&User{}). + Where("flavor_type IS NULL OR flavor_type = '' OR flavor_type = ?", UserFlavorAll). + Update("flavor_type", UserFlavorFlink).Error; err != nil { + return err + } + + if err := db.Model(&User{}). + Where("is_active IS NULL"). + Update("is_active", true).Error; err != nil { + return err + } + + return nil +} diff --git a/routes/routes.go b/routes/routes.go index 5b652ae..e574bed 100644 --- a/routes/routes.go +++ b/routes/routes.go @@ -23,7 +23,7 @@ func SetupRouter() *gin.Engine { records.POST("/analysis-by-ai", trainingController.AnalyzeByAI) // 可扩展其他路由:GET, PUT, DELETE等 } - steps := v1.Group("/step").Use(middleware.JWTAuth()) + steps := v1.Group("/step").Use(middleware.JWTAuth(), middleware.RequireStepTrainingAccess()) { steps.POST("", stepTrainController.CreateTrainingRecord) steps.GET("train-records", stepTrainController.GetTrainingRecords)