Files
zzyxyz_go_api/internal/vfs/models/vfs.go
zzy 429a863b76 feat(bookmark): 添加 Authorization 请求头支持
为 CORS 配置添加 `Authorization` 请求头,以支持携带认证信息的跨域请求。

feat(user-np): 改进错误信息和用户注册流程

- 在注册 VFS 服务失败时返回更详细的错误信息,包括状态码和响应体内容。
- 用户创建失败时返回具体的错误详情,并确保数据库记录被正确回滚。
- 注销用户时先尝试删除 VFS 服务中的相关资源,再执行数据库删除操作。
- 使用 `Unscoped().Delete()` 确保物理删除用户数据。

feat(vfs): 完善用户目录结构及节点创建逻辑

- 创建用户主目录 `/home/username` 及其子目录 `.Recycle_Bin`。
- 调整 `CreateVFSNode` 方法参数类型为值传递。
- 修复创建用户目录时传参不一致的问题。
- 删除用户时递归清理其在 VFS 中的所有节点,并通过代理删除关联的服务节点。

feat(mage): 新增生成 TypeScript 类型定义的任务

新增 `Gen_TS` Mage 任务用于将 OpenAPI 配置文件转换为 TypeScript 类型定义文件,
支持 `vfs`、`bookmark` 和 `user_np` 模块。
2025-09-28 12:28:22 +08:00

595 lines
14 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.

// vfs.go
package models
import (
"crypto/rand"
"database/sql"
"errors"
"fmt"
"path"
"strings"
"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
}
if userDir, getUserErr := v.GetNodeByParentIDAndName(homeDir.ID, username); getUserErr != nil {
panic(getUserErr)
} else {
dotDir := VfsNode{
Name: ".Recycle_Bin",
ParentID: userDir.ID,
Type: VfsNodeTypeDirectory,
CreatedAt: time.Now(),
UpdatedAt: time.Now(),
}
if createDirErr := v.CreateVFSNode(dotDir); 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
}
func (v *Vfs) GetChildrenID(parentID uint64, recursive bool) (ids []uint64, services []uint64, err error) {
if recursive {
// 递归获取所有子项ID
err := v.getChildrenIDRecursive(parentID, &ids, &services)
if err != nil {
return nil, nil, err
}
} else {
// 只获取直接子项ID
rows, err := v.DB.Query("SELECT id FROM vfs_nodes WHERE parent_id = ?", parentID)
if err != nil {
return nil, nil, err
}
defer rows.Close()
for rows.Next() {
var id uint64
if err := rows.Scan(&id); err != nil {
return nil, nil, err
}
ids = append(ids, id)
}
}
return ids, services, nil
}
// DeleteNodeRecursively 递归删除节点及其所有子项
func (v *Vfs) DeleteNodeRecursively(nodeID uint64) ([]uint64, error) {
// 获取所有需要删除的ID包括自己和所有子项
idsToDelete := []uint64{nodeID}
// 获取所有子项ID
childIDs, sericeIDs, err := v.GetChildrenID(nodeID, true)
if err != nil {
return nil, err
}
idsToDelete = append(idsToDelete, childIDs...)
// 构建删除语句
if len(idsToDelete) == 0 {
return nil, nil
}
// 创建占位符
placeholders := make([]string, len(idsToDelete))
args := make([]any, len(idsToDelete))
for i, id := range idsToDelete {
placeholders[i] = "?"
args[i] = id
}
query := fmt.Sprintf("DELETE FROM vfs_nodes WHERE id IN (%s)", strings.Join(placeholders, ","))
_, err = v.DB.Exec(query, args...)
return sericeIDs, err
}
// getChildrenIDRecursive 递归获取所有子项ID的辅助函数
func (v *Vfs) getChildrenIDRecursive(parentID uint64, ids *[]uint64, services *[]uint64) error {
// 获取当前层级的子项
rows, err := v.DB.Query("SELECT id, type FROM vfs_nodes WHERE parent_id = ?", parentID)
if err != nil {
return err
}
defer rows.Close()
for rows.Next() {
var id uint64
var nodeType VfsNodeType
if err := rows.Scan(&id, &nodeType); err != nil {
return err
}
// 将当前节点ID添加到列表中
*ids = append(*ids, id)
// 如果是目录,递归获取其子项
switch nodeType {
case VfsNodeTypeDirectory:
err := v.getChildrenIDRecursive(id, ids, services)
if err != nil {
return err
}
case VfsNodeTypeService:
*services = append(*services, id)
}
}
return 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) {
// FIXME: 路径判断以及update的类型判断
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 {
// FIXME: 路径和权限的检查
// 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) CheckNameValid(name string) error {
if name == "" || strings.Contains(name, "/") {
return fmt.Errorf("invalid node name")
}
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(ID uint64) *VfsNode {
node := &VfsNode{}
err := v.DB.QueryRow(`
SELECT id, name, parent_id, type, created_at, updated_at
FROM vfs_nodes
WHERE id = ?`, 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
}