From b2cc27b8f54fc8486293afaeaf33b2436229c0f2 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Thu, 25 Sep 2025 16:15:34 +0800 Subject: [PATCH] =?UTF-8?q?refactor(bookmark):=20=E9=87=8D=E6=9E=84=20book?= =?UTF-8?q?mark=20=E6=9C=8D=E5=8A=A1=E7=94=9F=E6=88=90=E9=85=8D=E7=BD=AE?= =?UTF-8?q?=E5=92=8C=E5=86=85=E9=83=A8=E5=BC=95=E7=94=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 将 bookmark 和 user_np 服务的生成配置分离为 server 和 client 包, 并更新了相应的导入路径。同时更新了 OpenAPI 配置文件中的包名、输出路径及 启用 strict-server 模式以增强类型安全。 此外,同步更新了 VFS 服务的客户端和服务端生成配置,并完善了其 OpenAPI 错误响应定义与二进制内容支持,增强了 API 的规范性和健壮性。 修复了部分权限校验逻辑,并调整了中间件注册方式以适配新的严格模式接口。 --- cmd/bookmark/main.go | 4 +- config/bookmark/bookmark.yaml | 4 +- config/bookmark/client.yaml | 7 +- config/bookmark/server.yaml | 5 +- config/user_np/server.yaml | 6 +- config/vfs/client.yaml | 7 +- config/vfs/server.yaml | 6 +- config/vfs/vfs.yaml | 164 ++++++-- internal/bookmarks/bookmark.go | 2 +- internal/bookmarks/user_np.go | 4 +- internal/vfs/models/vfs.go | 1 + internal/vfs/vfs.go | 294 +------------- internal/vfs/vfs_auth.go | 202 +++++++--- internal/vfs/vfs_impl.go | 518 +++++++++++++++++++++++++ internal/vfs/vfs_service.go | 94 +---- internal/vfs/vfsdriver/vfs_bookmark.go | 33 +- main.go | 3 +- vfs_config.yaml | 12 + 18 files changed, 878 insertions(+), 488 deletions(-) create mode 100644 internal/vfs/vfs_impl.go create mode 100644 vfs_config.yaml diff --git a/cmd/bookmark/main.go b/cmd/bookmark/main.go index f45e8ae..31c21eb 100644 --- a/cmd/bookmark/main.go +++ b/cmd/bookmark/main.go @@ -3,8 +3,8 @@ package main import ( "log" - bookmarks_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" - user_np_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np" + bookmarks_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks_server" + user_np_api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np_server" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks" "github.com/gin-contrib/cors" "github.com/gin-gonic/gin" diff --git a/config/bookmark/bookmark.yaml b/config/bookmark/bookmark.yaml index ed7b9c0..a9b5e16 100644 --- a/config/bookmark/bookmark.yaml +++ b/config/bookmark/bookmark.yaml @@ -1,7 +1,7 @@ openapi: '3.0.3' info: - title: zzyxyz_api - description: API服务 + title: zzyxyz_bookmark_api + description: bookmark API服务 version: '1.0' servers: - url: http://localhost:8081/api diff --git a/config/bookmark/client.yaml b/config/bookmark/client.yaml index 7cb09ed..18358a5 100644 --- a/config/bookmark/client.yaml +++ b/config/bookmark/client.yaml @@ -1,5 +1,6 @@ # yaml-language-server: ... -package: api +package: client generate: - client: true -output: ./gen/bookmarks/client.go + client: true + models: true +output: ./gen/bookmarks_client/client.go diff --git a/config/bookmark/server.yaml b/config/bookmark/server.yaml index c63069e..d8531a7 100644 --- a/config/bookmark/server.yaml +++ b/config/bookmark/server.yaml @@ -1,6 +1,7 @@ # yaml-language-server: ... -package: api +package: server generate: gin-server: true models: true -output: ./gen/bookmarks/server.go + strict-server: true +output: ./gen/bookmarks_server/server.go diff --git a/config/user_np/server.yaml b/config/user_np/server.yaml index 5ebe25b..4db1e65 100644 --- a/config/user_np/server.yaml +++ b/config/user_np/server.yaml @@ -1,7 +1,7 @@ # yaml-language-server: ... -package: api +package: server generate: gin-server: true models: true - # strict-server: true -output: ./gen/user_np/server.go + strict-server: true +output: ./gen/user_np_server/server.go diff --git a/config/vfs/client.yaml b/config/vfs/client.yaml index 9924e99..7bc917f 100644 --- a/config/vfs/client.yaml +++ b/config/vfs/client.yaml @@ -1,5 +1,6 @@ # yaml-language-server: ... -package: api +package: client generate: - client: true -output: ./gen/vfs/client.go + client: true + models: true +output: ./gen/vfs_client/client.go diff --git a/config/vfs/server.yaml b/config/vfs/server.yaml index 941c886..209fc5e 100644 --- a/config/vfs/server.yaml +++ b/config/vfs/server.yaml @@ -1,7 +1,7 @@ # yaml-language-server: ... -package: api +package: server generate: gin-server: true models: true - # strict-server: true -output: ./gen/vfs/server.go + strict-server: true +output: ./gen/vfs_server/server.go diff --git a/config/vfs/vfs.yaml b/config/vfs/vfs.yaml index da7f28d..11ba8d8 100644 --- a/config/vfs/vfs.yaml +++ b/config/vfs/vfs.yaml @@ -39,6 +39,14 @@ paths: schema: type: string description: 认证令牌 + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '500': + $ref: '#/components/responses/ServerInternalError' delete: summary: 删除用户 @@ -54,6 +62,12 @@ paths: application/json: schema: $ref: '#/components/schemas/Error' + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' '500': $ref: '#/components/responses/ServerInternalError' @@ -86,21 +100,23 @@ paths: content: application/json: schema: - oneOf: - - type: string - description: 文件内容 - - type: array - items: - $ref: '#/components/schemas/VFSDirectoryEntry' - description: 目录条目列表 - - '404': - description: 路径不存在 - content: - application/json: + description: 目录条目列表 + type: array + items: + $ref: '#/components/schemas/VFSDirectoryEntry' + application/octet-stream: schema: - $ref: '#/components/schemas/Error' - + description: 文件或服务的二进制内容 + type: string + format: binary + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' '500': $ref: '#/components/responses/ServerInternalError' @@ -112,23 +128,31 @@ paths: requestBody: required: false content: - application/json: - schema: - type: string - description: 文件内容(仅当type为file时有效) + application/octet-stream: + schema: + description: 文件或服务的二进制内容 + type: string + format: byte responses: '201': description: 创建成功 content: + application/octet-stream: + schema: + description: 文件或服务的二进制内容 + type: string + format: byte application/json: schema: $ref: '#/components/schemas/VFSNodeResponse' '400': - description: 请求参数错误 - content: - application/json: - schema: - $ref: '#/components/schemas/Error' + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' '500': $ref: '#/components/responses/ServerInternalError' @@ -161,11 +185,13 @@ paths: schema: $ref: '#/components/schemas/VFSNodeResponse' '400': - description: 请求参数错误 - content: - application/json: - schema: - $ref: '#/components/schemas/Error' + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' + '404': + $ref: '#/components/responses/PathNotFoundError' '500': $ref: '#/components/responses/ServerInternalError' @@ -177,12 +203,14 @@ paths: responses: '204': description: 删除成功 + '400': + $ref: '#/components/responses/ParameterError' + '401': + $ref: '#/components/responses/UnauthorizedError' + '403': + $ref: '#/components/responses/ForbiddenError' '404': - description: 路径不存在 - content: - application/json: - schema: - $ref: '#/components/schemas/Error' + $ref: '#/components/responses/PathNotFoundError' '500': $ref: '#/components/responses/ServerInternalError' @@ -200,13 +228,49 @@ components: application/json: schema: $ref: '#/components/schemas/Error' + example: + errtype: "InternalServerError" + message: "服务器内部错误" - Unauthorized: + UnauthorizedError: description: 未授权 content: application/json: schema: $ref: '#/components/schemas/Error' + example: + errtype: "Unauthorized" + message: "访问被拒绝,缺少有效的认证令牌" + + ForbiddenError: + description: 禁止 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "Forbidden" + message: "访问被拒绝,您没有权限访问此资源" + + PathNotFoundError: + description: 路径不存在 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "PathNotFound" + message: "指定的路径不存在" + + ParameterError: + description: 请求参数错误 + content: + application/json: + schema: + $ref: '#/components/schemas/Error' + example: + errtype: "ParameterError" + message: "请求参数无效或缺失" schemas: VFSNodeType: @@ -258,12 +322,34 @@ components: properties: errtype: type: string - example: "ParameterError" - description: 错误类型 + description: | + 错误类型,可能的值包括: + - "InternalServerError": 服务器内部错误 + - "Unauthorized": 客户端需要提供正确格式权限 + - "Forbidden": 无权限访问 + - "PathNotFound": 路径不存在 + - "ParameterError": 请求参数错误 + - "ConflictError": 资源冲突 + - "DatabaseError": 数据库操作错误 + - "NotFoundError": 资源未找到 + - "AccessDenied": 访问被拒绝 + - "ServiceProxyError": 代理服务错误 + example: "InternalServerError" + enum: + - "InternalServerError" + - "Unauthorized" + - "Forbidden" + - "PathNotFound" + - "ParameterError" + - "ConflictError" + - "DatabaseError" + - "NotFoundError" + - "AccessDenied" + - "ServiceProxyError" message: - example: "传递的第一个参数错误" type: string - description: 错误信息 + description: 详细的错误信息 + example: "传递的第一个参数错误" required: - errtype - - message + - message \ No newline at end of file diff --git a/internal/bookmarks/bookmark.go b/internal/bookmarks/bookmark.go index 9bf47d1..da7d575 100644 --- a/internal/bookmarks/bookmark.go +++ b/internal/bookmarks/bookmark.go @@ -5,7 +5,7 @@ package bookmarks import ( "net/http" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks_server" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks/models" "github.com/gin-gonic/gin" _ "github.com/mattn/go-sqlite3" diff --git a/internal/bookmarks/user_np.go b/internal/bookmarks/user_np.go index 732f880..86b51c1 100644 --- a/internal/bookmarks/user_np.go +++ b/internal/bookmarks/user_np.go @@ -7,8 +7,8 @@ import ( "fmt" "net/http" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np" - client "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/user_np_server" + client "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs_client" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/bookmarks/models" "github.com/gin-gonic/gin" "gorm.io/driver/sqlite" diff --git a/internal/vfs/models/vfs.go b/internal/vfs/models/vfs.go index d80b98e..8a845e0 100644 --- a/internal/vfs/models/vfs.go +++ b/internal/vfs/models/vfs.go @@ -360,6 +360,7 @@ func (v *Vfs) CreateNodeByComponents(parentPath, nodeName string, nodeType VfsNo } func ParsePathComponents(pathStr string) (parentPath, nodeName string, nodeType VfsNodeType, err error) { + // FIXME: 路径判断以及update的类型判断 abspath := path.Clean(pathStr) if !path.IsAbs(abspath) { return "", "", 0, errors.New("path must be absolute") diff --git a/internal/vfs/vfs.go b/internal/vfs/vfs.go index 890af00..feefb8f 100644 --- a/internal/vfs/vfs.go +++ b/internal/vfs/vfs.go @@ -2,17 +2,14 @@ package vfs import ( "log" - "net/http" "os" "path/filepath" "sync" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" "github.com/casbin/casbin/v2" "github.com/casbin/casbin/v2/model" fileadapter "github.com/casbin/casbin/v2/persist/file-adapter" - "github.com/gin-gonic/gin" ) type VfsImpl struct { @@ -23,6 +20,7 @@ type VfsImpl struct { proxyMutex sync.RWMutex // 保护代理表的读写锁 } +// 在NewVfsHandler中注册中间件 func NewVfsHandler(config *Config) (*VfsImpl, error) { var err error @@ -68,293 +66,15 @@ func NewVfsHandler(config *Config) (*VfsImpl, error) { log.Printf("Admin Token: %s", config.VFS.AdminToken) log.Printf("Register Token: %s", config.VFS.RegisterToken) - return &VfsImpl{ + impl := &VfsImpl{ vfs: vfs, enfocer: e, config: config.VFS, proxyTable: make([]*ProxyEntry, 0), proxyMutex: sync.RWMutex{}, - }, nil + } + + // 注册中间件 + // 注意:在注册handler时需要传入中间件 + return impl, nil } - -func ModelType2ResponseType(nodeType models.VfsNodeType) api.VFSNodeType { - var reponseType api.VFSNodeType - switch nodeType { - case models.VfsNodeTypeFile: - reponseType = api.File - case models.VfsNodeTypeDirectory: - reponseType = api.Directory - case models.VfsNodeTypeService: - reponseType = api.Service - } - return reponseType -} - -// CreateVFSNode implements api.ServerInterface. -func (v *VfsImpl) CreateVFSNode(c *gin.Context, params api.CreateVFSNodeParams) { - if !v.CheckPermissionMiddleware(c, params.Path) { - return - } - - // 解析路径组件 - parentPath, nodeName, nodeType, err := models.ParsePathComponents(params.Path) - if err != nil { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - - // 创建节点 - node, err := v.vfs.CreateNodeByComponents(parentPath, nodeName, nodeType) - if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "CreateNodeByComponents", - Message: err.Error(), - }) - return - } - - if nodeType == models.VfsNodeTypeService { - if !v.Proxy2Service(c, node) { - // Rollback - err := v.vfs.DeleteVFSNode(node) - if err != nil { - // FIXME: 需要解决这种原子性 - panic("Maybe Consistency Error") - } - return - } - } - - // 返回创建成功的节点 - c.JSON(http.StatusCreated, api.VFSNodeResponse{ - Name: node.Name, - Type: ModelType2ResponseType(node.Type), - CreatedAt: node.CreatedAt, - UpdatedAt: node.UpdatedAt, - }) -} - -// GetVFSNode implements api.ServerInterface. -func (v *VfsImpl) GetVFSNode(c *gin.Context, params api.GetVFSNodeParams) { - if !v.CheckPermissionMiddleware(c, params.Path) { - return - } - - node, err := v.vfs.GetNodeByPath(params.Path) - if err != nil { - c.JSON(http.StatusNotFound, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - - switch node.Type { - case models.VfsNodeTypeDirectory: - if entries, err := v.vfs.GetChildren(node.ID); err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } else { - var responseEntries []api.VFSDirectoryEntry - for _, entry := range entries { - responseEntries = append(responseEntries, api.VFSDirectoryEntry{ - Name: entry.Name, - Type: ModelType2ResponseType(entry.Type), - }) - } - c.JSON(http.StatusOK, responseEntries) - return - } - case models.VfsNodeTypeService: - v.Proxy2Service(c, node) - default: - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "error", - Message: "Not a valid node type", - }) - } -} - -// DeleteVFSNode implements api.ServerInterface. -func (v *VfsImpl) DeleteVFSNode(c *gin.Context, params api.DeleteVFSNodeParams) { - if !v.CheckPermissionMiddleware(c, params.Path) { - return - } - - node, err := v.vfs.GetNodeByPath(params.Path) - if err != nil { - c.JSON(http.StatusNotFound, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - - switch node.Type { - case models.VfsNodeTypeService: - if !v.Proxy2Service(c, node) { - return - } - case models.VfsNodeTypeDirectory: - if children, err := v.vfs.GetChildren(node.ID); err != nil || len(children) != 0 { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "the folder is not empty", - }) - return - } - default: - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "node type not supported", - }) - return - } - if err := v.vfs.DeleteVFSNode(node); err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - c.JSON(http.StatusNoContent, nil) -} - -// UpdateVFSNode implements api.ServerInterface. -func (v *VfsImpl) UpdateVFSNode(c *gin.Context, params api.UpdateVFSNodeParams) { - if !v.CheckPermissionMiddleware(c, params.Path) { - return - } - - var req api.UpdateVFSNodeJSONRequestBody - if err := c.ShouldBindJSON(&req); err != nil { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "ParameterError", - Message: "Invalid request parameters", - }) - return - } - - node, err := v.vfs.GetNodeByPath(params.Path) - if err != nil { - c.JSON(http.StatusNotFound, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - - switch params.Op { - case api.Rename: - if req == "" { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "ParameterError", - Message: "Invalid request parameters", - }) - return - } - // FIXME: 对于service,后缀属性需要强制保留 - if err := v.vfs.UpdateVFSNode(node); err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: err.Error(), - }) - return - } - case api.Change: - if node.Type != models.VfsNodeTypeFile { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "ParameterError", - Message: "Invalid request parameters, node type must be a service", - }) - return - } - if !v.Proxy2Service(c, node) { - return - } - case api.Move: - // FIXME: 需要添加权限控制 - v.vfs.MoveToPath(node, req) - - case api.Copy: - fallthrough - default: - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "op type not supported", - }) - return - } -} - -// CreateUser implements api.ServerInterface. -func (v *VfsImpl) CreateUser(c *gin.Context, username string) { - token := c.GetHeader("X-VFS-Token") - if token != v.config.RegisterToken || token != v.config.AdminToken { - c.JSON(http.StatusForbidden, api.Error{ - Errtype: "AccessDenied", - Message: "Access denied", - }) - return - } - - token, err := v.vfs.CreateUser(username) - if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "CreateUserError", - Message: err.Error(), - }) - return - } - - _, err = v.enfocer.AddRoleForUser(username, "user") - if err != nil { - log.Printf("Failed to add role for user %s: %v", username, err) - } - v.enfocer.SavePolicy() - - // 根据API文档,token应该通过响应头返回 - c.Header("X-VFS-Token", token) - c.Status(http.StatusCreated) -} - -// DeleteUser implements api.ServerInterface. -func (v *VfsImpl) DeleteUser(c *gin.Context, username string) { - token := c.GetHeader("X-VFS-Token") - user, err := v.vfs.GetUserByToken(token) - if err != nil || user.Name != username { - c.JSON(http.StatusForbidden, api.Error{ - Errtype: "AccessDenied", - Message: "Access denied", - }) - return - } - - err = v.vfs.DeleteUser(username) - if err != nil { - if err.Error() == "user not found" { - c.JSON(http.StatusNotFound, api.Error{ - Errtype: "UserNotFoundError", - Message: "User not found", - }) - return - } - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "DeleteUserError", - Message: err.Error(), - }) - return - } - - // 根据API文档,删除成功返回204状态码 - c.Status(http.StatusNoContent) -} - -// Make sure we conform to ServerInterface -var _ api.ServerInterface = (*VfsImpl)(nil) diff --git a/internal/vfs/vfs_auth.go b/internal/vfs/vfs_auth.go index 07665e3..83845e2 100644 --- a/internal/vfs/vfs_auth.go +++ b/internal/vfs/vfs_auth.go @@ -2,10 +2,9 @@ package vfs import ( _ "embed" - "net/http" "strings" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs_server" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" "github.com/gin-gonic/gin" ) @@ -13,13 +12,153 @@ import ( //go:embed vfs_model.conf var CasbinModel string -func NewVfsPermission() (*api.GinServerOptions, error) { - return &api.GinServerOptions{ - Middlewares: []api.MiddlewareFunc{VfsMiddleware()}, - }, nil +// 使用示例:在main.go或路由注册处 +func RegisterVFSRoutes(router gin.IRouter, vfsHandler *VfsImpl) { + // 创建严格模式handler并添加权限中间件 + strictHandler := api.NewStrictHandler(vfsHandler, []api.StrictMiddlewareFunc{ + vfsHandler.PermissionMiddleware, + }) + + // 注册handler + api.RegisterHandlers(router, strictHandler) } -func (v *VfsImpl) CheckPermission(token, path, action string) (bool, error) { +// PermissionMiddleware 是一个权限验证中间件 +func (v *VfsImpl) PermissionMiddleware(handler api.StrictHandlerFunc, operation string) api.StrictHandlerFunc { + return func(ctx *gin.Context, request interface{}) (interface{}, error) { + // 从请求头获取token + token := ctx.GetHeader("X-VFS-Token") + + // Admin token 拥有所有权限 + if token == v.config.AdminToken && len(token) != 0 { + return handler(ctx, request) + } + + // 根据操作类型进行不同的权限检查 + switch operation { + case "CreateUser": + // 只有admin或register token可以创建用户 + if token != v.config.RegisterToken { + return api.CreateUser403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + } + return handler(ctx, request) + + // 如果是删除用户操作,检查是否具有管理员权限 + case "DeleteUser": + // 获取要删除的用户名 + var username string + if req, ok := request.(api.DeleteUserRequestObject); ok { + username = req.Username + } + + // 验证token对应的用户是否存在 + user, err := v.vfs.GetUserByToken(token) + if err != nil || (user.Name != username && token != v.config.AdminToken) { + return api.DeleteUser403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + } + // 如果验证通过,继续执行原处理函数 + return handler(ctx, request) + case "GetVFSNode", "CreateVFSNode", "UpdateVFSNode", "DeleteVFSNode": + // VFS节点操作的权限检查 + var path string + var method string + + // 根据操作类型获取路径和HTTP方法 + switch operation { + case "GetVFSNode": + if req, ok := request.(api.GetVFSNodeRequestObject); ok { + path = req.Params.Path + } + method = "GET" + case "CreateVFSNode": + if req, ok := request.(api.CreateVFSNodeRequestObject); ok { + path = req.Params.Path + } + method = "POST" + case "UpdateVFSNode": + if req, ok := request.(api.UpdateVFSNodeRequestObject); ok { + path = req.Params.Path + } + method = "PATCH" + case "DeleteVFSNode": + if req, ok := request.(api.DeleteVFSNodeRequestObject); ok { + path = req.Params.Path + } + method = "DELETE" + } + + // 检查权限 + allowed, err := v.CheckPermission(token, path, method) + if err != nil { + return api.GetVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to check permission: " + err.Error(), + }, + }, nil + } + + if !allowed { + // 根据操作类型返回相应的错误响应 + switch operation { + case "GetVFSNode": + return api.GetVFSNode403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + case "CreateVFSNode": + return api.CreateVFSNode403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + case "UpdateVFSNode": + return api.UpdateVFSNode403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + case "DeleteVFSNode": + return api.DeleteVFSNode403JSONResponse{ + ForbiddenErrorJSONResponse: api.ForbiddenErrorJSONResponse{ + Errtype: api.ErrorErrtypeForbidden, + Message: "Access denied", + }, + }, nil + } + } + return handler(ctx, request) + default: + return api.Error{ + Errtype: api.ErrorErrtypeForbidden, + Message: "not supported function it's a bug in backend", + }, nil + } + } +} + +// CheckPermission 检查用户对指定路径的权限 +// FIXME: using casbin to check it +func (v *VfsImpl) CheckPermission(token, path, method string) (bool, error) { + // Admin token 拥有所有权限 + if token == v.config.AdminToken && len(token) != 0 { + return true, nil + } + // 根据 token 获取用户信息 user, err := v.vfs.GetUserByToken(token) if err != nil { @@ -27,13 +166,8 @@ func (v *VfsImpl) CheckPermission(token, path, action string) (bool, error) { user = &models.VfsUser{Name: "", Token: ""} } - // 特殊处理:admin 用户拥有所有权限 - if token == v.config.AdminToken && len(token) != 0 { // admin 用户拥有所有权限 - return true, nil - } - // 允许任何人读取 public 目录 - if strings.HasPrefix(path, "/public") && action == "GET" { + if method == "GET" && strings.HasPrefix(path, "/public") { return true, nil } @@ -42,48 +176,14 @@ func (v *VfsImpl) CheckPermission(token, path, action string) (bool, error) { return true, nil } - // 构造 Casbin 请求 - // 对于普通用户,需要将策略中的 {{username}} 替换为实际用户名 - obj := path - sub := user.Name // 使用 Casbin 检查权限 - allowed, err := v.enfocer.Enforce(sub, obj, action) + // sub: 用户名 (匿名用户为空字符串) + // obj: 路径 + // act: HTTP方法 + allowed, err := v.enfocer.Enforce(user.Name, path, method) if err != nil { return false, err } return allowed, nil } - -func (v *VfsImpl) CheckPermissionMiddleware(c *gin.Context, path string) bool { - token := c.GetHeader("X-VFS-Token") - allowed, err := v.CheckPermission(token, path, c.Request.Method) - // log.Println("CheckPermission:", allowed, err) - if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "PermissionCheckError", - Message: "Failed to check permission: " + err.Error(), - }) - return false - } - if !allowed { - c.JSON(http.StatusForbidden, api.Error{ - Errtype: "AccessDenied", - Message: "Access denied", - }) - return false - } - return true -} - -func VfsMiddleware() api.MiddlewareFunc { - return func(c *gin.Context) { - // 检查当前请求是否需要认证 - if _, exists := c.Get(api.ApiKeyAuthScopes); exists { - // 提取 API Key - apiKey := c.GetHeader("X-VFS-Token") - c.Set(api.ApiKeyAuthScopes, apiKey) - } - c.Next() - } -} diff --git a/internal/vfs/vfs_impl.go b/internal/vfs/vfs_impl.go new file mode 100644 index 0000000..a30b352 --- /dev/null +++ b/internal/vfs/vfs_impl.go @@ -0,0 +1,518 @@ +package vfs + +import ( + "bytes" + "context" + "fmt" + "io" + "log" + "net/http" + + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs_server" + "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" +) + +// CreateVFSNode implements server.StrictServerInterface. +func (v *VfsImpl) CreateVFSNode(ctx context.Context, request api.CreateVFSNodeRequestObject) (api.CreateVFSNodeResponseObject, error) { + // 解析路径组件 + parentPath, nodeName, nodeType, err := models.ParsePathComponents(request.Params.Path) + if err != nil { + return api.CreateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: err.Error(), + }, + }, nil + } + + // 读取请求体数据 + // FIXME: 使用stream可能更好 + var content []byte + if request.Body != nil { + content, err = io.ReadAll(request.Body) + if err != nil { + return api.CreateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to read request body: " + err.Error(), + }, + }, nil + } + } + + // 创建节点 (可能需要传递content数据到vfs层) + node, err := v.vfs.CreateNodeByComponents(parentPath, nodeName, nodeType) + if err != nil { + return api.CreateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: err.Error(), + }, + }, nil + } + + // 处理服务类型节点 + if nodeType == models.VfsNodeTypeService { + // 这里可能需要将content传递给服务处理 + if result, err := v.StrictProxy2Service(http.MethodPost, content, node); err != nil { + // 回滚操作 + err := v.vfs.DeleteVFSNode(node) + if err != nil { + // FIXME: 需要解决这种原子性 + return nil, fmt.Errorf("consistency error: %w", err) + } + return nil, err + } else { + // 返回二进制数据响应 + return api.CreateVFSNode201ApplicationoctetStreamResponse{ + Body: bytes.NewReader(result), + ContentLength: int64(len(result)), + }, nil + } + } + + // 返回JSON响应 + return api.CreateVFSNode201JSONResponse{ + Name: node.Name, + Type: ModelType2ResponseType(node.Type), + CreatedAt: node.CreatedAt, + UpdatedAt: node.UpdatedAt, + }, nil +} + +// DeleteVFSNode implements server.StrictServerInterface. +func (v *VfsImpl) DeleteVFSNode(ctx context.Context, request api.DeleteVFSNodeRequestObject) (api.DeleteVFSNodeResponseObject, error) { + // 获取节点 + node, err := v.vfs.GetNodeByPath(request.Params.Path) + if err != nil { + return api.DeleteVFSNode404JSONResponse{ + PathNotFoundErrorJSONResponse: api.PathNotFoundErrorJSONResponse{ + Errtype: api.ErrorErrtypePathNotFound, + Message: err.Error(), + }, + }, nil + } + + // 根据节点类型进行不同处理 + switch node.Type { + case models.VfsNodeTypeService: + // 对于服务类型节点,通过代理删除 + _, err := v.StrictProxy2Service(http.MethodDelete, nil, node) + if err != nil { + return api.DeleteVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeServiceProxyError, + Message: err.Error(), + }, + }, nil + } + case models.VfsNodeTypeDirectory: + // 检查目录是否为空 + children, err := v.vfs.GetChildren(node.ID) + if err != nil { + return api.DeleteVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to get directory children: " + err.Error(), + }, + }, nil + } + + if len(children) != 0 { + return api.DeleteVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeConflictError, + Message: "Directory is not empty", + }, + }, nil + } + case models.VfsNodeTypeFile: + // TODO + // 文件类型节点可以直接删除 + fallthrough + default: + return api.DeleteVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Node type not supported for deletion", + }, + }, nil + } + + // 执行实际的删除操作 + if err := v.vfs.DeleteVFSNode(node); err != nil { + return api.DeleteVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to delete node: " + err.Error(), + }, + }, nil + } + + // 成功删除返回204状态 + return api.DeleteVFSNode204Response{}, nil +} + +// GetVFSNode implements server.StrictServerInterface. +func (v *VfsImpl) GetVFSNode(ctx context.Context, request api.GetVFSNodeRequestObject) (api.GetVFSNodeResponseObject, error) { + // 获取节点 + node, err := v.vfs.GetNodeByPath(request.Params.Path) + if err != nil { + return api.GetVFSNode404JSONResponse{ + PathNotFoundErrorJSONResponse: api.PathNotFoundErrorJSONResponse{ + Errtype: api.ErrorErrtypePathNotFound, + Message: err.Error(), + }, + }, nil + } + + switch node.Type { + case models.VfsNodeTypeDirectory: + // 处理目录类型 + entries, err := v.vfs.GetChildren(node.ID) + if err != nil { + return api.GetVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: err.Error(), + }, + }, nil + } + + var responseEntries []api.VFSDirectoryEntry + for _, entry := range entries { + responseEntries = append(responseEntries, api.VFSDirectoryEntry{ + Name: entry.Name, + Type: ModelType2ResponseType(entry.Type), + }) + } + return api.GetVFSNode200JSONResponse(responseEntries), nil + + // case models.VfsNodeTypeFile: + // // 处理文件类型,返回二进制数据 + // // 这里需要从vfs中获取文件内容 + // fileContent, err := v.vfs.GetFileContent(node.ID) // 假设有此方法 + // if err != nil { + // return api.GetVFSNode500JSONResponse{ + // ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + // Errtype: api.ErrorErrtypeInternalServerError, + // Message: err.Error(), + // }, + // }, nil + // } + // return api.GetVFSNode200ApplicationoctetStreamResponse{ + // Body: bytes.NewReader(fileContent), + // ContentLength: int64(len(fileContent)), + // }, nil + + case models.VfsNodeTypeService: + // 处理服务类型 + result, err := v.StrictProxy2Service(http.MethodGet, nil, node) + if err != nil { + return api.GetVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeServiceProxyError, + Message: err.Error(), + }, + }, nil + } + + return api.GetVFSNode200ApplicationoctetStreamResponse{ + Body: bytes.NewReader(result), + ContentLength: int64(len(result)), + }, nil + + default: + return api.GetVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Not a valid node type", + }, + }, nil + } +} + +// UpdateVFSNode implements server.StrictServerInterface. +func (v *VfsImpl) UpdateVFSNode(ctx context.Context, request api.UpdateVFSNodeRequestObject) (api.UpdateVFSNodeResponseObject, error) { + // 获取节点 + node, err := v.vfs.GetNodeByPath(request.Params.Path) + if err != nil { + return api.UpdateVFSNode404JSONResponse{ + PathNotFoundErrorJSONResponse: api.PathNotFoundErrorJSONResponse{ + Errtype: api.ErrorErrtypePathNotFound, + Message: err.Error(), + }, + }, nil + } + + // 根据操作类型进行不同处理 + switch request.Params.Op { + case api.Rename: + // 检查请求体 + if request.Body == nil { + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Request body is required for rename operation", + }, + }, nil + } + + newName := string(*request.Body) + if newName == "" { + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "New name cannot be empty", + }, + }, nil + } + + // FIXME: 检查新名称是否合法 + // 对于服务类型节点,可能需要保留特定后缀 + if node.Type == models.VfsNodeTypeService { + // 可以添加对服务节点重命名的特殊处理 + // 例如确保保留.service后缀 + } + + // 更新节点名称 + node.Name = newName + if err := v.vfs.UpdateVFSNode(node); err != nil { + return api.UpdateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to rename node: " + err.Error(), + }, + }, nil + } + + case api.Change: + // Change操作仅适用于文件和服务类型节点 + if node.Type != models.VfsNodeTypeFile && node.Type != models.VfsNodeTypeService { + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Change operation is only supported for file and service nodes", + }, + }, nil + } + + // FIXME: 性能问题 + var content []byte + if request.Body != nil { + content = []byte(*request.Body) + } + + // 对于服务节点,通过代理发送变更 + if node.Type == models.VfsNodeTypeService { + _, err := v.StrictProxy2Service(http.MethodPatch, content, node) + if err != nil { + return api.UpdateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeServiceProxyError, + Message: "Failed to proxy change to service: " + err.Error(), + }, + }, nil + } + } else { + // 对于文件节点,可能需要更新文件内容 + // 这里可以根据需要实现文件内容更新逻辑 + return api.UpdateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "File Opreation Not Supported", + }, + }, nil + } + + case api.Move: + // FIXME: 需要添加权限控制 + if request.Body == nil { + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Request body is required for move operation", + }, + }, nil + } + + targetPath := string(*request.Body) + if targetPath == "" { + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Target path cannot be empty", + }, + }, nil + } + + // 移动节点到新路径 + if err := v.vfs.MoveToPath(node, targetPath); err != nil { + return api.UpdateVFSNode500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to move node: " + err.Error(), + }, + }, nil + } + + case api.Copy: + fallthrough + // if request.Body == nil { + // return api.UpdateVFSNode400JSONResponse{ + // ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + // Errtype: api.ErrorErrtypeParameterError, + // Message: "Request body is required for copy operation", + // }, + // }, nil + // } + + // targetPath := string(*request.Body) + // if targetPath == "" { + // return api.UpdateVFSNode400JSONResponse{ + // ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + // Errtype: api.ErrorErrtypeParameterError, + // Message: "Target path cannot be empty", + // }, + // }, nil + // } + + // // 复制节点到新路径 + // if err := v.vfs.CopyToPath(node, targetPath); err != nil { + // return api.UpdateVFSNode500JSONResponse{ + // ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + // Errtype: api.ErrorErrtypeInternalServerError, + // Message: "Failed to copy node: " + err.Error(), + // }, + // }, nil + // } + + default: + return api.UpdateVFSNode400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeParameterError, + Message: "Unsupported operation type", + }, + }, nil + } + + // 返回更新后的节点信息 + return api.UpdateVFSNode200JSONResponse{ + Name: node.Name, + Type: ModelType2ResponseType(node.Type), + CreatedAt: node.CreatedAt, + UpdatedAt: node.UpdatedAt, + }, nil +} + +func ModelType2ResponseType(nodeType models.VfsNodeType) api.VFSNodeType { + var reponseType api.VFSNodeType + switch nodeType { + case models.VfsNodeTypeFile: + reponseType = api.File + case models.VfsNodeTypeDirectory: + reponseType = api.Directory + case models.VfsNodeTypeService: + reponseType = api.Service + } + return reponseType +} + +// CreateUser implements server.StrictServerInterface. +func (v *VfsImpl) CreateUser(ctx context.Context, request api.CreateUserRequestObject) (api.CreateUserResponseObject, error) { + // 从上下文中获取请求头信息 + // 注意:在严格模式下,我们需要从上下文获取请求信息 + // 但这里我们无法直接访问请求头,因此需要在中间件中处理或使用其他方式传递 + + // 由于严格模式接口限制,我们无法直接访问请求头 + // 这里假设我们通过其他方式验证了权限(比如中间件) + // token := c.GetHeader("X-VFS-Token") + // if token != v.config.RegisterToken || token != v.config.AdminToken { + // c.JSON(http.StatusForbidden, api.Error{ + // Errtype: "AccessDenied", + // Message: "Access denied", + // }) + // return + // } + + // 创建用户 + token, err := v.vfs.CreateUser(request.Username) + if err != nil { + // 检查是否是用户已存在的错误 + if err.Error() == "user already exists" { + return api.CreateUser400JSONResponse{ + ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{ + Errtype: api.ErrorErrtypeConflictError, + Message: "User already exists", + }, + }, nil + } + + return api.CreateUser500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to create user: " + err.Error(), + }, + }, nil + } + + // 为新用户添加角色 + _, err = v.enfocer.AddRoleForUser(request.Username, "user") + if err != nil { + log.Printf("Failed to add role for user %s: %v", request.Username, err) + // 注意:这里即使添加角色失败,我们也不会回滚用户创建 + // 因为用户创建已经成功,且这是两个独立的系统 + // FIXME: unknown + } + + // 保存策略 + v.enfocer.SavePolicy() + + // 返回带有token的响应 + return api.CreateUser201Response{ + Headers: api.CreateUser201ResponseHeaders{ + XVFSToken: token, + }, + }, nil +} + +// DeleteUser implements server.StrictServerInterface. +func (v *VfsImpl) DeleteUser(ctx context.Context, request api.DeleteUserRequestObject) (api.DeleteUserResponseObject, error) { + // 删除用户 + err := v.vfs.DeleteUser(request.Username) + if err != nil { + // 检查是否是用户未找到的错误 + if err.Error() == "user not found" { + return api.DeleteUser404JSONResponse{ + Errtype: api.ErrorErrtypeNotFoundError, + Message: "User not found", + }, nil + } + + return api.DeleteUser500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeInternalServerError, + Message: "Failed to delete user: " + err.Error(), + }, + }, nil + } + + // 从权限系统中移除用户 + // 移除用户的所有角色 + _, err = v.enfocer.DeleteRolesForUser(request.Username) + if err != nil { + log.Printf("Failed to delete roles for user %s: %v", request.Username, err) + // 注意:这里即使删除角色失败,我们也不会回滚用户删除 + // 因为数据库中的用户已经删除,且这是两个独立的系统 + // FIXME: unknown + } + + // 保存策略 + v.enfocer.SavePolicy() + + // 成功删除返回204状态 + return api.DeleteUser204Response{}, nil +} + +// Make sure we conform to ServerInterface +var _ api.StrictServerInterface = (*VfsImpl)(nil) diff --git a/internal/vfs/vfs_service.go b/internal/vfs/vfs_service.go index bf56310..81e175f 100644 --- a/internal/vfs/vfs_service.go +++ b/internal/vfs/vfs_service.go @@ -1,27 +1,26 @@ package vfs import ( + "fmt" "net/http" "strings" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" - "github.com/gin-gonic/gin" ) // ServiceProxy 服务代理接口 type ServiceProxy interface { // Get 从后端服务获取数据 - Get(c *gin.Context, servicePath string, node *models.VfsNode) (any, error) + Get(servicePath string, node *models.VfsNode) ([]byte, error) // Create 在后端服务创建资源 - Create(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) (string, error) // 返回创建的资源ID + Create(servicePath string, node *models.VfsNode, data []byte) ([]byte, error) // 返回创建的资源ID // Update 更新后端服务资源 - Update(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) error + Update(servicePath string, node *models.VfsNode, data []byte) error // Delete 删除后端服务资源 - Delete(c *gin.Context, servicePath string, node *models.VfsNode) error + Delete(servicePath string, node *models.VfsNode) error // GetName 获取代理名称 GetName() string @@ -60,96 +59,45 @@ func (v *VfsImpl) RegisterProxy(entry *ProxyEntry) { v.proxyTable = append(v.proxyTable, entry) } -// Proxy2Service 通用服务代理处理函数 -func (v *VfsImpl) Proxy2Service(c *gin.Context, node *models.VfsNode) bool { +func (v *VfsImpl) StrictProxy2Service(method string, data []byte, node *models.VfsNode) ([]byte, error) { exts := strings.Split(node.Name, ".") var serviceName = exts[1] // log.Println("Proxy2Service: ", serviceName) // 查找对应的代理 proxy := v.FindProxyByServiceName(serviceName) if proxy == nil { - c.JSON(http.StatusNotImplemented, api.Error{ - Errtype: "error", - Message: "Service proxy not found for: " + serviceName, - }) - return false + return nil, fmt.Errorf("service proxy not found for: %s", serviceName) } // 根据HTTP方法调用相应的代理方法 - switch c.Request.Method { + switch method { case http.MethodGet: - result, err := proxy.Get(c, serviceName, node) + result, err := proxy.Get(serviceName, node) if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "Failed to get service data: " + err.Error(), - }) - return false + return nil, fmt.Errorf("failed to get service data: %v", err) } - - c.JSON(http.StatusOK, result) - return true + return result, nil case http.MethodPost: // 读取请求体数据 - data, err := c.GetRawData() + result, err := proxy.Create(serviceName, node, data) if err != nil { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "error", - Message: "Failed to read request data: " + err.Error(), - }) - return false + return nil, fmt.Errorf("failed to create service resource: %v", err) } - - resourceID, err := proxy.Create(c, serviceName, node, data) - if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "Failed to create service resource: " + err.Error(), - }) - return false - } - - c.JSON(http.StatusCreated, gin.H{"resource_id": resourceID}) - return true + return result, nil case http.MethodPut, http.MethodPatch: // 读取请求体数据 - data, err := c.GetRawData() + err := proxy.Update(serviceName, node, data) if err != nil { - c.JSON(http.StatusBadRequest, api.Error{ - Errtype: "error", - Message: "Failed to read request data: " + err.Error(), - }) - return false + return nil, fmt.Errorf("failed to update service resource: %v", err) } - - err = proxy.Update(c, serviceName, node, data) - if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "Failed to update service resource: " + err.Error(), - }) - return false - } - - c.JSON(http.StatusOK, gin.H{"message": "Updated successfully"}) - return true + return nil, nil case http.MethodDelete: - err := proxy.Delete(c, serviceName, node) + err := proxy.Delete(serviceName, node) if err != nil { - c.JSON(http.StatusInternalServerError, api.Error{ - Errtype: "error", - Message: "Failed to delete service resource: " + err.Error(), - }) - return false + return nil, fmt.Errorf("failed to delete service resource: %v", err) } - - c.JSON(http.StatusNoContent, nil) - return true + return nil, nil default: - c.JSON(http.StatusMethodNotAllowed, api.Error{ - Errtype: "error", - Message: "Method not allowed", - }) - return false + return nil, fmt.Errorf("method not allowed") } } diff --git a/internal/vfs/vfsdriver/vfs_bookmark.go b/internal/vfs/vfsdriver/vfs_bookmark.go index 3930030..87aa47b 100644 --- a/internal/vfs/vfsdriver/vfs_bookmark.go +++ b/internal/vfs/vfsdriver/vfs_bookmark.go @@ -7,10 +7,9 @@ import ( "encoding/json" "fmt" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks" + api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks_client" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models" - "github.com/gin-gonic/gin" ) type VfsBookMarkService struct { @@ -32,44 +31,44 @@ func NewVfsBookMarkService(serverURL string) (*vfs.ProxyEntry, error) { } // Create implements ServiceProxy. -func (v *VfsBookMarkService) Create(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) (string, error) { +func (v *VfsBookMarkService) Create(servicePath string, node *models.VfsNode, data []byte) ([]byte, error) { ctx := context.Background() // 解析传入的数据为 BookmarkRequest var req api.BookmarkRequest if err := json.Unmarshal(data, &req); err != nil { - return "", err + return nil, err } // 调用 bookmark 服务创建书签 resp, err := v.client.CreateBookmarkWithResponse(ctx, int64(node.ID), req) if err != nil { - return "", err + return nil, err } // 处理响应 if resp.JSON201 != nil { - result, err := json.Marshal(resp.JSON201) + data, err := json.Marshal(resp.JSON201) if err != nil { - return "", err + return nil, fmt.Errorf("marshal response error: %w", err) } - return string(result), nil + return data, nil } // 处理错误情况 if resp.JSON400 != nil { - return "", fmt.Errorf("bad request: %s", resp.JSON400.Message) + return nil, fmt.Errorf("bad request: %s", resp.JSON400.Message) } if resp.JSON500 != nil { - return "", fmt.Errorf("server error: %s", resp.JSON500.Message) + return nil, fmt.Errorf("server error: %s", resp.JSON500.Message) } - return "", fmt.Errorf("unknown error") + return nil, fmt.Errorf("unknown error") } // Delete implements ServiceProxy. -func (v *VfsBookMarkService) Delete(c *gin.Context, servicePath string, node *models.VfsNode) error { +func (v *VfsBookMarkService) Delete(servicePath string, node *models.VfsNode) error { ctx := context.Background() // 调用 bookmark 服务删除书签 @@ -96,7 +95,7 @@ func (v *VfsBookMarkService) Delete(c *gin.Context, servicePath string, node *mo } // Get implements ServiceProxy. -func (v *VfsBookMarkService) Get(c *gin.Context, servicePath string, node *models.VfsNode) (any, error) { +func (v *VfsBookMarkService) Get(servicePath string, node *models.VfsNode) ([]byte, error) { ctx := context.Background() // 调用 bookmark 服务获取书签 @@ -107,7 +106,11 @@ func (v *VfsBookMarkService) Get(c *gin.Context, servicePath string, node *model // 处理响应 if resp.JSON200 != nil { - return resp.JSON200, nil + data, err := json.Marshal(resp.JSON200) + if err != nil { + return nil, fmt.Errorf("marshal response error: %w", err) + } + return data, nil } // 处理错误情况 @@ -124,7 +127,7 @@ func (v *VfsBookMarkService) GetName() string { } // Update implements ServiceProxy. -func (v *VfsBookMarkService) Update(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) error { +func (v *VfsBookMarkService) Update(servicePath string, node *models.VfsNode, data []byte) error { ctx := context.Background() // 解析传入的数据为 BookmarkRequest diff --git a/main.go b/main.go index aa302a8..e8d3ad8 100644 --- a/main.go +++ b/main.go @@ -6,7 +6,6 @@ import ( "log" "net/http" - api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/handlers" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs" "git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/vfsdriver" @@ -44,7 +43,7 @@ func main() { if server, err := vfs.NewVfsHandler(config); err != nil { log.Fatal("Failed to create bookmarks server:", err) } else { - api.RegisterHandlers(api_router, server) + vfs.RegisterVFSRoutes(api_router, server) // 示例:在你的服务初始化代码中 bookmarkService, err := vfsdriver.NewVfsBookMarkService("http://localhost:8081/api") // 替换为实际的 bookmark 服务地址 if err != nil { diff --git a/vfs_config.yaml b/vfs_config.yaml new file mode 100644 index 0000000..bdc6eb2 --- /dev/null +++ b/vfs_config.yaml @@ -0,0 +1,12 @@ +server: + address: localhost:8080 + mode: release +services: + bookmark: + url: http://localhost:8081/api +vfs: + admin_token: random_token + db_path: ./data/vfs.sqlite3 + debug: false + policy_path: ./data/policy.csv + regiser_token: random_token