From 429a863b76c343d15c4a27956a702119d7666cd9 Mon Sep 17 00:00:00 2001 From: zzy <2450266535@qq.com> Date: Sun, 28 Sep 2025 12:28:22 +0800 Subject: [PATCH] =?UTF-8?q?feat(bookmark):=20=E6=B7=BB=E5=8A=A0=20Authoriz?= =?UTF-8?q?ation=20=E8=AF=B7=E6=B1=82=E5=A4=B4=E6=94=AF=E6=8C=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 为 CORS 配置添加 `Authorization` 请求头,以支持携带认证信息的跨域请求。 feat(user-np): 改进错误信息和用户注册流程 - 在注册 VFS 服务失败时返回更详细的错误信息,包括状态码和响应体内容。 - 用户创建失败时返回具体的错误详情,并确保数据库记录被正确回滚。 - 注销用户时先尝试删除 VFS 服务中的相关资源,再执行数据库删除操作。 - 使用 `Unscoped().Delete()` 确保物理删除用户数据。 feat(vfs): 完善用户目录结构及节点创建逻辑 - 创建用户主目录 `/home/username` 及其子目录 `.Recycle_Bin`。 - 调整 `CreateVFSNode` 方法参数类型为值传递。 - 修复创建用户目录时传参不一致的问题。 - 删除用户时递归清理其在 VFS 中的所有节点,并通过代理删除关联的服务节点。 feat(mage): 新增生成 TypeScript 类型定义的任务 新增 `Gen_TS` Mage 任务用于将 OpenAPI 配置文件转换为 TypeScript 类型定义文件, 支持 `vfs`、`bookmark` 和 `user_np` 模块。 --- cmd/bookmark/main.go | 2 +- internal/bookmarks/user_np.go | 16 +++++++++++----- internal/vfs/models/vfs.go | 22 ++++++++++++++++++--- internal/vfs/vfs_impl.go | 36 ++++++++++++++++++++--------------- internal/vfs/vfs_user.go | 1 + magefile.go | 30 +++++++++++++++++++++++++++++ 6 files changed, 83 insertions(+), 24 deletions(-) create mode 100644 internal/vfs/vfs_user.go diff --git a/cmd/bookmark/main.go b/cmd/bookmark/main.go index 31c21eb..5ecd00d 100644 --- a/cmd/bookmark/main.go +++ b/cmd/bookmark/main.go @@ -18,7 +18,7 @@ func main() { config := cors.Config{ AllowAllOrigins: true, AllowMethods: []string{"GET", "POST", "PUT", "PATCH", "DELETE", "HEAD"}, - AllowHeaders: []string{"Origin", "Content-Length", "Content-Type"}, + AllowHeaders: []string{"Origin", "Content-Length", "Content-Type", "Authorization"}, } router.Use(cors.New(config)) diff --git a/internal/bookmarks/user_np.go b/internal/bookmarks/user_np.go index dca8ff7..cf4e395 100644 --- a/internal/bookmarks/user_np.go +++ b/internal/bookmarks/user_np.go @@ -57,7 +57,7 @@ func (u *UserNPImpl) RegisterVFSService(username, token string) (*string, error) } if reqs.StatusCode() != http.StatusCreated { - return nil, fmt.Errorf("failed to register vfs service: %s", reqs.Status()) + return nil, fmt.Errorf("failed to register vfs service: %s %s", reqs.Status(), string(reqs.Body)) } tokenHeader := reqs.HTTPResponse.Header.Get("X-VFS-Token") @@ -148,12 +148,13 @@ func (u *UserNPImpl) UserRegister(c *gin.Context, username string) { // 保存到数据库 if err := u.db.Create(&user).Error; err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "用户创建失败"}) + c.JSON(http.StatusInternalServerError, gin.H{"error": "用户创建失败", "detail": err.Error()}) + return } if token, err := u.RegisterVFSService(username, u.vfsToken); err != nil { - c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成访问令牌"}) - u.db.Delete(&user) + c.JSON(http.StatusInternalServerError, gin.H{"error": "无法生成访问令牌", "detail": err.Error()}) + u.db.Unscoped().Delete(&user) } else { user.Token = token u.db.Save(&user) @@ -182,7 +183,12 @@ func (u *UserNPImpl) DeleteUser(c *gin.Context, username string) { return } - u.db.Delete(&user) + if err := u.UnregisterVFSService(username, u.vfsToken); err != nil { + c.JSON(http.StatusInternalServerError, gin.H{"error": "无法注销访问令牌", "detail": err.Error()}) + return + } + + u.db.Unscoped().Delete(&user) c.JSON(http.StatusNoContent, nil) } diff --git a/internal/vfs/models/vfs.go b/internal/vfs/models/vfs.go index 6890272..c63fc9d 100644 --- a/internal/vfs/models/vfs.go +++ b/internal/vfs/models/vfs.go @@ -152,14 +152,14 @@ func (v *Vfs) CreateUser(username string) (string, error) { ParentID: 0, Type: VfsNodeTypeDirectory, } - if createHomeErr := v.CreateVFSNode(homeDir); createHomeErr != nil { + if createHomeErr := v.CreateVFSNode(*homeDir); createHomeErr != nil { err = createHomeErr return "", err } } // 创建用户目录 /home/username - userDir := &VfsNode{ + userDir := VfsNode{ Name: username, ParentID: homeDir.ID, Type: VfsNodeTypeDirectory, @@ -171,6 +171,22 @@ func (v *Vfs) CreateUser(username string) (string, error) { return "", err } + if userDir, getUserErr := v.GetNodeByParentIDAndName(homeDir.ID, username); getUserErr != nil { + panic(getUserErr) + } else { + dotDir := VfsNode{ + Name: ".Recycle_Bin", + ParentID: userDir.ID, + Type: VfsNodeTypeDirectory, + CreatedAt: time.Now(), + UpdatedAt: time.Now(), + } + if createDirErr := v.CreateVFSNode(dotDir); createDirErr != nil { + err = createDirErr + return "", err + } + } + return token, nil } @@ -540,7 +556,7 @@ func (v *Vfs) CheckNameValid(name string) error { return nil } -func (v *Vfs) CreateVFSNode(p *VfsNode) error { +func (v *Vfs) CreateVFSNode(p VfsNode) error { _, err := v.DB.Exec(` INSERT INTO vfs_nodes (name, parent_id, type, created_at, updated_at) VALUES (?, ?, ?, ?, ?)`, diff --git a/internal/vfs/vfs_impl.go b/internal/vfs/vfs_impl.go index 0d8dff8..39fd617 100644 --- a/internal/vfs/vfs_impl.go +++ b/internal/vfs/vfs_impl.go @@ -429,21 +429,6 @@ func ModelType2ResponseType(nodeType models.VfsNodeType) api.VFSNodeType { // 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 { @@ -506,6 +491,27 @@ func (v *VfsImpl) DeleteUser(ctx context.Context, request api.DeleteUserRequestO }, nil } + if userDir, err := v.vfs.GetNodeByPath("/home/" + request.Username + "/"); err != nil { + panic(err) + } else { + if serviceIDs, err := v.vfs.DeleteNodeRecursively(userDir.ID); err != nil { + panic(err) + } else { + for _, serviceID := range serviceIDs { + // 对于服务类型节点,通过代理删除 + _, err := v.StrictProxy2Service(http.MethodDelete, nil, v.vfs.GetVFSNode(serviceID)) + if err != nil { + return api.DeleteUser500JSONResponse{ + ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{ + Errtype: api.ErrorErrtypeServiceProxyError, + Message: err.Error(), + }, + }, nil + } + } + } + } + // 从权限系统中移除用户 // 移除用户的所有角色 _, err = v.enfocer.DeleteRolesForUser(request.Username) diff --git a/internal/vfs/vfs_user.go b/internal/vfs/vfs_user.go new file mode 100644 index 0000000..54c6094 --- /dev/null +++ b/internal/vfs/vfs_user.go @@ -0,0 +1 @@ +package vfs diff --git a/magefile.go b/magefile.go index 7536a9c..55cefb4 100644 --- a/magefile.go +++ b/magefile.go @@ -71,3 +71,33 @@ func Build_All() error { return nil } + +func Gen_TS() error { + // 创建输出目录 + if err := os.MkdirAll("./bin/ts", 0755); err != nil { + return fmt.Errorf("failed to create output directory: %w", err) + } + + // 定义需要生成 TypeScript 类型的配置 + configs := []struct { + Input string + Output string + }{ + {"config/vfs/vfs.yaml", "bin/ts/vfs.ts"}, + {"config/bookmark/bookmark.yaml", "bin/ts/bookmark.ts"}, + {"config/user_np/user_np.yaml", "bin/ts/user_np.ts"}, + } + + // 为每个配置生成 TypeScript 类型文件 + for _, cfg := range configs { + fmt.Printf("Generating TypeScript types for %s -> %s\n", cfg.Input, cfg.Output) + + cmd := exec.Command("openapi-typescript", + cfg.Input, "--output", cfg.Output) + if err := cmd.Run(); err != nil { + return fmt.Errorf("failed to generate TypeScript types for %s: %w", cfg.Input, err) + } + } + + return nil +}