refactor(bookmark): 重构书签服务入口文件并整合用户权限功能
将 bookmark.go 重命名为 main.go,并调整包引用路径。将 bookmarks 和 user_np 两个模块的处理逻辑合并到同一个服务中,统一注册路由。同时更新了相关 API 的引用路径,确保生成代码与内部实现正确绑定。 此外,移除了独立的 user_np 服务入口文件,其功能已整合至 bookmark 服务中。 配置文件中调整了 user_np 和 vfs 服务的端口及部分接口定义,完善了用户 相关操作的路径参数和请求体结构。
This commit is contained in:
475
internal/vfs/models/vfs.go
Normal file
475
internal/vfs/models/vfs.go
Normal file
@ -0,0 +1,475 @@
|
||||
// vfs.go
|
||||
package models
|
||||
|
||||
import (
|
||||
"crypto/rand"
|
||||
"database/sql"
|
||||
"errors"
|
||||
"fmt"
|
||||
"path"
|
||||
"time"
|
||||
|
||||
_ "github.com/mattn/go-sqlite3"
|
||||
)
|
||||
|
||||
type Vfs struct {
|
||||
DB *sql.DB
|
||||
}
|
||||
|
||||
type VfsNodeType int
|
||||
|
||||
const (
|
||||
VfsNodeTypeFile VfsNodeType = iota
|
||||
VfsNodeTypeService
|
||||
VfsNodeTypeDirectory
|
||||
VfsNodeTypeSymlink
|
||||
)
|
||||
|
||||
type VfsNode struct {
|
||||
ID uint64
|
||||
Name string
|
||||
ParentID uint64
|
||||
Type VfsNodeType
|
||||
CreatedAt time.Time
|
||||
UpdatedAt time.Time
|
||||
}
|
||||
|
||||
type VfsUser struct {
|
||||
Name string
|
||||
Token string
|
||||
}
|
||||
|
||||
type VfsDirEntry struct {
|
||||
ID uint64
|
||||
Name string
|
||||
Type VfsNodeType
|
||||
}
|
||||
|
||||
// NewVfs 创建新的 VFS 实例
|
||||
func NewVfs(dbPath string) (*Vfs, error) {
|
||||
db, err := sql.Open("sqlite3", dbPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS vfs_nodes (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL,
|
||||
parent_id INTEGER,
|
||||
type INTEGER NOT NULL,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
|
||||
UNIQUE(parent_id, name)
|
||||
)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
_, err = db.Exec(`
|
||||
CREATE TABLE IF NOT EXISTS vfs_users (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
name TEXT NOT NULL UNIQUE,
|
||||
token TEXT NOT NULL UNIQUE,
|
||||
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
|
||||
)`)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = createInitialDirectories(db)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return &Vfs{DB: db}, nil
|
||||
}
|
||||
|
||||
// 创建初始目录结构
|
||||
func createInitialDirectories(db *sql.DB) error {
|
||||
var err error
|
||||
// 创建 /home 目录
|
||||
_, err = db.Exec(`
|
||||
INSERT OR IGNORE INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES ('home', 0, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
VfsNodeTypeDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 创建 /public 目录
|
||||
_, err = db.Exec(`
|
||||
INSERT OR IGNORE INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES ('public', 0, ?, CURRENT_TIMESTAMP, CURRENT_TIMESTAMP)`,
|
||||
VfsNodeTypeDirectory)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func generateToken() string {
|
||||
bytes := make([]byte, 16)
|
||||
if _, err := rand.Read(bytes); err != nil {
|
||||
// fallback to time-based token
|
||||
return fmt.Sprintf("%x", time.Now().UnixNano())
|
||||
}
|
||||
return fmt.Sprintf("%x", bytes)
|
||||
}
|
||||
|
||||
// 添加用户相关操作
|
||||
func (v *Vfs) CreateUser(username string) (string, error) {
|
||||
// 生成随机token
|
||||
token := generateToken()
|
||||
|
||||
// 检查用户是否已存在
|
||||
_, err := v.GetUserByName(username)
|
||||
if err == nil {
|
||||
return "", errors.New("user already exists")
|
||||
}
|
||||
|
||||
// 插入用户(不使用事务)
|
||||
_, err = v.DB.Exec("INSERT INTO vfs_users (name, token) VALUES (?, ?)", username, token)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 使用 defer 确保出错时能清理已创建的用户
|
||||
defer func() {
|
||||
if err != nil {
|
||||
v.DB.Exec("DELETE FROM vfs_users WHERE name = ? AND token = ?", username, token)
|
||||
}
|
||||
}()
|
||||
|
||||
// 确保 /home 目录存在
|
||||
homeDir, getHomeErr := v.GetNodeByPath("/home/")
|
||||
if getHomeErr != nil {
|
||||
// 如果 /home 不存在,创建它
|
||||
homeDir = &VfsNode{
|
||||
Name: "home",
|
||||
ParentID: 0,
|
||||
Type: VfsNodeTypeDirectory,
|
||||
}
|
||||
if createHomeErr := v.CreateVFSNode(homeDir); createHomeErr != nil {
|
||||
err = createHomeErr
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// 创建用户目录 /home/username
|
||||
userDir := &VfsNode{
|
||||
Name: username,
|
||||
ParentID: homeDir.ID,
|
||||
Type: VfsNodeTypeDirectory,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
if createDirErr := v.CreateVFSNode(userDir); createDirErr != nil {
|
||||
err = createDirErr
|
||||
return "", err
|
||||
}
|
||||
|
||||
return token, nil
|
||||
}
|
||||
|
||||
func (v *Vfs) DeleteUser(username string) error {
|
||||
// TODO: 递归删除用户相关文件
|
||||
// 这里暂时只删除用户记录
|
||||
_, err := v.DB.Exec("DELETE FROM vfs_users WHERE name = ?", username)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Vfs) GetUserByToken(token string) (*VfsUser, error) {
|
||||
user := &VfsUser{}
|
||||
err := v.DB.QueryRow("SELECT name, token FROM vfs_users WHERE token = ?", token).
|
||||
Scan(&user.Name, &user.Token)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
func (v *Vfs) GetUserByName(username string) (*VfsUser, error) {
|
||||
user := &VfsUser{}
|
||||
err := v.DB.QueryRow("SELECT name, token FROM vfs_users WHERE name = ?", username).
|
||||
Scan(&user.Name, &user.Token)
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.New("user not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
return user, nil
|
||||
}
|
||||
|
||||
// GetChildrenID 获取目录下所有子项的 ID
|
||||
func (v *Vfs) GetChildren(parentID uint64) ([]VfsDirEntry, error) {
|
||||
rows, err := v.DB.Query("SELECT id, name, type FROM vfs_nodes WHERE parent_id = ?", parentID)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
defer rows.Close()
|
||||
|
||||
var dirEntrys []VfsDirEntry
|
||||
for rows.Next() {
|
||||
var entry VfsDirEntry
|
||||
if err := rows.Scan(&entry.ID, &entry.Name, &entry.Type); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
dirEntrys = append(dirEntrys, entry)
|
||||
}
|
||||
|
||||
return dirEntrys, nil
|
||||
}
|
||||
|
||||
// GetParentID 根据父路径查找父节点 ID
|
||||
// parentPath 应该是 ParsePathComponents 的第一个返回值
|
||||
func (v *Vfs) GetParentID(parentPath string) (uint64, error) {
|
||||
// 根目录特殊处理
|
||||
if parentPath == "/" || parentPath == "" {
|
||||
return 0, nil
|
||||
}
|
||||
|
||||
// 递归查找父路径ID
|
||||
// 先解析父路径的父路径和节点名
|
||||
grandParentPath, parentName, _, err := ParsePathComponents(parentPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 递归获取祖父节点ID
|
||||
grandParentID, err := v.GetParentID(grandParentPath)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
|
||||
// 在祖父节点下查找父节点
|
||||
var parentID uint64
|
||||
err = v.DB.QueryRow("SELECT id FROM vfs_nodes WHERE parent_id = ? AND name = ?",
|
||||
grandParentID, parentName).Scan(&parentID)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return 0, errors.New("parent path not found")
|
||||
}
|
||||
return 0, err
|
||||
}
|
||||
|
||||
return parentID, nil
|
||||
}
|
||||
|
||||
// GetNodeByPath 根据完整路径查找节点
|
||||
func (v *Vfs) GetNodeByPath(fullPath string) (*VfsNode, error) {
|
||||
// 根目录特殊处理
|
||||
if path.Clean(fullPath) == "/" {
|
||||
return &VfsNode{
|
||||
ID: 0,
|
||||
Name: "/",
|
||||
ParentID: 0,
|
||||
Type: VfsNodeTypeDirectory,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}, nil
|
||||
}
|
||||
|
||||
// 使用 ParsePathComponents 解析路径
|
||||
parentPath, nodeName, nodeType, err := ParsePathComponents(fullPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取父节点ID
|
||||
parentID, err := v.GetParentID(parentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 根据 parentID, nodeName, nodeType 查找或创建节点
|
||||
node, err := v.GetNodeByParentIDAndName(parentID, nodeName)
|
||||
if err != nil {
|
||||
// 如果节点不存在,可以选择创建它或者返回错误
|
||||
// 这里根据你的需求决定是返回错误还是创建节点
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if node.Type != nodeType {
|
||||
return nil, errors.New("node type mismatch")
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// GetNodeByParentIDAndName 根据父ID和名称查找节点
|
||||
func (v *Vfs) GetNodeByParentIDAndName(parentID uint64, name string) (*VfsNode, error) {
|
||||
node := &VfsNode{}
|
||||
err := v.DB.QueryRow(`
|
||||
SELECT id, name, parent_id, type, created_at, updated_at
|
||||
FROM vfs_nodes
|
||||
WHERE parent_id = ? AND name = ?`, parentID, name).Scan(
|
||||
&node.ID, &node.Name, &node.ParentID, &node.Type, &node.CreatedAt, &node.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
if err == sql.ErrNoRows {
|
||||
return nil, errors.New("node not found")
|
||||
}
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return node, nil
|
||||
}
|
||||
|
||||
// CreateNodeByComponents 根据路径组件创建节点
|
||||
func (v *Vfs) CreateNodeByComponents(parentPath, nodeName string, nodeType VfsNodeType) (*VfsNode, error) {
|
||||
// 获取父节点ID
|
||||
parentID, err := v.GetParentID(parentPath)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 创建新节点
|
||||
node := &VfsNode{
|
||||
Name: nodeName,
|
||||
ParentID: parentID,
|
||||
Type: nodeType,
|
||||
CreatedAt: time.Now(),
|
||||
UpdatedAt: time.Now(),
|
||||
}
|
||||
|
||||
// 保存到数据库
|
||||
result, err := v.DB.Exec(`
|
||||
INSERT INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
node.Name, node.ParentID, node.Type, node.CreatedAt, node.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 获取插入的ID
|
||||
id, err := result.LastInsertId()
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
node.ID = uint64(id)
|
||||
return node, nil
|
||||
}
|
||||
|
||||
func ParsePathComponents(pathStr string) (parentPath, nodeName string, nodeType VfsNodeType, err error) {
|
||||
abspath := path.Clean(pathStr)
|
||||
if !path.IsAbs(abspath) {
|
||||
return "", "", 0, errors.New("path must be absolute")
|
||||
}
|
||||
|
||||
// 特殊处理根路径
|
||||
if abspath == "/" {
|
||||
return "", "", VfsNodeTypeDirectory, nil
|
||||
}
|
||||
|
||||
// 判断是文件还是目录
|
||||
nodeType = VfsNodeTypeFile
|
||||
// 如果原始路径以 / 结尾,则为目录
|
||||
if len(pathStr) > 1 && pathStr[len(pathStr)-1] == '/' {
|
||||
nodeType = VfsNodeTypeDirectory
|
||||
}
|
||||
if nodeType == VfsNodeTypeFile && path.Ext(pathStr) == ".api" {
|
||||
nodeType = VfsNodeTypeService
|
||||
}
|
||||
|
||||
// 分割路径
|
||||
parentPath, nodeName = path.Split(abspath)
|
||||
|
||||
// 清理父路径
|
||||
if parentPath != "/" && parentPath != "" {
|
||||
parentPath = path.Clean(parentPath)
|
||||
}
|
||||
|
||||
// 处理特殊情况
|
||||
if parentPath == "." {
|
||||
parentPath = "/"
|
||||
}
|
||||
|
||||
return parentPath, nodeName, nodeType, nil
|
||||
}
|
||||
|
||||
// MoveToPath 将节点移动到指定路径
|
||||
func (v *Vfs) MoveToPath(node *VfsNode, destPath string) error {
|
||||
// 1. 解析目标路径
|
||||
parentPath, nodeName, _, err := ParsePathComponents(destPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("invalid destination path: %w", err)
|
||||
}
|
||||
|
||||
// 2. 查找目标父节点
|
||||
parentID, err := v.GetParentID(parentPath)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to find parent directory '%s': %w", parentPath, err)
|
||||
}
|
||||
|
||||
// 3. 检查目标位置是否已存在同名节点
|
||||
_, err = v.GetNodeByParentIDAndName(parentID, nodeName)
|
||||
if err == nil {
|
||||
return fmt.Errorf("destination path '%s' already exists", destPath)
|
||||
}
|
||||
if err.Error() != "node not found" {
|
||||
return fmt.Errorf("error checking destination path: %w", err)
|
||||
}
|
||||
|
||||
// 4. 更新节点的父节点ID和名称
|
||||
node.ParentID = parentID
|
||||
node.Name = nodeName
|
||||
node.UpdatedAt = time.Now()
|
||||
|
||||
// 5. 更新数据库中的节点信息
|
||||
_, err = v.DB.Exec(`
|
||||
UPDATE vfs_nodes
|
||||
SET name = ?, parent_id = ?, updated_at = ?
|
||||
WHERE id = ?`,
|
||||
node.Name, node.ParentID, node.UpdatedAt, node.ID)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to update node: %w", err)
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *Vfs) CreateVFSNode(p *VfsNode) error {
|
||||
_, err := v.DB.Exec(`
|
||||
INSERT INTO vfs_nodes (name, parent_id, type, created_at, updated_at)
|
||||
VALUES (?, ?, ?, ?, ?)`,
|
||||
p.Name, p.ParentID, p.Type, time.Now(), time.Now())
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Vfs) DeleteVFSNode(p *VfsNode) error {
|
||||
_, err := v.DB.Exec("DELETE FROM vfs_nodes WHERE id = ?", p.ID)
|
||||
return err
|
||||
}
|
||||
|
||||
func (v *Vfs) GetVFSNode(p *VfsNode) *VfsNode {
|
||||
node := &VfsNode{}
|
||||
err := v.DB.QueryRow(`
|
||||
SELECT id, name, parent_id, type, created_at, updated_at
|
||||
FROM vfs_nodes
|
||||
WHERE id = ?`, p.ID).Scan(
|
||||
&node.ID, &node.Name, &node.ParentID, &node.Type, &node.CreatedAt, &node.UpdatedAt)
|
||||
|
||||
if err != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (v *Vfs) UpdateVFSNode(p *VfsNode) error {
|
||||
_, err := v.DB.Exec(`
|
||||
UPDATE vfs_nodes
|
||||
SET name = ?, parent_id = ?, type = ?, updated_at = ?
|
||||
WHERE id = ?`,
|
||||
p.Name, p.ParentID, p.Type, time.Now(), p.ID)
|
||||
return err
|
||||
}
|
129
internal/vfs/models/vfs_test.go
Normal file
129
internal/vfs/models/vfs_test.go
Normal file
@ -0,0 +1,129 @@
|
||||
package models_test
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
)
|
||||
|
||||
func TestParsePathComponents(t *testing.T) {
|
||||
tests := []struct {
|
||||
name string
|
||||
pathStr string
|
||||
wantParent string
|
||||
wantName string
|
||||
wantNodeType models.VfsNodeType
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "directory path with trailing slash",
|
||||
pathStr: "/home/",
|
||||
wantParent: "/",
|
||||
wantName: "home",
|
||||
wantNodeType: models.VfsNodeTypeDirectory,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "file path without extension",
|
||||
pathStr: "/home",
|
||||
wantParent: "/",
|
||||
wantName: "home",
|
||||
wantNodeType: models.VfsNodeTypeFile,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "file path with extension",
|
||||
pathStr: "/home.txt",
|
||||
wantParent: "/",
|
||||
wantName: "home.txt",
|
||||
wantNodeType: models.VfsNodeTypeFile,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nested directory path with trailing slash",
|
||||
pathStr: "/home/user/",
|
||||
wantParent: "/home",
|
||||
wantName: "user",
|
||||
wantNodeType: models.VfsNodeTypeDirectory,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "nested file path",
|
||||
pathStr: "/home/user/file.txt",
|
||||
wantParent: "/home/user",
|
||||
wantName: "file.txt",
|
||||
wantNodeType: models.VfsNodeTypeFile,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "deep nested directory path with trailing slash",
|
||||
pathStr: "/home/user/documents/",
|
||||
wantParent: "/home/user",
|
||||
wantName: "documents",
|
||||
wantNodeType: models.VfsNodeTypeDirectory,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "deep nested file path",
|
||||
pathStr: "/home/user/documents/file.txt",
|
||||
wantParent: "/home/user/documents",
|
||||
wantName: "file.txt",
|
||||
wantNodeType: models.VfsNodeTypeFile,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "relative path should error",
|
||||
pathStr: ".",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative path should error",
|
||||
pathStr: "home.txt",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "relative path should error",
|
||||
pathStr: "home/user/",
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "path with multiple slashes",
|
||||
pathStr: "//home//user//",
|
||||
wantParent: "/home",
|
||||
wantName: "user",
|
||||
wantNodeType: models.VfsNodeTypeDirectory,
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "path with multiple slashes and file",
|
||||
pathStr: "//home//user//file.txt",
|
||||
wantParent: "/home/user",
|
||||
wantName: "file.txt",
|
||||
wantNodeType: models.VfsNodeTypeFile,
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
parent, name, nodeType, err := models.ParsePathComponents(tt.pathStr)
|
||||
|
||||
if (err != nil) != tt.wantErr {
|
||||
t.Errorf("ParsePathComponents() error = %v, wantErr %v", err, tt.wantErr)
|
||||
return
|
||||
}
|
||||
|
||||
if !tt.wantErr {
|
||||
if parent != tt.wantParent {
|
||||
t.Errorf("ParsePathComponents() parent = %v, want %v", parent, tt.wantParent)
|
||||
}
|
||||
if name != tt.wantName {
|
||||
t.Errorf("ParsePathComponents() name = %v, want %v", name, tt.wantName)
|
||||
}
|
||||
if nodeType != tt.wantNodeType {
|
||||
t.Errorf("ParsePathComponents() nodeType = %v, want %v", nodeType, tt.wantNodeType)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
360
internal/vfs/vfs.go
Normal file
360
internal/vfs/vfs.go
Normal 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)
|
89
internal/vfs/vfs_auth.go
Normal file
89
internal/vfs/vfs_auth.go
Normal file
@ -0,0 +1,89 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
_ "embed"
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
//go:embed vfs_model.conf
|
||||
var CasbinModel string
|
||||
|
||||
func NewVfsPermission() (*api.GinServerOptions, error) {
|
||||
return &api.GinServerOptions{
|
||||
Middlewares: []api.MiddlewareFunc{VfsMiddleware()},
|
||||
}, nil
|
||||
}
|
||||
|
||||
func (v *VfsImpl) CheckPermission(token, path, action string) (bool, error) {
|
||||
// 根据 token 获取用户信息
|
||||
user, err := v.vfs.GetUserByToken(token)
|
||||
if err != nil {
|
||||
// 匿名用户
|
||||
user = &models.VfsUser{Name: "", Token: ""}
|
||||
}
|
||||
|
||||
// 特殊处理:admin 用户拥有所有权限
|
||||
if token == v.config.AdminToken && len(token) != 0 { // admin 用户拥有所有权限
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 允许任何人读取 public 目录
|
||||
if strings.HasPrefix(path, "/public") && action == "GET" {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 如果是普通用户访问自己的主目录,则允许
|
||||
if user.Name != "" && strings.HasPrefix(path, "/home/"+user.Name) {
|
||||
return true, nil
|
||||
}
|
||||
|
||||
// 构造 Casbin 请求
|
||||
// 对于普通用户,需要将策略中的 {{username}} 替换为实际用户名
|
||||
obj := path
|
||||
sub := user.Name
|
||||
// 使用 Casbin 检查权限
|
||||
allowed, err := v.enfocer.Enforce(sub, obj, action)
|
||||
if err != nil {
|
||||
return false, err
|
||||
}
|
||||
|
||||
return allowed, nil
|
||||
}
|
||||
|
||||
func (v *VfsImpl) CheckPermissionMiddleware(c *gin.Context, path string) bool {
|
||||
token := c.GetHeader("X-VFS-Token")
|
||||
allowed, err := v.CheckPermission(token, path, c.Request.Method)
|
||||
// log.Println("CheckPermission:", allowed, err)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "PermissionCheckError",
|
||||
Message: "Failed to check permission: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
if !allowed {
|
||||
c.JSON(http.StatusForbidden, api.Error{
|
||||
Errtype: "AccessDenied",
|
||||
Message: "Access denied",
|
||||
})
|
||||
return false
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func VfsMiddleware() api.MiddlewareFunc {
|
||||
return func(c *gin.Context) {
|
||||
// 检查当前请求是否需要认证
|
||||
if _, exists := c.Get(api.ApiKeyAuthScopes); exists {
|
||||
// 提取 API Key
|
||||
apiKey := c.GetHeader("X-VFS-Token")
|
||||
c.Set(api.ApiKeyAuthScopes, apiKey)
|
||||
}
|
||||
c.Next()
|
||||
}
|
||||
}
|
75
internal/vfs/vfs_config.go
Normal file
75
internal/vfs/vfs_config.go
Normal file
@ -0,0 +1,75 @@
|
||||
// internal/handlers/vfs/vfs_config.go
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"log"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type Config struct {
|
||||
Server ServerConfig `mapstructure:"server"`
|
||||
VFS VFSConfig `mapstructure:"vfs"`
|
||||
Services ServicesConfig `mapstructure:"services"`
|
||||
}
|
||||
|
||||
type ServerConfig struct {
|
||||
Address string `mapstructure:"address"`
|
||||
Mode string `mapstructure:"mode"`
|
||||
}
|
||||
|
||||
type VFSConfig struct {
|
||||
DbPath string `mapstructure:"db_path"`
|
||||
PolicyPath string `mapstructure:"policy_path"`
|
||||
AdminToken string `mapstructure:"admin_token"`
|
||||
RegisterToken string `mapstructure:"regiser_token"`
|
||||
Debug bool `mapstructure:"debug"`
|
||||
}
|
||||
|
||||
type ServicesConfig struct {
|
||||
Bookmark BookmarkServiceConfig `mapstructure:"bookmark"`
|
||||
}
|
||||
|
||||
type BookmarkServiceConfig struct {
|
||||
URL string `mapstructure:"url"`
|
||||
}
|
||||
|
||||
func genrateRandomToken(length int) string {
|
||||
// 生成随机字符串
|
||||
return "random_token"
|
||||
}
|
||||
|
||||
// LoadConfig 加载配置
|
||||
func LoadConfig() (*Config, error) {
|
||||
viper.SetConfigName("vfs_config")
|
||||
viper.SetConfigType("yaml")
|
||||
viper.AddConfigPath(".")
|
||||
viper.AddConfigPath("./config")
|
||||
viper.AddConfigPath("config")
|
||||
|
||||
// 设置默认值
|
||||
viper.SetDefault("server.address", "localhost:8080")
|
||||
viper.SetDefault("server.mode", "release")
|
||||
viper.SetDefault("vfs.db_path", "./data/vfs.sqlite3")
|
||||
viper.SetDefault("vfs.policy_path", "./data/policy.csv")
|
||||
viper.SetDefault("vfs.debug", false)
|
||||
viper.SetDefault("services.bookmark.url", "http://localhost:8081/api")
|
||||
|
||||
viper.SetDefault("vfs.admin_token", genrateRandomToken(64))
|
||||
viper.SetDefault("vfs.regiser_token", genrateRandomToken(64))
|
||||
// 自动读取环境变量
|
||||
viper.AutomaticEnv()
|
||||
|
||||
// 读取配置文件
|
||||
if err := viper.ReadInConfig(); err != nil {
|
||||
log.Printf("Warning: unable to read config file: %v. Using defaults and environment variables.", err)
|
||||
}
|
||||
|
||||
var config Config
|
||||
if err := viper.Unmarshal(&config); err != nil {
|
||||
return nil, fmt.Errorf("unable to decode into struct: %v", err)
|
||||
}
|
||||
|
||||
return &config, nil
|
||||
}
|
14
internal/vfs/vfs_model.conf
Normal file
14
internal/vfs/vfs_model.conf
Normal file
@ -0,0 +1,14 @@
|
||||
[request_definition]
|
||||
r = sub, obj, act
|
||||
|
||||
[policy_definition]
|
||||
p = sub, obj, act, eft
|
||||
|
||||
[role_definition]
|
||||
g = _, _
|
||||
|
||||
[policy_effect]
|
||||
e = some(where (p.eft == allow)) && !some(where (p.eft == deny))
|
||||
|
||||
[matchers]
|
||||
m = g(r.sub, p.sub) && r.obj == p.obj && r.act == p.act
|
155
internal/vfs/vfs_service.go
Normal file
155
internal/vfs/vfs_service.go
Normal file
@ -0,0 +1,155 @@
|
||||
package vfs
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
"strings"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
// ServiceProxy 服务代理接口
|
||||
type ServiceProxy interface {
|
||||
// Get 从后端服务获取数据
|
||||
Get(c *gin.Context, servicePath string, node *models.VfsNode) (any, error)
|
||||
|
||||
// Create 在后端服务创建资源
|
||||
Create(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) (string, error) // 返回创建的资源ID
|
||||
|
||||
// Update 更新后端服务资源
|
||||
Update(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) error
|
||||
|
||||
// Delete 删除后端服务资源
|
||||
Delete(c *gin.Context, servicePath string, node *models.VfsNode) error
|
||||
|
||||
// GetName 获取代理名称
|
||||
GetName() string
|
||||
}
|
||||
|
||||
// ProxyEntry 代理表条目
|
||||
type ProxyEntry struct {
|
||||
Name string
|
||||
MatchExt string
|
||||
Proxy ServiceProxy // 对应的代理实现
|
||||
}
|
||||
|
||||
// FindProxyByServiceName 根据服务节点名称查找对应的代理
|
||||
func (v *VfsImpl) FindProxyByServiceName(serviceName string) ServiceProxy {
|
||||
v.proxyMutex.RLock()
|
||||
defer v.proxyMutex.RUnlock()
|
||||
|
||||
if serviceName == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 根据服务名称匹配前缀
|
||||
for _, entry := range v.proxyTable {
|
||||
if entry.MatchExt == serviceName {
|
||||
return entry.Proxy
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func (v *VfsImpl) RegisterProxy(entry *ProxyEntry) {
|
||||
v.proxyMutex.Lock()
|
||||
defer v.proxyMutex.Unlock()
|
||||
|
||||
v.proxyTable = append(v.proxyTable, entry)
|
||||
}
|
||||
|
||||
// Proxy2Service 通用服务代理处理函数
|
||||
func (v *VfsImpl) Proxy2Service(c *gin.Context, node *models.VfsNode) bool {
|
||||
exts := strings.Split(node.Name, ".")
|
||||
var serviceName = exts[1]
|
||||
// log.Println("Proxy2Service: ", serviceName)
|
||||
// 查找对应的代理
|
||||
proxy := v.FindProxyByServiceName(serviceName)
|
||||
if proxy == nil {
|
||||
c.JSON(http.StatusNotImplemented, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Service proxy not found for: " + serviceName,
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
// 根据HTTP方法调用相应的代理方法
|
||||
switch c.Request.Method {
|
||||
case http.MethodGet:
|
||||
result, err := proxy.Get(c, serviceName, node)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to get service data: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, result)
|
||||
return true
|
||||
case http.MethodPost:
|
||||
// 读取请求体数据
|
||||
data, err := c.GetRawData()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to read request data: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
resourceID, err := proxy.Create(c, serviceName, node, data)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to create service resource: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusCreated, gin.H{"resource_id": resourceID})
|
||||
return true
|
||||
case http.MethodPut, http.MethodPatch:
|
||||
// 读取请求体数据
|
||||
data, err := c.GetRawData()
|
||||
if err != nil {
|
||||
c.JSON(http.StatusBadRequest, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to read request data: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
err = proxy.Update(c, serviceName, node, data)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to update service resource: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusOK, gin.H{"message": "Updated successfully"})
|
||||
return true
|
||||
case http.MethodDelete:
|
||||
err := proxy.Delete(c, serviceName, node)
|
||||
if err != nil {
|
||||
c.JSON(http.StatusInternalServerError, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Failed to delete service resource: " + err.Error(),
|
||||
})
|
||||
return false
|
||||
}
|
||||
|
||||
c.JSON(http.StatusNoContent, nil)
|
||||
return true
|
||||
default:
|
||||
c.JSON(http.StatusMethodNotAllowed, api.Error{
|
||||
Errtype: "error",
|
||||
Message: "Method not allowed",
|
||||
})
|
||||
return false
|
||||
}
|
||||
}
|
163
internal/vfs/vfsdriver/vfs_bookmark.go
Normal file
163
internal/vfs/vfsdriver/vfs_bookmark.go
Normal file
@ -0,0 +1,163 @@
|
||||
// internal/handlers/vfs_driver/vfs_bookmark.go
|
||||
|
||||
package vfsdriver
|
||||
|
||||
import (
|
||||
"context"
|
||||
"encoding/json"
|
||||
"fmt"
|
||||
|
||||
api "git.zzyxyz.com/zzy/zzyxyz_go_api/gen/bookmarks"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs"
|
||||
"git.zzyxyz.com/zzy/zzyxyz_go_api/internal/vfs/models"
|
||||
"github.com/gin-gonic/gin"
|
||||
)
|
||||
|
||||
type VfsBookMarkService struct {
|
||||
client *api.ClientWithResponses
|
||||
}
|
||||
|
||||
func NewVfsBookMarkService(serverURL string) (*vfs.ProxyEntry, error) {
|
||||
client, err := api.NewClientWithResponses(serverURL)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
ret := vfs.ProxyEntry{
|
||||
Name: "bookmark",
|
||||
MatchExt: "bk",
|
||||
Proxy: &VfsBookMarkService{client: client},
|
||||
}
|
||||
return &ret, nil
|
||||
}
|
||||
|
||||
// Create implements ServiceProxy.
|
||||
func (v *VfsBookMarkService) Create(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) (string, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 解析传入的数据为 BookmarkRequest
|
||||
var req api.BookmarkRequest
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 调用 bookmark 服务创建书签
|
||||
resp, err := v.client.CreateBookmarkWithResponse(ctx, int64(node.ID), req)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
if resp.JSON201 != nil {
|
||||
result, err := json.Marshal(resp.JSON201)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return string(result), nil
|
||||
}
|
||||
|
||||
// 处理错误情况
|
||||
if resp.JSON400 != nil {
|
||||
return "", fmt.Errorf("bad request: %s", resp.JSON400.Message)
|
||||
}
|
||||
|
||||
if resp.JSON500 != nil {
|
||||
return "", fmt.Errorf("server error: %s", resp.JSON500.Message)
|
||||
}
|
||||
|
||||
return "", fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
// Delete implements ServiceProxy.
|
||||
func (v *VfsBookMarkService) Delete(c *gin.Context, servicePath string, node *models.VfsNode) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 调用 bookmark 服务删除书签
|
||||
resp, err := v.client.DeleteBookmarkWithResponse(ctx, int64(node.ID))
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
if resp.StatusCode() == 204 {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理错误情况
|
||||
if resp.JSON404 != nil {
|
||||
return fmt.Errorf("not found: %s", resp.JSON404.Message)
|
||||
}
|
||||
|
||||
if resp.JSON500 != nil {
|
||||
return fmt.Errorf("server error: %s", resp.JSON500.Message)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
// Get implements ServiceProxy.
|
||||
func (v *VfsBookMarkService) Get(c *gin.Context, servicePath string, node *models.VfsNode) (any, error) {
|
||||
ctx := context.Background()
|
||||
|
||||
// 调用 bookmark 服务获取书签
|
||||
resp, err := v.client.GetBookmarkWithResponse(ctx, int64(node.ID))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
if resp.JSON200 != nil {
|
||||
return resp.JSON200, nil
|
||||
}
|
||||
|
||||
// 处理错误情况
|
||||
if resp.JSON404 != nil {
|
||||
return nil, fmt.Errorf("not found: %s", resp.JSON404.Message)
|
||||
}
|
||||
|
||||
return nil, fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
// GetName implements ServiceProxy.
|
||||
func (v *VfsBookMarkService) GetName() string {
|
||||
return "bookmark"
|
||||
}
|
||||
|
||||
// Update implements ServiceProxy.
|
||||
func (v *VfsBookMarkService) Update(c *gin.Context, servicePath string, node *models.VfsNode, data []byte) error {
|
||||
ctx := context.Background()
|
||||
|
||||
// 解析传入的数据为 BookmarkRequest
|
||||
var req api.BookmarkRequest
|
||||
if err := json.Unmarshal(data, &req); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 调用 bookmark 服务更新书签
|
||||
resp, err := v.client.UpdateBookmarkWithResponse(ctx, int64(node.ID), req)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// 处理响应
|
||||
if resp.JSON200 != nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
// 处理错误情况
|
||||
if resp.JSON400 != nil {
|
||||
return fmt.Errorf("bad request: %s", resp.JSON400.Message)
|
||||
}
|
||||
|
||||
if resp.JSON404 != nil {
|
||||
return fmt.Errorf("not found: %s", resp.JSON404.Message)
|
||||
}
|
||||
|
||||
if resp.JSON500 != nil {
|
||||
return fmt.Errorf("server error: %s", resp.JSON500.Message)
|
||||
}
|
||||
|
||||
return fmt.Errorf("unknown error")
|
||||
}
|
||||
|
||||
var _ vfs.ServiceProxy = (*VfsBookMarkService)(nil)
|
Reference in New Issue
Block a user