refactor(bookmark): 重构 bookmark 服务生成配置和内部引用

将 bookmark 和 user_np 服务的生成配置分离为 server 和 client 包,
并更新了相应的导入路径。同时更新了 OpenAPI 配置文件中的包名、输出路径及
启用 strict-server 模式以增强类型安全。

此外,同步更新了 VFS 服务的客户端和服务端生成配置,并完善了其 OpenAPI
错误响应定义与二进制内容支持,增强了 API 的规范性和健壮性。

修复了部分权限校验逻辑,并调整了中间件注册方式以适配新的严格模式接口。
This commit is contained in:
zzy
2025-09-25 16:15:34 +08:00
parent 24f238f377
commit b2cc27b8f5
18 changed files with 878 additions and 488 deletions

518
internal/vfs/vfs_impl.go Normal file
View File

@ -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)