在 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,确保服务间通信的安全性与权限控制。
506 lines
16 KiB
Go
506 lines
16 KiB
Go
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)
|