package developer import ( "log" "strings" "time" "unicode" "verification-platform-backend/internal/database" "verification-platform-backend/internal/model" "verification-platform-backend/pkg/response" "github.com/dop251/goja" "github.com/gin-gonic/gin" ) func normalizeKey(name string) string { key := strings.ToLower(strings.TrimSpace(name)) key = strings.Map(func(r rune) rune { if unicode.IsLetter(r) || unicode.IsDigit(r) || r == '_' || r == '-' { return r } return '_' }, key) return key } func validateDynamicCode(code string) error { vm := goja.New() vm.Set("params", vm.NewObject()) vm.Set("user", vm.NewObject()) vm.Set("app", vm.NewObject()) wrappedCode := "(function() { " + code + " })()" _, err := vm.RunString(wrappedCode) if err != nil { errStr := err.Error() if strings.Contains(errStr, "ReferenceError") || strings.Contains(errStr, "is not defined") { return nil } return err } return nil } func SetupDynamicRoutes(r *gin.RouterGroup) { dynamicCodes := r.Group("/dynamic-codes") { dynamicCodes.GET("", handleListDynamicCodes) dynamicCodes.GET("/:id", handleGetDynamicCode) dynamicCodes.POST("", handleCreateDynamicCode) dynamicCodes.PUT("/:id", handleUpdateDynamicCode) dynamicCodes.DELETE("/:id", handleDeleteDynamicCode) dynamicCodes.PUT("/:id/status", handleUpdateDynamicCodeStatus) dynamicCodes.DELETE("/batch", handleBatchDeleteDynamicCodes) dynamicCodes.PUT("/batch/status", handleBatchUpdateDynamicCodeStatus) } riskControl := r.Group("/risk-control") { riskControl.GET("/rules", handleGetRiskControlRules) riskControl.POST("/rules", handleCreateRiskControlRule) riskControl.PUT("/rules/:id", handleUpdateRiskControlRule) riskControl.DELETE("/rules/:id", handleDeleteRiskControlRule) riskControl.PUT("/rules/:id/status", handleUpdateRiskControlRuleStatus) riskControl.DELETE("/rules/batch", handleBatchDeleteRiskControlRules) riskControl.PUT("/rules/batch/status", handleBatchUpdateRiskControlRuleStatus) } } func handleListDynamicCodes(c *gin.Context) { userID := c.GetUint("user_id") var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } var dynamicCodes []model.DynamicCode if err := database.DB.Preload("Application").Preload("Creator").Where("application_id IN ?", applicationIDs).Find(&dynamicCodes).Error; err != nil { response.Error(c, 500, "获取动态代码列表失败") return } type DynamicCodeResponse struct { ID uint `json:"id"` Name string `json:"name"` Code string `json:"code"` Description string `json:"description"` ApplicationID uint `json:"application_id"` ApplicationName string `json:"application_name"` Enabled bool `json:"enabled"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Creator *struct { ID uint `json:"id"` Username string `json:"username"` } `json:"creator,omitempty"` } var result []DynamicCodeResponse for _, dc := range dynamicCodes { var creator *struct { ID uint `json:"id"` Username string `json:"username"` } if dc.UserID != nil && dc.Creator.ID != 0 { creator = &struct { ID uint `json:"id"` Username string `json:"username"` }{ ID: dc.Creator.ID, Username: dc.Creator.Username, } } result = append(result, DynamicCodeResponse{ ID: dc.ID, Name: dc.Name, Code: dc.Code, Description: dc.Description, ApplicationID: dc.ApplicationID, ApplicationName: dc.Application.Name, Enabled: dc.Status == "active", CreatedAt: dc.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: dc.UpdatedAt.Format("2006-01-02 15:04:05"), Creator: creator, }) } response.Success(c, result) } func handleGetDynamicCode(c *gin.Context) { userID := c.GetUint("user_id") id := c.Param("id") var dynamicCode model.DynamicCode if err := database.DB.Preload("Application").Preload("Creator").Where("id = ?", id).First(&dynamicCode).Error; err != nil { response.Error(c, 404, "动态代码不存在") return } var app model.Application if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil { response.Error(c, 403, "无权限访问此动态代码") return } type DynamicCodeResponse struct { ID uint `json:"id"` Name string `json:"name"` Code string `json:"code"` Description string `json:"description"` ApplicationID uint `json:"application_id"` ApplicationName string `json:"application_name"` Enabled bool `json:"enabled"` CreatedAt string `json:"created_at"` UpdatedAt string `json:"updated_at"` Creator *struct { ID uint `json:"id"` Username string `json:"username"` } `json:"creator,omitempty"` } var creator *struct { ID uint `json:"id"` Username string `json:"username"` } if dynamicCode.UserID != nil && dynamicCode.Creator.ID != 0 { creator = &struct { ID uint `json:"id"` Username string `json:"username"` }{ ID: dynamicCode.Creator.ID, Username: dynamicCode.Creator.Username, } } result := DynamicCodeResponse{ ID: dynamicCode.ID, Name: dynamicCode.Name, Code: dynamicCode.Code, Description: dynamicCode.Description, ApplicationID: dynamicCode.ApplicationID, ApplicationName: dynamicCode.Application.Name, Enabled: dynamicCode.Status == "active", CreatedAt: dynamicCode.CreatedAt.Format("2006-01-02 15:04:05"), UpdatedAt: dynamicCode.UpdatedAt.Format("2006-01-02 15:04:05"), Creator: creator, } response.Success(c, result) } func handleCreateDynamicCode(c *gin.Context) { userID := c.GetUint("user_id") var req struct { Name string `json:"name" binding:"required"` Code string `json:"code" binding:"required"` Description string `json:"description"` Enabled bool `json:"enabled"` ApplicationID uint `json:"application_id" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误: "+err.Error()) return } if err := validateDynamicCode(req.Code); err != nil { response.Error(c, 400, "代码语法错误: "+err.Error()) return } var app model.Application if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&app).Error; err != nil { response.Error(c, 404, "应用不存在") return } status := "inactive" if req.Enabled { status = "active" } key := normalizeKey(req.Name) var existingCode model.DynamicCode err := database.DB.Unscoped().Where("key = ?", key).First(&existingCode).Error if err == nil { if existingCode.DeletedAt.Valid { database.DB.Unscoped().Delete(&existingCode) } else { response.Error(c, 400, "该名称的动态代码已存在") return } } dynamicCode := model.DynamicCode{ UserID: &userID, ApplicationID: req.ApplicationID, Name: req.Name, Key: key, Code: req.Code, Description: req.Description, Status: status, } if err := database.DB.Create(&dynamicCode).Error; err != nil { if strings.Contains(err.Error(), "UNIQUE constraint failed") || strings.Contains(err.Error(), "constraint failed") { response.Error(c, 400, "该名称的动态代码已存在") return } response.Error(c, 500, "创建失败: "+err.Error()) return } response.Success(c, dynamicCode) } func handleUpdateDynamicCode(c *gin.Context) { userID := c.GetUint("user_id") id := c.Param("id") var dynamicCode model.DynamicCode if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil { response.Error(c, 404, "动态代码不存在") return } var app model.Application if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil { response.Error(c, 403, "无权限操作此动态代码") return } var req struct { Name string `json:"name" binding:"required"` Code string `json:"code" binding:"required"` Description string `json:"description"` Enabled bool `json:"enabled"` ApplicationID uint `json:"application_id" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } if err := validateDynamicCode(req.Code); err != nil { response.Error(c, 400, "代码语法错误: "+err.Error()) return } if req.ApplicationID != dynamicCode.ApplicationID { var newApp model.Application if err := database.DB.Where("id = ? AND user_id = ?", req.ApplicationID, userID).First(&newApp).Error; err != nil { response.Error(c, 403, "无权限操作此应用") return } dynamicCode.ApplicationID = req.ApplicationID } status := "inactive" if req.Enabled { status = "active" } dynamicCode.Name = req.Name dynamicCode.Key = normalizeKey(req.Name) dynamicCode.Code = req.Code dynamicCode.Description = req.Description dynamicCode.Status = status if err := database.DB.Save(&dynamicCode).Error; err != nil { response.Error(c, 500, "更新失败") return } response.Success(c, dynamicCode) } func handleDeleteDynamicCode(c *gin.Context) { userID := c.GetUint("user_id") id := c.Param("id") var dynamicCode model.DynamicCode if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil { response.Error(c, 404, "动态代码不存在") return } var app model.Application if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil { response.Error(c, 403, "无权限操作此动态代码") return } if err := database.DB.Delete(&dynamicCode).Error; err != nil { response.Error(c, 500, "删除失败") return } response.Success(c, gin.H{"message": "删除成功"}) } func handleUpdateDynamicCodeStatus(c *gin.Context) { userID := c.GetUint("user_id") id := c.Param("id") var dynamicCode model.DynamicCode if err := database.DB.Where("id = ?", id).First(&dynamicCode).Error; err != nil { response.Error(c, 404, "动态代码不存在") return } var app model.Application if err := database.DB.Where("id = ? AND user_id = ?", dynamicCode.ApplicationID, userID).First(&app).Error; err != nil { response.Error(c, 403, "无权限操作此动态代码") return } var req struct { Enabled bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } status := "inactive" if req.Enabled { status = "active" } dynamicCode.Status = status if err := database.DB.Save(&dynamicCode).Error; err != nil { response.Error(c, 500, "更新状态失败") return } response.Success(c, dynamicCode) } func handleBatchDeleteDynamicCodes(c *gin.Context) { userID := c.GetUint("user_id") var req struct { IDs []uint `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Delete(&model.DynamicCode{}).Error; err != nil { response.Error(c, 500, "批量删除失败") return } response.Success(c, gin.H{"message": "批量删除成功"}) } func handleBatchUpdateDynamicCodeStatus(c *gin.Context) { userID := c.GetUint("user_id") var req struct { IDs []uint `json:"ids" binding:"required"` Enabled bool `json:"enabled"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } status := "inactive" if req.Enabled { status = "active" } if err := database.DB.Model(&model.DynamicCode{}).Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Update("status", status).Error; err != nil { response.Error(c, 500, "批量更新状态失败") return } response.Success(c, gin.H{"message": "批量更新状态成功"}) } type RiskControlRule struct { ID uint `json:"id"` Type string `json:"type"` Value string `json:"value"` Reason string `json:"reason"` Status string `json:"status"` ExpiresAt *string `json:"expires_at"` CreatedAt string `json:"created_at"` } func handleGetRiskControlRules(c *gin.Context) { userID := c.GetUint("user_id") var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } var rules []model.RiskControlRule if err := database.DB.Where("user_id = ? OR application_id IN ?", userID, applicationIDs).Order("created_at DESC").Find(&rules).Error; err != nil { response.Error(c, 500, "获取风控规则失败") return } var result []gin.H for _, rule := range rules { var expiresAt *string if rule.ExpiresAt != nil { t := rule.ExpiresAt.Format("2006-01-02 15:04:05") expiresAt = &t } var appID *uint if rule.ApplicationID != nil { appID = rule.ApplicationID } result = append(result, gin.H{ "id": rule.ID, "type": rule.Type, "value": rule.Value, "reason": rule.Reason, "status": rule.Status, "expires_at": expiresAt, "application_id": appID, "is_global": rule.ApplicationID == nil, "created_at": rule.CreatedAt.Format("2006-01-02 15:04:05"), }) } response.Success(c, result) } func handleCreateRiskControlRule(c *gin.Context) { userID := c.GetUint("user_id") var req struct { Type string `json:"type" binding:"required"` Value string `json:"value" binding:"required"` Reason string `json:"reason"` ExpiresAt *string `json:"expires_at"` ApplicationID *uint `json:"application_id"` IsGlobal bool `json:"is_global"` } if err := c.ShouldBindJSON(&req); err != nil { log.Printf("[ERROR] Failed to bind JSON: %v", err) response.Error(c, 400, "参数错误") return } log.Printf("[DEBUG] Create risk control rule: type=%s, value=%s, is_global=%v, application_id=%v", req.Type, req.Value, req.IsGlobal, req.ApplicationID) var appID *uint switch req.Type { case "user": if req.ApplicationID == nil { response.Error(c, 400, "用户封禁规则必须指定应用") return } var application model.Application if err := database.DB.Where("id = ? AND user_id = ?", *req.ApplicationID, userID).First(&application).Error; err != nil { response.Error(c, 404, "应用不存在或无权限") return } appID = req.ApplicationID case "ip", "device", "region": if !req.IsGlobal && req.ApplicationID != nil { var application model.Application if err := database.DB.Where("id = ? AND user_id = ?", *req.ApplicationID, userID).First(&application).Error; err != nil { response.Error(c, 404, "应用不存在或无权限") return } appID = req.ApplicationID } else { appID = nil } default: response.Error(c, 400, "不支持的规则类型") return } rule := model.RiskControlRule{ UserID: userID, ApplicationID: appID, Type: req.Type, Value: req.Value, Reason: req.Reason, Status: "active", } if req.ExpiresAt != nil && *req.ExpiresAt != "" { t, err := time.Parse("2006-01-02T15:04", *req.ExpiresAt) if err == nil { rule.ExpiresAt = &t } else { log.Printf("[WARN] Failed to parse expires_at: %v", err) } } log.Printf("[DEBUG] Creating rule: %+v", rule) if err := database.DB.Create(&rule).Error; err != nil { log.Printf("[ERROR] Failed to create risk control rule: %v", err) response.Error(c, 500, "创建风控规则失败") return } var expiresAt *string if rule.ExpiresAt != nil { t := rule.ExpiresAt.Format("2006-01-02 15:04:05") expiresAt = &t } var appIDResp *uint if rule.ApplicationID != nil { appIDResp = rule.ApplicationID } response.Success(c, gin.H{ "id": rule.ID, "type": rule.Type, "value": rule.Value, "reason": rule.Reason, "status": rule.Status, "expires_at": expiresAt, "application_id": appIDResp, "is_global": rule.ApplicationID == nil, "created_at": rule.CreatedAt.Format("2006-01-02 15:04:05"), }) } func handleUpdateRiskControlRule(c *gin.Context) { userID := c.GetUint("user_id") ruleID := c.Param("id") var req struct { Type string `json:"type" binding:"required"` Value string `json:"value" binding:"required"` Reason string `json:"reason"` ExpiresAt *string `json:"expires_at"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } var rule model.RiskControlRule if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).First(&rule).Error; err != nil { response.Error(c, 404, "风控规则不存在") return } rule.Type = req.Type rule.Value = req.Value rule.Reason = req.Reason if req.ExpiresAt != nil && *req.ExpiresAt != "" { t, err := time.Parse("2006-01-02T15:04", *req.ExpiresAt) if err == nil { rule.ExpiresAt = &t } } else { rule.ExpiresAt = nil } if err := database.DB.Save(&rule).Error; err != nil { response.Error(c, 500, "更新风控规则失败") return } var expiresAt *string if rule.ExpiresAt != nil { t := rule.ExpiresAt.Format("2006-01-02 15:04:05") expiresAt = &t } response.Success(c, RiskControlRule{ ID: rule.ID, Type: rule.Type, Value: rule.Value, Reason: rule.Reason, Status: rule.Status, ExpiresAt: expiresAt, CreatedAt: rule.CreatedAt.Format("2006-01-02 15:04:05"), }) } func handleDeleteRiskControlRule(c *gin.Context) { userID := c.GetUint("user_id") ruleID := c.Param("id") var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).Delete(&model.RiskControlRule{}).Error; err != nil { response.Error(c, 500, "删除风控规则失败") return } response.Success(c, gin.H{"message": "删除成功"}) } func handleUpdateRiskControlRuleStatus(c *gin.Context) { userID := c.GetUint("user_id") ruleID := c.Param("id") var req struct { Status string `json:"status" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } var rule model.RiskControlRule if err := database.DB.Where("id = ? AND application_id IN ?", ruleID, applicationIDs).First(&rule).Error; err != nil { response.Error(c, 404, "风控规则不存在") return } rule.Status = req.Status if err := database.DB.Save(&rule).Error; err != nil { response.Error(c, 500, "更新状态失败") return } response.Success(c, gin.H{"message": "状态更新成功"}) } func handleBatchDeleteRiskControlRules(c *gin.Context) { userID := c.GetUint("user_id") var req struct { IDs []uint `json:"ids" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } if err := database.DB.Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Delete(&model.RiskControlRule{}).Error; err != nil { response.Error(c, 500, "批量删除失败") return } response.Success(c, gin.H{"message": "批量删除成功"}) } func handleBatchUpdateRiskControlRuleStatus(c *gin.Context) { userID := c.GetUint("user_id") var req struct { IDs []uint `json:"ids" binding:"required"` Status string `json:"status" binding:"required"` } if err := c.ShouldBindJSON(&req); err != nil { response.Error(c, 400, "参数错误") return } var applications []model.Application if err := database.DB.Where("user_id = ?", userID).Find(&applications).Error; err != nil { response.Error(c, 500, "获取应用列表失败") return } var applicationIDs []uint for _, app := range applications { applicationIDs = append(applicationIDs, app.ID) } if err := database.DB.Model(&model.RiskControlRule{}).Where("id IN ? AND application_id IN ?", req.IDs, applicationIDs).Update("status", req.Status).Error; err != nil { response.Error(c, 500, "批量更新状态失败") return } response.Success(c, gin.H{"message": "批量更新状态成功"}) }