feat(plugins): 插件管理 - 区分系统与官方插件权限 (#329)

This commit is contained in:
MasterChiefCN
2026-08-27 11:22:00 +08:00
committed by GitHub
parent 23b4628c81
commit f111d57fc6
32 changed files with 1137 additions and 143 deletions

View File

@@ -38,7 +38,7 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
if err := svc.RequireWorkflowPluginForUser(user.ID, "comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
@@ -61,7 +61,7 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
if err := svc.RequireWorkflowPluginForUser(user.ID, "comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
@@ -78,10 +78,6 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
if err := svc.RevokeComfyBridge(user.ID, c.Param("id")); err != nil {
failService(c, err)
return
@@ -97,10 +93,6 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
wait, err := comfyBridgeWait(c.Query("wait"))
if err != nil {
fail(c, http.StatusBadRequest, err)
@@ -122,10 +114,6 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
if c.Request.ContentLength > maxComfyBridgeRequestBytes {
fail(c, http.StatusRequestEntityTooLarge, errors.New("Bridge 结果请求体超过 64MB"))
return
@@ -163,10 +151,6 @@ func RegisterComfyBridgeRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("comfyui-bridge-image"); err != nil {
failService(c, err)
return
}
var req struct {
Capabilities map[string]any `json:"capabilities,omitempty"`
}

View File

@@ -16,11 +16,56 @@ import (
func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
statusRoutes := r.Group("/plugins")
statusRoutes.GET("/status", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"statuses": svc.WorkflowPluginStatuses()})
states, err := svc.PluginStatesForUser(user)
if err != nil {
failService(c, err)
return
}
statuses, err := svc.WorkflowPluginStatusesForUser(user.ID)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"statuses": statuses, "states": states})
})
adminRoutes := r.Group("/admin/plugins")
adminRoutes.GET("", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
states, err := svc.AdminPluginStates(user)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"plugins": svc.Plugins(), "states": states})
})
adminRoutes.PUT("/:id/availability", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
var request struct {
Available bool `json:"available"`
}
if err := c.ShouldBindJSON(&request); err != nil {
fail(c, http.StatusBadRequest, err)
return
}
state, err := svc.SetPluginPlatformAvailability(user, c.Param("id"), request.Available)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"state": state})
})
pluginRoutes := r.Group("/plugins")
pluginRoutes.Use(requirePluginCenterAccess(svc))
@@ -37,7 +82,12 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
ok(c, gin.H{"plugins": plugins})
states, err := svc.PluginStatesForUser(user)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"plugins": plugins, "states": states})
})
pluginRoutes.GET("/catalog", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
@@ -77,7 +127,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
fail(c, http.StatusBadRequest, err)
return
}
plugin, err := svc.InstallPlugin(data, fileName)
plugin, err := svc.InstallPluginForAdmin(user, data, fileName)
if err != nil {
failService(c, err)
return
@@ -85,10 +135,27 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, gin.H{"plugin": plugin})
})
pluginRoutes.GET("/:id/package", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
visiblePlugins, err := svc.PluginsForUser(user)
if err != nil {
failService(c, err)
return
}
visible := false
for _, plugin := range visiblePlugins {
if plugin.Manifest.ID == c.Param("id") {
visible = true
break
}
}
if !visible {
failService(c, service.Forbidden("无权访问该插件包"))
return
}
data, fileName, err := svc.PluginPackage(c.Param("id"))
if err != nil {
failService(c, err)
@@ -103,6 +170,26 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
})
pluginRoutes.POST("/:id/enable", pluginToggle(svc, true))
pluginRoutes.POST("/:id/disable", pluginToggle(svc, false))
pluginRoutes.PUT("/:id/activation", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
var request struct {
Enabled bool `json:"enabled"`
}
if err := c.ShouldBindJSON(&request); err != nil {
fail(c, http.StatusBadRequest, err)
return
}
state, err := svc.SetUserPluginEnabled(user, c.Param("id"), request.Enabled)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"state": state})
})
pluginRoutes.DELETE("/:id", func(c *gin.Context) {
user, err := currentUser(c, svc)
if err != nil {
@@ -113,7 +200,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
failService(c, err)
return
}
if err := svc.UninstallPlugin(c.Param("id")); err != nil {
if err := svc.UninstallPluginForAdmin(user, c.Param("id")); err != nil {
failService(c, err)
return
}
@@ -121,8 +208,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
})
pluginRoutes.GET("/eagle/library", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
library, err := svc.EagleLibrary(c.Query("baseUrl"))
@@ -134,8 +220,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, gin.H{"library": library})
})
pluginRoutes.GET("/eagle/items", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
limit, _ := strconv.Atoi(c.DefaultQuery("limit", "60"))
@@ -148,8 +233,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, gin.H{"items": items})
})
pluginRoutes.GET("/eagle/items/:itemId/file", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
file, err := svc.OpenEagleItemFile(c.Query("baseUrl"), c.Param("itemId"))
@@ -164,8 +248,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
c.DataFromReader(http.StatusOK, file.Size, file.MimeType, file.Body, nil)
})
pluginRoutes.GET("/eagle/items/:itemId/thumbnail", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
file, err := svc.OpenEagleItemThumbnail(c.Query("baseUrl"), c.Param("itemId"))
@@ -179,8 +262,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
c.DataFromReader(http.StatusOK, file.Size, file.MimeType, file.Body, nil)
})
pluginRoutes.POST("/eagle/items", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 160<<20)
@@ -197,8 +279,7 @@ func RegisterPluginRoutes(r *gin.RouterGroup, svc *service.Service) {
ok(c, gin.H{"item": item})
})
pluginRoutes.POST("/eagle/folders", func(c *gin.Context) {
if _, err := currentUser(c, svc); err != nil {
failService(c, err)
if _, allowed := requireEnabledPlugin(c, svc, service.PluginEagleAssetConnector); !allowed {
return
}
c.Request.Body = http.MaxBytesReader(c.Writer, c.Request.Body, 16<<10)
@@ -250,11 +331,23 @@ func pluginToggle(svc *service.Service, enabled bool) gin.HandlerFunc {
failService(c, err)
return
}
plugin, err := svc.SetPluginEnabled(c.Param("id"), enabled)
state, err := svc.SetPluginPlatformAvailability(user, c.Param("id"), enabled)
if err != nil {
failService(c, err)
return
}
ok(c, gin.H{"plugin": plugin})
ok(c, gin.H{"state": state})
}
}
func requireEnabledPlugin(c *gin.Context, svc *service.Service, pluginID string) (*model.User, bool) {
user, err := currentUser(c, svc)
if err == nil {
err = svc.RequirePluginForUser(user.ID, pluginID)
}
if err != nil {
failService(c, err)
return nil, false
}
return user, true
}

View File

@@ -11,11 +11,12 @@ import (
// RegisterRunningHubRoutes 是独立工作流 Provider 的管理代理,不复用 ModelChannel。
func RegisterRunningHubRoutes(r *gin.RouterGroup, svc *service.Service) {
fetch := func(c *gin.Context, app bool) {
if _, err := currentUser(c, svc); err != nil {
user, err := currentUser(c, svc)
if err != nil {
failService(c, err)
return
}
if err := svc.RequireWorkflowPluginForInterface("runninghub-workflow-image"); err != nil {
if err := svc.RequireWorkflowPluginForUser(user.ID, "runninghub-workflow-image"); err != nil {
failService(c, err)
return
}
@@ -26,7 +27,6 @@ func RegisterRunningHubRoutes(r *gin.RouterGroup, svc *service.Service) {
return
}
var result map[string]any
var err error
if app {
result, err = svc.FetchRunningHubAppInfo(c.Request.Context(), req)
} else {