refactor(bookmark): 重构书签服务入口文件并整合用户权限功能

将 bookmark.go 重命名为 main.go,并调整包引用路径。将 bookmarks 和 user_np
两个模块的处理逻辑合并到同一个服务中,统一注册路由。同时更新了相关 API
的引用路径,确保生成代码与内部实现正确绑定。

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

配置文件中调整了 user_np 和 vfs 服务的端口及部分接口定义,完善了用户
相关操作的路径参数和请求体结构。
This commit is contained in:
zzy
2025-09-25 09:50:35 +08:00
parent 1e81e603de
commit 24f238f377
23 changed files with 1173 additions and 601 deletions

360
internal/vfs/vfs.go Normal file
View File

@ -0,0 +1,360 @@
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)