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

View File

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