Files
zzyxyz_go_api/internal/vfs/vfs_impl.go
zzy 9cab95c0f7 feat(bookmark): 添加 401 和 403 响应引用并完善错误定义
在 bookmark.yaml 配置文件中,为多个接口路径添加了 '401' 和 '403' 状态码的响应引用,
分别指向 components 中定义的 Unauthorized 和 Forbidden 响应。同时在 components
部分补充了 Forbidden 响应的定义,增强了 API 文档的完整性与规范性。

feat(user_np): 新增用户信息接口与基础错误结构定义

在 user_np.yaml 中新增了 /auth/info 路径下的 GET 和 PUT 接口,用于获取和保存用户信息。
同时,在 components 中定义了 ServerInternalError 响应和 Error 结构体,统一错误返回格式,
提升接口一致性与可维护性。

feat(vfs): 调整内容类型为 text/plain 并增强节点名称校验逻辑

将 vfs.yaml 中涉及二进制流传输的内容类型由 application/octet-stream 修改为 text/plain,
简化数据处理方式。同时在 vfs.go 模型中新增 CheckNameValid 方法,用于校验节点名称合法性,
防止非法字符(如斜杠)造成路径问题。

refactor(bookmark): 优化 API Key 验证逻辑并暴露更新时间字段

重构 BookMarksImpl 的 validateApiKey 函数,简化认证判断流程,并将 adminToken 从指针改为字符串常量。
此外,在 bookmarkModel2Res 函数中新增 UpdatedAt 字段,使书签响应包含更新时间信息。

feat(user_np): 实现用户信息相关接口占位函数

在 UserNPImpl 中新增 GetUserInfo 和 SaveUserInfo 两个方法的占位实现,为后续业务逻辑开发做好准备。

refactor(vfs): 使用文本请求体并加强服务节点操作校验

修改 vfs_impl.go 中读取请求体的方式,由 io.Reader 改为直接解引用文本内容,提升处理效率。
更新 CreateVFSNode、GetVFSNode 和 UpdateVFSNode 方法中对请求体和响应体的处理逻辑,
统一使用文本格式,增强代码一致性与健壮性。

feat(vfs): 为书签代理服务添加认证 Token 支持

在 vfs_bookmark.go 中为 VfsBookMarkService 结构体增加 token 字段,并在调用 bookmark 服务各接口时,
通过 HTTP 请求头设置 X-BookMark-Token,确保服务间通信的安全性与权限控制。
2025-09-26 14:42:22 +08:00

506 lines
16 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package vfs
import (
"context"
"fmt"
"log"
"net/http"
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs_server"
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
)
// TODO 重命名名称冲突以及合法名称检测以及JSONBody and TextBody的检测
// 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 = []byte(*request.Body)
// 创建节点 (可能需要传递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 {
// 回滚操作
delete_err := v.vfs.DeleteVFSNode(node)
log.Printf("service node: %s, err %s", node.Name, err.Error())
if delete_err != nil {
// FIXME: 需要解决这种原子性
return nil, fmt.Errorf("consistency error: %w", err)
}
return api.CreateVFSNode500JSONResponse{
ServerInternalErrorJSONResponse: api.ServerInternalErrorJSONResponse{
Errtype: api.ErrorErrtypeServiceProxyError,
Message: err.Error(),
},
}, nil
} else {
// 返回二进制数据响应
return api.CreateVFSNode201TextResponse(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.GetVFSNode200TextResponse(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.JSONBody == nil {
return api.UpdateVFSNode400JSONResponse{
ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{
Errtype: api.ErrorErrtypeParameterError,
Message: "Request body is required for rename operation",
},
}, nil
}
newName := string(*request.JSONBody)
if err := v.vfs.CheckNameValid(newName); err != nil {
return api.UpdateVFSNode400JSONResponse{
ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{
Errtype: api.ErrorErrtypeParameterError,
Message: err.Error(),
},
}, 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: 使用stream可能更好
var content []byte = []byte(*request.TextBody)
// 对于服务节点,通过代理发送变更
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.JSONBody == nil {
return api.UpdateVFSNode400JSONResponse{
ParameterErrorJSONResponse: api.ParameterErrorJSONResponse{
Errtype: api.ErrorErrtypeParameterError,
Message: "Request body is required for move operation",
},
}, nil
}
targetPath := string(*request.JSONBody)
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)