Files
zzyxyz_go_api/internal/vfs/vfs.go
zzy 24f238f377 refactor(bookmark): 重构书签服务入口文件并整合用户权限功能
将 bookmark.go 重命名为 main.go,并调整包引用路径。将 bookmarks 和 user_np
两个模块的处理逻辑合并到同一个服务中,统一注册路由。同时更新了相关 API
的引用路径,确保生成代码与内部实现正确绑定。

此外,移除了独立的 user_np 服务入口文件,其功能已整合至 bookmark 服务中。

配置文件中调整了 user_np 和 vfs 服务的端口及部分接口定义,完善了用户
相关操作的路径参数和请求体结构。
2025-09-25 09:50:35 +08:00

361 lines
8.4 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 (
"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 {
vfs *models.Vfs
enfocer *casbin.Enforcer
config VFSConfig
proxyTable []*ProxyEntry // 动态代理表
proxyMutex sync.RWMutex // 保护代理表的读写锁
}
func NewVfsHandler(config *Config) (*VfsImpl, error) {
var err error
vfs, err := models.NewVfs(config.VFS.DbPath)
if err != nil {
return nil, err
}
var policyPath = config.VFS.PolicyPath
// 检查策略文件是否存在,如果不存在则创建
if _, err := os.Stat(policyPath); os.IsNotExist(err) {
// 确保目录存在
dir := filepath.Dir(policyPath)
if err := os.MkdirAll(dir, 0755); err != nil {
log.Fatalf("error: failed to create policy directory: %s", err)
}
// 创建空的策略文件
file, err := os.Create(policyPath)
if err != nil {
log.Fatalf("error: failed to create policy file: %s", err)
}
file.Close()
log.Printf("Created policy file: %s", policyPath)
}
a := fileadapter.NewAdapter(policyPath)
if a == nil {
log.Fatalf("error: adapter: %s", err)
}
m, err := model.NewModelFromString(CasbinModel)
if err != nil {
log.Fatalf("error: model: %s", err)
}
e, err := casbin.NewEnforcer(m, a)
if err != nil {
return nil, err
}
log.Printf("Admin Token: %s", config.VFS.AdminToken)
log.Printf("Register Token: %s", config.VFS.RegisterToken)
return &VfsImpl{
vfs: vfs,
enfocer: e,
config: config.VFS,
proxyTable: make([]*ProxyEntry, 0),
proxyMutex: sync.RWMutex{},
}, 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)