为 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` 模块。
595 lines
14 KiB
Go
595 lines
14 KiB
Go
// 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
|
||
}
|