Repository: AlistGo/alist (language: go)

Issue:
最新commit虚拟路径故障，所有添加的存储会平摊在根目录。
### Please make sure of the following things

- [X] I have read the [documentation](https://alist.nn.ci).
- [X] I'm sure there are no duplicate issues or discussions.
- [X] I'm sure it's due to `alist` and not something else(such as `Dependencies` or `Operational`).
- [X] I'm sure I'm using the latest version

### Alist Version / Alist 版本

xhofe/alist:main DIGEST dc6d51b6063d

### Driver used / 使用的存储驱动

阿里云盘 百度网盘 天翼云盘 123网盘 Alist v3

### Describe the bug / 问题描述

12月17号最新的commit虚拟路径故障，所有添加的存储会平摊在根目录。例如123云盘/盘1 正常应该是 123云盘 文件夹下有一个 盘1，现在变成生成一个 名称是 123云盘/盘1 的文件夹。
![Screenshot_20221217_230436_com huawei browser](https://user-images.githubusercontent.com/26871282/208278916-6e9266a0-cecd-4946-8129-a53dbdd08ba7.jpg)
![Screenshot_20221217_230624](https://user-images.githubusercontent.com/26871282/208278918-3083a159-3104-4b6d-b252-e2ec90ea670f.jpg)
![Screenshot_20221217_230149](https://user-images.githubusercontent.com/26871282/208278919-74543adf-0359-4c90-a193-9daeaac5700a.jpg)
 

### Reproduction / 复现链接

http://opensoft.top:2095 
网盘过多个人文件不便公开，使用最新commit的docker镜像创建了一个Alist容器用于演示。
![Screenshot_20221218_105407_com huawei browser](https://user-images.githubusercontent.com/26871282/208279059-bc73dc23-395e-47c0-9bc9-eceac17cd63a.jpg)


### Logs / 日志

stderr: [36mINFO[0m[2022-12-18 03:02:57] reading config file: data/config.json        
stderr: [36mINFO[0m[2022-12-18 03:02:57] load config from env with prefix:            
stderr: [36mINFO[0m[2022-12-18 03:02:57] init logrus...                               
stdout: [33mWARN[0m[2022-12-18 03:02:58] not enable search                            
stderr: [36mINFO[0m[2022-12-18 03:02:58] start server @ 0.0.0.0:2095                  
stderr: [36mINFO[0m[2022-12-18 03:02:58] success load storage: [/test/1], driver: [Virtual] 
stderr: [36mINFO[0m[2022-12-18 03:02:58] success load storage: [/test/2], driver: [Virtual] 
stderr: [36mINFO[0m[2022-12-18 03:03:00] Aria2 not ready.                             
stdout: [GIN] 2022/12/18 - 03:03:05 | 200 |     257.845µs | 192.168.100.195 | GET      "/"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     366.642µs | 192.168.100.195 | GET      "/assets/index.659f4289.css"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |   43.048227ms | 192.168.100.195 | GET      "/assets/index.bcb03b86.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     332.224µs | 192.168.100.195 | GET      "/assets/entry.614346e3.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |    1.937341ms | 192.168.100.195 | GET      "/api/public/settings"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     406.019µs | 192.168.100.195 | GET      "/api/me"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     139.714µs | 192.168.100.195 | GET      "/assets/useTitle.52821376.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     517.733µs | 192.168.100.195 | GET      "/assets/Layout.7e0131da.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     412.727µs | 192.168.100.195 | GET      "/assets/index.ca28c2a7.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     169.758µs | 192.168.100.195 | GET      "/assets/useT.b14d75d6.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     160.424µs | 192.168.100.195 | GET      "/assets/index.2661e5b4.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     164.507µs | 192.168.100.195 | GET      "/assets/api.ef82dfa8.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     127.756µs | 192.168.100.195 | GET      "/assets/FolderTree.8d4c1c62.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |      217.01µs | 192.168.100.195 | GET      "/assets/useUtil.57710fd5.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |   29.968405ms | 192.168.100.195 | GET      "/assets/Markdown.dc29123f.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |      3.3657ms | 192.168.100.195 | POST     "/api/fs/get"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     4.04794ms | 192.168.100.195 | POST     "/api/fs/list"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     393.186µs | 192.168.100.195 | GET      "/assets/Folder.b511ba9e.css"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     490.898µs | 192.168.100.195 | GET      "/assets/Folder.a52bc702.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |      123.38µs | 192.168.100.195 | GET      "/assets/icon.5794cfdd.js"
stdout: [GIN] 2022/12/18 - 03:03:06 | 200 |     192.217µs | 192.168.100.195 | GET      "/assets/List.9f4af9e3.js"



Discussion / hints:

Fix patch:
diff --git a/cmd/admin.go b/cmd/admin.go
index 6f61ccabb8c..62ca0f675a1 100644
--- a/cmd/admin.go
+++ b/cmd/admin.go
@@ -4,7 +4,7 @@ Copyright © 2022 NAME HERE <EMAIL ADDRESS>
 package cmd
 
 import (
-	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/spf13/cobra"
 )
@@ -16,7 +16,7 @@ var passwordCmd = &cobra.Command{
 	Short:   "Show admin user's info",
 	Run: func(cmd *cobra.Command, args []string) {
 		Init()
-		admin, err := db.GetAdmin()
+		admin, err := op.GetAdmin()
 		if err != nil {
 			utils.Log.Errorf("failed get admin user: %+v", err)
 		} else {
diff --git a/cmd/cancel2FA.go b/cmd/cancel2FA.go
index d90a398eb25..46f7e81d815 100644
--- a/cmd/cancel2FA.go
+++ b/cmd/cancel2FA.go
@@ -4,7 +4,7 @@ Copyright © 2022 NAME HERE <EMAIL ADDRESS>
 package cmd
 
 import (
-	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/spf13/cobra"
 )
@@ -15,11 +15,11 @@ var cancel2FACmd = &cobra.Command{
 	Short: "Delete 2FA of admin user",
 	Run: func(cmd *cobra.Command, args []string) {
 		Init()
-		admin, err := db.GetAdmin()
+		admin, err := op.GetAdmin()
 		if err != nil {
 			utils.Log.Errorf("failed to get admin user: %+v", err)
 		} else {
-			err := db.Cancel2FAByUser(admin)
+			err := op.Cancel2FAByUser(admin)
 			if err != nil {
 				utils.Log.Errorf("failed to cancel 2FA: %+v", err)
 			}
diff --git a/internal/bootstrap/data/setting.go b/internal/bootstrap/data/setting.go
index db680f2d56f..3a136631cf8 100644
--- a/internal/bootstrap/data/setting.go
+++ b/internal/bootstrap/data/setting.go
@@ -3,8 +3,8 @@ package data
 import (
 	"github.com/alist-org/alist/v3/cmd/flags"
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils/random"
 	"github.com/pkg/errors"
 	log "github.com/sirupsen/logrus"
@@ -16,40 +16,45 @@ var initialSettingItems []model.SettingItem
 func initSettings() {
 	InitialSettings()
 	// check deprecated
-	settings, err := db.GetSettingItems()
+	settings, err := op.GetSettingItems()
 	if err != nil {
 		log.Fatalf("failed get settings: %+v", err)
 	}
+
 	for i := range settings {
-		if !isActive(settings[i].Key) {
+		if !isActive(settings[i].Key) && settings[i].Flag != model.DEPRECATED {
 			settings[i].Flag = model.DEPRECATED
+			err = op.SaveSettingItem(&settings[i])
+			if err != nil {
+				log.Fatalf("failed save setting: %+v", err)
+			}
 		}
 	}
-	// what's going on here???
-	//if settings != nil && len(settings) > 0 {
-	//	err = db.SaveSettingItems(settings)
-	//	if err != nil {
-	//		log.Fatalf("failed save settings: %+v", err)
-	//	}
-	//}
-	// insert new items
+
+	// create or save setting
 	for i := range initialSettingItems {
-		v := initialSettingItems[i]
-		stored, err := db.GetSettingItemByKey(v.Key)
-		if errors.Is(err, gorm.ErrRecordNotFound) || v.Key == conf.VERSION {
-			err = db.SaveSettingItem(v)
-			if err != nil {
-				log.Fatalf("failed create setting: %+v", err)
-			}
-		} else if err != nil {
+		item := &initialSettingItems[i]
+		// err
+		stored, err := op.GetSettingItemByKey(item.Key)
+		if err != nil && !errors.Is(err, gorm.ErrRecordNotFound) {
 			log.Fatalf("failed get setting: %+v", err)
-		} else {
-			v.Value = stored.Value
-			err = db.SaveSettingItem(v)
+			continue
+		}
+
+		// save
+		if stored != nil {
+			item.Value = stored.Value
+		}
+		if stored == nil || *item != *stored {
+			err = op.SaveSettingItem(item)
 			if err != nil {
-				log.Fatalf("failed resave setting: %+v", err)
+				log.Fatalf("failed save setting: %+v", err)
 			}
+			continue
 		}
+
+		// Not save so needs to execute hook
+		op.HandleSettingItemHook(item)
 	}
 }
 
diff --git a/internal/bootstrap/data/user.go b/internal/bootstrap/data/user.go
index ac6ca1cd368..04018ee0a96 100644
--- a/internal/bootstrap/data/user.go
+++ b/internal/bootstrap/data/user.go
@@ -6,6 +6,7 @@ import (
 	"github.com/alist-org/alist/v3/cmd/flags"
 	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/pkg/utils/random"
 	"github.com/pkg/errors"
@@ -13,7 +14,7 @@ import (
 )
 
 func initUser() {
-	admin, err := db.GetAdmin()
+	admin, err := op.GetAdmin()
 	adminPassword := random.String(8)
 	envpass := os.Getenv("ALIST_ADMIN_PASSWORD")
 	if flags.Dev {
@@ -29,7 +30,7 @@ func initUser() {
 				Role:     model.ADMIN,
 				BasePath: "/",
 			}
-			if err := db.CreateUser(admin); err != nil {
+			if err := op.CreateUser(admin); err != nil {
 				panic(err)
 			} else {
 				utils.Log.Infof("Successfully created the admin user and the initial password is: %s", admin.Password)
@@ -38,7 +39,7 @@ func initUser() {
 			panic(err)
 		}
 	}
-	guest, err := db.GetGuest()
+	guest, err := op.GetGuest()
 	if err != nil {
 		if errors.Is(err, gorm.ErrRecordNotFound) {
 			guest = &model.User{
diff --git a/internal/db/meta.go b/internal/db/meta.go
index 8fd54fd3a08..8b6a605e809 100644
--- a/internal/db/meta.go
+++ b/internal/db/meta.go
@@ -1,52 +1,16 @@
 package db
 
 import (
-	stdpath "path"
-	"time"
-
-	"github.com/Xhofe/go-cache"
-	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/alist-org/alist/v3/pkg/singleflight"
-	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/pkg/errors"
-	"gorm.io/gorm"
 )
 
-var metaCache = cache.NewMemCache(cache.WithShards[*model.Meta](2))
-
-// metaG maybe not needed
-var metaG singleflight.Group[*model.Meta]
-
-func GetNearestMeta(path string) (*model.Meta, error) {
-	path = utils.FixAndCleanPath(path)
-	meta, err := GetMetaByPath(path)
-	if err == nil {
-		return meta, nil
-	}
-	if errors.Cause(err) != gorm.ErrRecordNotFound {
-		return nil, err
-	}
-	if path == "/" {
-		return nil, errors.WithStack(errs.MetaNotFound)
-	}
-	return GetNearestMeta(stdpath.Dir(path))
-}
-
 func GetMetaByPath(path string) (*model.Meta, error) {
-	meta, ok := metaCache.Get(path)
-	if ok {
-		return meta, nil
+	meta := model.Meta{Path: path}
+	if err := db.Where(meta).First(&meta).Error; err != nil {
+		return nil, errors.Wrapf(err, "failed select meta")
 	}
-	meta, err, _ := metaG.Do(path, func() (*model.Meta, error) {
-		meta := model.Meta{Path: path}
-		if err := db.Where(meta).First(&meta).Error; err != nil {
-			return nil, errors.Wrapf(err, "failed select meta")
-		}
-		metaCache.Set(path, &meta, cache.WithEx[*model.Meta](time.Hour))
-		return &meta, nil
-	})
-	return meta, err
+	return &meta, nil
 }
 
 func GetMetaById(id uint) (*model.Meta, error) {
@@ -62,32 +26,20 @@ func CreateMeta(u *model.Meta) error {
 }
 
 func UpdateMeta(u *model.Meta) error {
-	old, err := GetMetaById(u.ID)
-	if err != nil {
-		return err
-	}
-	metaCache.Del(old.Path)
 	return errors.WithStack(db.Save(u).Error)
 }
 
-func GetMetas(pageIndex, pageSize int) ([]model.Meta, int64, error) {
+func GetMetas(pageIndex, pageSize int) (metas []model.Meta, count int64, err error) {
 	metaDB := db.Model(&model.Meta{})
-	var count int64
-	if err := metaDB.Count(&count).Error; err != nil {
+	if err = metaDB.Count(&count).Error; err != nil {
 		return nil, 0, errors.Wrapf(err, "failed get metas count")
 	}
-	var metas []model.Meta
-	if err := metaDB.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&metas).Error; err != nil {
+	if err = metaDB.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&metas).Error; err != nil {
 		return nil, 0, errors.Wrapf(err, "failed get find metas")
 	}
 	return metas, count, nil
 }
 
 func DeleteMetaById(id uint) error {
-	old, err := GetMetaById(id)
-	if err != nil {
-		return err
-	}
-	metaCache.Del(old.Path)
 	return errors.WithStack(db.Delete(&model.Meta{}, id).Error)
 }
diff --git a/internal/db/settinghooks.go b/internal/db/settinghooks.go
deleted file mode 100644
index f7d85a07873..00000000000
--- a/internal/db/settinghooks.go
+++ /dev/null
@@ -1,79 +0,0 @@
-package db
-
-import (
-	"regexp"
-	"strings"
-
-	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/alist-org/alist/v3/pkg/utils"
-	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
-)
-
-type SettingItemHook func(item *model.SettingItem) error
-
-var settingItemHooks = map[string]SettingItemHook{
-	conf.VideoTypes: func(item *model.SettingItem) error {
-		conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
-		return nil
-	},
-	conf.AudioTypes: func(item *model.SettingItem) error {
-		conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
-		return nil
-	},
-	conf.ImageTypes: func(item *model.SettingItem) error {
-		conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
-		return nil
-	},
-	conf.TextTypes: func(item *model.SettingItem) error {
-		conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
-		return nil
-	},
-	conf.ProxyTypes: func(item *model.SettingItem) error {
-		conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
-		return nil
-	},
-
-	conf.PrivacyRegs: func(item *model.SettingItem) error {
-		regStrs := strings.Split(item.Value, "\n")
-		regs := make([]*regexp.Regexp, 0, len(regStrs))
-		for _, regStr := range regStrs {
-			reg, err := regexp.Compile(regStr)
-			if err != nil {
-				return errors.WithStack(err)
-			}
-			regs = append(regs, reg)
-		}
-		conf.PrivacyReg = regs
-		return nil
-	},
-	conf.FilenameCharMapping: func(item *model.SettingItem) error {
-		err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
-		if err != nil {
-			return err
-		}
-		log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
-		return nil
-	},
-}
-
-func HandleSettingItem(item *model.SettingItem) (bool, error) {
-	if hook, ok := settingItemHooks[item.Key]; ok {
-		return true, hook(item)
-	}
-	return false, nil
-}
-
-func RegisterSettingItemHook(key string, hook SettingItemHook) {
-	settingItemHooks[key] = hook
-}
-
-// func HandleSettingItems(items []model.SettingItem) error {
-// 	for i := range items {
-// 		if err := HandleSettingItem(&items[i]); err != nil {
-// 			return err
-// 		}
-// 	}
-// 	return nil
-// }
diff --git a/internal/db/settingitem.go b/internal/db/settingitem.go
index 6cb88b4079b..2ba0c665acd 100644
--- a/internal/db/settingitem.go
+++ b/internal/db/settingitem.go
@@ -4,45 +4,9 @@ import (
 	"fmt"
 
 	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/alist-org/alist/v3/pkg/generic_sync"
 	"github.com/pkg/errors"
-	log "github.com/sirupsen/logrus"
 )
 
-var settingsMap generic_sync.MapOf[string, string]
-var publicSettingsMap generic_sync.MapOf[string, string]
-
-func settingsUpdate() {
-	settingsMap.Clear()
-	publicSettingsMap.Clear()
-}
-
-func GetPublicSettingsMap() map[string]string {
-	if publicSettingsMap.Empty() {
-		settingItems, err := GetPublicSettingItems()
-		if err != nil {
-			log.Errorf("failed to get settingItems: %+v", err)
-		}
-		for _, settingItem := range settingItems {
-			publicSettingsMap.Store(settingItem.Key, settingItem.Value)
-		}
-	}
-	return publicSettingsMap.ToMap()
-}
-
-func GetSettingsMap() *generic_sync.MapOf[string, string] {
-	if settingsMap.Empty() {
-		settingItems, err := GetSettingItems()
-		if err != nil {
-			log.Errorf("failed to get settingItems: %+v", err)
-		}
-		for _, settingItem := range settingItems {
-			settingsMap.Store(settingItem.Key, settingItem.Value)
-		}
-	}
-	return &settingsMap
-}
-
 func GetSettingItems() ([]model.SettingItem, error) {
 	var settingItems []model.SettingItem
 	if err := db.Find(&settingItems).Error; err != nil {
@@ -59,18 +23,18 @@ func GetSettingItemByKey(key string) (*model.SettingItem, error) {
 	return &settingItem, nil
 }
 
-func GetSettingItemInKeys(keys []string) ([]model.SettingItem, error) {
-	var settingItem []model.SettingItem
-	if err := db.Where(fmt.Sprintf("%s in ?", columnName("key")), keys).Find(&settingItem).Error; err != nil {
-		return nil, errors.WithStack(err)
-	}
-	return settingItem, nil
-}
+// func GetSettingItemInKeys(keys []string) ([]model.SettingItem, error) {
+// 	var settingItem []model.SettingItem
+// 	if err := db.Where(fmt.Sprintf("%s in ?", columnName("key")), keys).Find(&settingItem).Error; err != nil {
+// 		return nil, errors.WithStack(err)
+// 	}
+// 	return settingItem, nil
+// }
 
 func GetPublicSettingItems() ([]model.SettingItem, error) {
 	var settingItems []model.SettingItem
 	if err := db.Where(fmt.Sprintf("%s in ?", columnName("flag")), []int{model.PUBLIC, model.READONLY}).Find(&settingItems).Error; err != nil {
-		return nil, err
+		return nil, errors.WithStack(err)
 	}
 	return settingItems, nil
 }
@@ -91,58 +55,14 @@ func GetSettingItemsInGroups(groups []int) ([]model.SettingItem, error) {
 	return settingItems, nil
 }
 
-func SaveSettingItems(items []model.SettingItem) error {
-	others := make([]model.SettingItem, 0)
-	for i := range items {
-		if ok, err := HandleSettingItem(&items[i]); ok {
-			if err != nil {
-				return err
-			} else {
-				err = db.Save(items[i]).Error
-				if err != nil {
-					return errors.WithStack(err)
-				}
-			}
-		} else {
-			others = append(others, items[i])
-		}
-	}
-	if len(others) > 0 {
-		err := db.Save(others).Error
-		if err != nil {
-			if len(others) < len(items) {
-				settingsUpdate()
-			}
-			return err
-		}
-	}
-	settingsUpdate()
-	return nil
+func SaveSettingItems(items []model.SettingItem) (err error) {
+	return errors.WithStack(db.Save(items).Error)
 }
 
-func SaveSettingItem(item model.SettingItem) error {
-	_, err := HandleSettingItem(&item)
-	if err != nil {
-		return err
-	}
-	err = db.Save(item).Error
-	if err == nil {
-		settingsUpdate()
-	}
-	return errors.WithStack(err)
+func SaveSettingItem(item *model.SettingItem) error {
+	return errors.WithStack(db.Save(item).Error)
 }
 
 func DeleteSettingItemByKey(key string) error {
-	settingItem := model.SettingItem{
-		Key: key,
-	}
-	old, err := GetSettingItemByKey(key)
-	if err != nil {
-		return errors.WithMessage(err, "failed to get settingItem")
-	}
-	if !old.IsDeprecated() {
-		return errors.Errorf("setting [%s] is not deprecated", key)
-	}
-	settingsUpdate()
-	return errors.WithStack(db.Delete(&settingItem).Error)
+	return errors.WithStack(db.Delete(&model.SettingItem{Key: key}).Error)
 }
diff --git a/internal/db/user.go b/internal/db/user.go
index 04bdfbaf716..cc2d817f173 100644
--- a/internal/db/user.go
+++ b/internal/db/user.go
@@ -1,61 +1,24 @@
 package db
 
 import (
-	"time"
-
-	"github.com/Xhofe/go-cache"
-	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/alist-org/alist/v3/pkg/singleflight"
 	"github.com/pkg/errors"
 )
 
-var userCache = cache.NewMemCache(cache.WithShards[*model.User](2))
-var userG singleflight.Group[*model.User]
-var guest *model.User
-var admin *model.User
-
-func GetAdmin() (*model.User, error) {
-	if admin != nil {
-		return admin, nil
-	}
-	user := model.User{Role: model.ADMIN}
+func GetUserByRole(role int) (*model.User, error) {
+	user := model.User{Role: role}
 	if err := db.Where(user).Take(&user).Error; err != nil {
 		return nil, err
 	}
-	admin = &user
-	return &user, nil
-}
-
-func GetGuest() (*model.User, error) {
-	if guest != nil {
-		return guest, nil
-	}
-	user := model.User{Role: model.GUEST}
-	if err := db.Where(user).Take(&user).Error; err != nil {
-		return nil, err
-	}
-	guest = &user
 	return &user, nil
 }
 
 func GetUserByName(username string) (*model.User, error) {
-	if username == "" {
-		return nil, errors.WithStack(errs.EmptyUsername)
-	}
-	user, ok := userCache.Get(username)
-	if ok {
-		return user, nil
+	user := model.User{Username: username}
+	if err := db.Where(user).First(&user).Error; err != nil {
+		return nil, errors.Wrapf(err, "failed find user")
 	}
-	user, err, _ := userG.Do(username, func() (*model.User, error) {
-		user := model.User{Username: username}
-		if err := db.Where(user).First(&user).Error; err != nil {
-			return nil, errors.Wrapf(err, "failed find user")
-		}
-		userCache.Set(username, &user, cache.WithEx[*model.User](time.Hour))
-		return &user, nil
-	})
-	return user, err
+	return &user, nil
 }
 
 func GetUserById(id uint) (*model.User, error) {
@@ -71,40 +34,14 @@ func CreateUser(u *model.User) error {
 }
 
 func UpdateUser(u *model.User) error {
-	old, err := GetUserById(u.ID)
-	if err != nil {
-		return err
-	}
-	userCache.Del(old.Username)
-	if u.IsGuest() {
-		guest = nil
-	}
-	if u.IsAdmin() {
-		admin = nil
-	}
 	return errors.WithStack(db.Save(u).Error)
 }
 
-func Cancel2FAByUser(u *model.User) error {
-	u.OtpSecret = ""
-	return errors.WithStack(UpdateUser(u))
-}
-
-func Cancel2FAById(id uint) error {
-	user, err := GetUserById(id)
-	if err != nil {
-		return err
-	}
-	return Cancel2FAByUser(user)
-}
-
-func GetUsers(pageIndex, pageSize int) ([]model.User, int64, error) {
+func GetUsers(pageIndex, pageSize int) (users []model.User, count int64, err error) {
 	userDB := db.Model(&model.User{})
-	var count int64
 	if err := userDB.Count(&count).Error; err != nil {
 		return nil, 0, errors.Wrapf(err, "failed get users count")
 	}
-	var users []model.User
 	if err := userDB.Offset((pageIndex - 1) * pageSize).Limit(pageSize).Find(&users).Error; err != nil {
 		return nil, 0, errors.Wrapf(err, "failed get find users")
 	}
@@ -112,13 +49,5 @@ func GetUsers(pageIndex, pageSize int) ([]model.User, int64, error) {
 }
 
 func DeleteUserById(id uint) error {
-	old, err := GetUserById(id)
-	if err != nil {
-		return err
-	}
-	if old.IsAdmin() || old.IsGuest() {
-		return errors.WithStack(errs.DeleteAdminOrGuest)
-	}
-	userCache.Del(old.Username)
 	return errors.WithStack(db.Delete(&model.User{}, id).Error)
 }
diff --git a/internal/fs/list.go b/internal/fs/list.go
index 04521b92d19..0cdf059139d 100644
--- a/internal/fs/list.go
+++ b/internal/fs/list.go
@@ -2,8 +2,6 @@ package fs
 
 import (
 	"context"
-	"regexp"
-	"strings"
 
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/op"
@@ -16,15 +14,15 @@ import (
 func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error) {
 	meta := ctx.Value("meta").(*model.Meta)
 	user := ctx.Value("user").(*model.User)
-	var objs []model.Obj
-	storage, actualPath, err := op.GetStorageAndActualPath(path)
 	virtualFiles := op.GetStorageVirtualFilesByPath(path)
-	if err != nil {
-		if len(virtualFiles) == 0 {
-			return nil, errors.WithMessage(err, "failed get storage")
-		}
-	} else {
-		_objs, err := op.List(ctx, storage, actualPath, model.ListArgs{
+	storage, actualPath, err := op.GetStorageAndActualPath(path)
+	if err != nil && len(virtualFiles) == 0 {
+		return nil, errors.WithMessage(err, "failed get storage")
+	}
+
+	var _objs []model.Obj
+	if storage != nil {
+		_objs, err = op.List(ctx, storage, actualPath, model.ListArgs{
 			ReqPath: path,
 		}, refresh...)
 		if err != nil {
@@ -33,21 +31,13 @@ func list(ctx context.Context, path string, refresh ...bool) ([]model.Obj, error
 				return nil, errors.WithMessage(err, "failed get objs")
 			}
 		}
-		objs = make([]model.Obj, len(_objs))
-		copy(objs, _objs)
-	}
-	if objs == nil {
-		objs = virtualFiles
-	} else {
-		for _, storageFile := range virtualFiles {
-			if !containsByName(objs, storageFile) {
-				objs = append(objs, storageFile)
-			}
-		}
 	}
+
+	om := model.NewObjMerge()
 	if whetherHide(user, meta, path) {
-		objs = hide(objs, meta)
+		om.InitHideReg(meta.Hide)
 	}
+	objs := om.Merge(virtualFiles, _objs...)
 	// sort objs
 	if storage != nil {
 		if storage.Config().LocalSort {
@@ -78,26 +68,3 @@ func whetherHide(user *model.User, meta *model.Meta, path string) bool {
 	// if is guest, hide
 	return true
 }
-
-func hide(objs []model.Obj, meta *model.Meta) []model.Obj {
-	var res []model.Obj
-	deleted := make([]bool, len(objs))
-	rs := strings.Split(meta.Hide, "\n")
-	for _, r := range rs {
-		re := regexp.MustCompile(r)
-		for i, obj := range objs {
-			if deleted[i] {
-				continue
-			}
-			if re.MatchString(obj.GetName()) {
-				deleted[i] = true
-			}
-		}
-	}
-	for i, obj := range objs {
-		if !deleted[i] {
-			res = append(res, obj)
-		}
-	}
-	return res
-}
diff --git a/internal/fs/walk.go b/internal/fs/walk.go
index 53d1aec08c0..2069b8798ba 100644
--- a/internal/fs/walk.go
+++ b/internal/fs/walk.go
@@ -5,8 +5,8 @@ import (
 	"path"
 	"path/filepath"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 )
 
 // WalkFS traverses filesystem fs starting at name up to depth levels.
@@ -26,7 +26,7 @@ func WalkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
 	if !info.IsDir() || depth == 0 {
 		return nil
 	}
-	meta, _ := db.GetNearestMeta(name)
+	meta, _ := op.GetNearestMeta(name)
 	// Read directory names.
 	objs, err := List(context.WithValue(ctx, "meta", meta), name)
 	if err != nil {
diff --git a/internal/model/obj.go b/internal/model/obj.go
index 3a069858b2b..a81b318e37d 100644
--- a/internal/model/obj.go
+++ b/internal/model/obj.go
@@ -2,9 +2,13 @@ package model
 
 import (
 	"io"
+	"regexp"
 	"sort"
+	"strings"
 	"time"
 
+	mapset "github.com/deckarep/golang-set/v2"
+
 	"github.com/maruel/natural"
 )
 
@@ -100,3 +104,50 @@ func WrapObjsName(objs []Obj) {
 		objs[i] = &ObjWrapName{Obj: objs[i]}
 	}
 }
+
+func NewObjMerge() *ObjMerge {
+	return &ObjMerge{
+		set: mapset.NewSet[string](),
+	}
+}
+
+type ObjMerge struct {
+	regs []*regexp.Regexp
+	set  mapset.Set[string]
+}
+
+func (om *ObjMerge) Merge(objs []Obj, objs_ ...Obj) []Obj {
+	newObjs := make([]Obj, 0, len(objs)+len(objs_))
+	newObjs = om.insertObjs(om.insertObjs(newObjs, objs...), objs_...)
+	return newObjs
+}
+
+func (om *ObjMerge) insertObjs(objs []Obj, objs_ ...Obj) []Obj {
+	for _, obj := range objs_ {
+		if om.clickObj(obj) {
+			objs = append(objs, obj)
+		}
+	}
+	return objs
+}
+
+func (om *ObjMerge) clickObj(obj Obj) bool {
+	for _, reg := range om.regs {
+		if reg.MatchString(obj.GetName()) {
+			return false
+		}
+	}
+	return om.set.Add(obj.GetName())
+}
+
+func (om *ObjMerge) InitHideReg(hides string) {
+	rs := strings.Split(hides, "\n")
+	om.regs = make([]*regexp.Regexp, 0, len(rs))
+	for _, r := range rs {
+		om.regs = append(om.regs, regexp.MustCompile(r))
+	}
+}
+
+func (om *ObjMerge) Reset() {
+	om.set.Clear()
+}
diff --git a/internal/op/hook.go b/internal/op/hook.go
index 7c624e9ec72..bf0e0df3bbb 100644
--- a/internal/op/hook.go
+++ b/internal/op/hook.go
@@ -1,7 +1,17 @@
 package op
 
-import "github.com/alist-org/alist/v3/internal/model"
+import (
+	"regexp"
+	"strings"
 
+	"github.com/alist-org/alist/v3/internal/conf"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/pkg/errors"
+	log "github.com/sirupsen/logrus"
+)
+
+// Obj
 type ObjsUpdateHook = func(parent string, objs []model.Obj)
 
 var (
@@ -11,3 +21,78 @@ var (
 func RegisterObjsUpdateHook(hook ObjsUpdateHook) {
 	objsUpdateHooks = append(objsUpdateHooks, hook)
 }
+
+func HandleObjsUpdateHook(parent string, objs []model.Obj) {
+	for _, hook := range objsUpdateHooks {
+		hook(parent, objs)
+	}
+}
+
+// Setting
+type SettingItemHook func(item *model.SettingItem) error
+
+var settingItemHooks = map[string]SettingItemHook{
+	conf.VideoTypes: func(item *model.SettingItem) error {
+		conf.TypesMap[conf.VideoTypes] = strings.Split(item.Value, ",")
+		return nil
+	},
+	conf.AudioTypes: func(item *model.SettingItem) error {
+		conf.TypesMap[conf.AudioTypes] = strings.Split(item.Value, ",")
+		return nil
+	},
+	conf.ImageTypes: func(item *model.SettingItem) error {
+		conf.TypesMap[conf.ImageTypes] = strings.Split(item.Value, ",")
+		return nil
+	},
+	conf.TextTypes: func(item *model.SettingItem) error {
+		conf.TypesMap[conf.TextTypes] = strings.Split(item.Value, ",")
+		return nil
+	},
+	conf.ProxyTypes: func(item *model.SettingItem) error {
+		conf.TypesMap[conf.ProxyTypes] = strings.Split(item.Value, ",")
+		return nil
+	},
+
+	conf.PrivacyRegs: func(item *model.SettingItem) error {
+		regStrs := strings.Split(item.Value, "\n")
+		regs := make([]*regexp.Regexp, 0, len(regStrs))
+		for _, regStr := range regStrs {
+			reg, err := regexp.Compile(regStr)
+			if err != nil {
+				return errors.WithStack(err)
+			}
+			regs = append(regs, reg)
+		}
+		conf.PrivacyReg = regs
+		return nil
+	},
+	conf.FilenameCharMapping: func(item *model.SettingItem) error {
+		err := utils.Json.UnmarshalFromString(item.Value, &conf.FilenameCharMap)
+		if err != nil {
+			return err
+		}
+		log.Debugf("filename char mapping: %+v", conf.FilenameCharMap)
+		return nil
+	},
+}
+
+func RegisterSettingItemHook(key string, hook SettingItemHook) {
+	settingItemHooks[key] = hook
+}
+
+func HandleSettingItemHook(item *model.SettingItem) (hasHook bool, err error) {
+	if hook, ok := settingItemHooks[item.Key]; ok {
+		return true, hook(item)
+	}
+	return false, nil
+}
+
+//func HandleSettingItemsHook(items []model.SettingItem) (err error) {
+//	for i := 0; i < len(items); i++ {
+//		_, err = HandleSettingItemHook(&items[i])
+//		if err != nil {
+//			return err
+//		}
+//	}
+//	return nil
+//}
diff --git a/internal/op/meta.go b/internal/op/meta.go
new file mode 100644
index 00000000000..af14f3953b1
--- /dev/null
+++ b/internal/op/meta.go
@@ -0,0 +1,88 @@
+package op
+
+import (
+	stdpath "path"
+	"time"
+
+	"github.com/Xhofe/go-cache"
+	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/pkg/singleflight"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/pkg/errors"
+	"gorm.io/gorm"
+)
+
+var metaCache = cache.NewMemCache(cache.WithShards[*model.Meta](2))
+
+// metaG maybe not needed
+var metaG singleflight.Group[*model.Meta]
+
+func GetNearestMeta(path string) (*model.Meta, error) {
+	return getNearestMeta(utils.FixAndCleanPath(path))
+}
+func getNearestMeta(path string) (*model.Meta, error) {
+	meta, err := GetMetaByPath(path)
+	if err == nil {
+		return meta, nil
+	}
+	if errors.Cause(err) != gorm.ErrRecordNotFound {
+		return nil, err
+	}
+	if path == "/" {
+		return nil, errs.MetaNotFound
+	}
+	return getNearestMeta(stdpath.Dir(path))
+}
+
+func GetMetaByPath(path string) (*model.Meta, error) {
+	return getMetaByPath(utils.FixAndCleanPath(path))
+}
+func getMetaByPath(path string) (*model.Meta, error) {
+	meta, ok := metaCache.Get(path)
+	if ok {
+		return meta, nil
+	}
+	meta, err, _ := metaG.Do(path, func() (*model.Meta, error) {
+		_meta, err := db.GetMetaByPath(path)
+		if err != nil {
+			return nil, err
+		}
+		metaCache.Set(path, _meta, cache.WithEx[*model.Meta](time.Hour))
+		return _meta, nil
+	})
+	return meta, err
+}
+
+func DeleteMetaById(id uint) error {
+	old, err := db.GetMetaById(id)
+	if err != nil {
+		return err
+	}
+	metaCache.Del(old.Path)
+	return db.DeleteMetaById(id)
+}
+
+func UpdateMeta(u *model.Meta) error {
+	u.Path = utils.FixAndCleanPath(u.Path)
+	old, err := db.GetMetaById(u.ID)
+	if err != nil {
+		return err
+	}
+	metaCache.Del(old.Path)
+	return db.UpdateMeta(u)
+}
+
+func CreateMeta(u *model.Meta) error {
+	u.Path = utils.FixAndCleanPath(u.Path)
+	return db.CreateMeta(u)
+}
+
+func GetMetaById(id uint) (*model.Meta, error) {
+	return db.GetMetaById(id)
+}
+
+func GetMetas(pageIndex, pageSize int) (metas []model.Meta, count int64, err error) {
+	return db.GetMetas(pageIndex, pageSize)
+}
diff --git a/internal/op/path.go b/internal/op/path.go
index 9909fbc20c7..6e3dfdf7422 100644
--- a/internal/op/path.go
+++ b/internal/op/path.go
@@ -19,7 +19,7 @@ func GetStorageAndActualPath(rawPath string) (storage driver.Driver, actualPath
 		return
 	}
 	log.Debugln("use storage: ", storage.GetStorage().MountPath)
-	virtualPath := utils.GetActualVirtualPath(storage.GetStorage().MountPath)
-	actualPath = utils.FixAndCleanPath(strings.TrimPrefix(rawPath, virtualPath))
+	mountPath := utils.GetActualMountPath(storage.GetStorage().MountPath)
+	actualPath = utils.FixAndCleanPath(strings.TrimPrefix(rawPath, mountPath))
 	return
 }
diff --git a/internal/op/setting.go b/internal/op/setting.go
new file mode 100644
index 00000000000..83d19c12fbe
--- /dev/null
+++ b/internal/op/setting.go
@@ -0,0 +1,198 @@
+package op
+
+import (
+	"sort"
+	"strconv"
+	"strings"
+	"time"
+
+	"github.com/Xhofe/go-cache"
+	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/pkg/singleflight"
+	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/pkg/errors"
+)
+
+var settingCache = cache.NewMemCache(cache.WithShards[*model.SettingItem](4))
+var settingG singleflight.Group[*model.SettingItem]
+var settingCacheF = func(item *model.SettingItem) {
+	settingCache.Set(item.Key, item, cache.WithEx[*model.SettingItem](time.Hour))
+}
+
+var settingGroupCache = cache.NewMemCache(cache.WithShards[[]model.SettingItem](4))
+var settingGroupG singleflight.Group[[]model.SettingItem]
+var settingGroupCacheF = func(key string, item []model.SettingItem) {
+	settingGroupCache.Set(key, item, cache.WithEx[[]model.SettingItem](time.Hour))
+}
+
+func settingCacheUpdate() {
+	settingCache.Clear()
+	settingGroupCache.Clear()
+}
+
+func GetPublicSettingsMap() map[string]string {
+	items, _ := GetPublicSettingItems()
+	pSettings := make(map[string]string)
+	for _, item := range items {
+		pSettings[item.Key] = item.Value
+	}
+	return pSettings
+}
+
+func GetSettingsMap() map[string]string {
+	items, _ := GetSettingItems()
+	settings := make(map[string]string)
+	for _, item := range items {
+		settings[item.Key] = item.Value
+	}
+	return settings
+}
+
+func GetSettingItems() ([]model.SettingItem, error) {
+	if items, ok := settingGroupCache.Get("ALL_SETTING_ITEMS"); ok {
+		return items, nil
+	}
+	items, err, _ := settingGroupG.Do("ALL_SETTING_ITEMS", func() ([]model.SettingItem, error) {
+		_items, err := db.GetSettingItems()
+		if err != nil {
+			return nil, err
+		}
+		settingGroupCacheF("ALL_SETTING_ITEMS", _items)
+		return _items, nil
+	})
+	return items, err
+}
+
+func GetPublicSettingItems() ([]model.SettingItem, error) {
+	if items, ok := settingGroupCache.Get("ALL_PUBLIC_SETTING_ITEMS"); ok {
+		return items, nil
+	}
+	items, err, _ := settingGroupG.Do("ALL_PUBLIC_SETTING_ITEMS", func() ([]model.SettingItem, error) {
+		_items, err := db.GetPublicSettingItems()
+		if err != nil {
+			return nil, err
+		}
+		settingGroupCacheF("ALL_PUBLIC_SETTING_ITEMS", _items)
+		return _items, nil
+	})
+	return items, err
+}
+
+func GetSettingItemByKey(key string) (*model.SettingItem, error) {
+	if item, ok := settingCache.Get(key); ok {
+		return item, nil
+	}
+
+	item, err, _ := settingG.Do(key, func() (*model.SettingItem, error) {
+		_item, err := db.GetSettingItemByKey(key)
+		if err != nil {
+			return nil, err
+		}
+		settingCacheF(_item)
+		return _item, nil
+	})
+	return item, err
+}
+
+func GetSettingItemInKeys(keys []string) ([]model.SettingItem, error) {
+	var items []model.SettingItem
+	for _, key := range keys {
+		item, err := GetSettingItemByKey(key)
+		if err != nil {
+			return nil, err
+		}
+		items = append(items, *item)
+	}
+	return items, nil
+}
+
+func GetSettingItemsByGroup(group int) ([]model.SettingItem, error) {
+	key := strconv.Itoa(group)
+	if items, ok := settingGroupCache.Get(key); ok {
+		return items, nil
+	}
+	items, err, _ := settingGroupG.Do(key, func() ([]model.SettingItem, error) {
+		_items, err := db.GetSettingItemsByGroup(group)
+		if err != nil {
+			return nil, err
+		}
+		settingGroupCacheF(key, _items)
+		return _items, nil
+	})
+	return items, err
+}
+
+func GetSettingItemsInGroups(groups []int) ([]model.SettingItem, error) {
+	sort.Ints(groups)
+	key := strings.Join(utils.MustSliceConvert(groups, func(i int) string {
+		return strconv.Itoa(i)
+	}), ",")
+
+	if items, ok := settingGroupCache.Get(key); ok {
+		return items, nil
+	}
+	items, err, _ := settingGroupG.Do(key, func() ([]model.SettingItem, error) {
+		_items, err := db.GetSettingItemsInGroups(groups)
+		if err != nil {
+			return nil, err
+		}
+		settingGroupCacheF(key, _items)
+		return _items, nil
+	})
+	return items, err
+}
+
+func SaveSettingItems(items []model.SettingItem) error {
+	noHookItems := make([]model.SettingItem, 0)
+	errs := make([]error, 0)
+	for i := range items {
+		if ok, err := HandleSettingItemHook(&items[i]); ok {
+			if err != nil {
+				errs = append(errs, err)
+			} else {
+				err = db.SaveSettingItem(&items[i])
+				if err != nil {
+					errs = append(errs, err)
+				}
+			}
+		} else {
+			noHookItems = append(noHookItems, items[i])
+		}
+	}
+	if len(noHookItems) > 0 {
+		err := db.SaveSettingItems(noHookItems)
+		if err != nil {
+			errs = append(errs, err)
+		}
+	}
+	if len(errs) < len(items)-len(noHookItems)+1 {
+		settingCacheUpdate()
+	}
+	return utils.MergeErrors(errs...)
+}
+
+func SaveSettingItem(item *model.SettingItem) (err error) {
+	// hook
+	if _, err := HandleSettingItemHook(item); err != nil {
+		return err
+	}
+	// update
+	if err = db.SaveSettingItem(item); err != nil {
+		return err
+	}
+	settingCacheUpdate()
+	return nil
+}
+
+func DeleteSettingItemByKey(key string) error {
+	old, err := GetSettingItemByKey(key)
+	if err != nil {
+		return errors.WithMessage(err, "failed to get settingItem")
+	}
+	if !old.IsDeprecated() {
+		return errors.Errorf("setting [%s] is not deprecated", key)
+	}
+	settingCacheUpdate()
+	return db.DeleteSettingItemByKey(key)
+}
diff --git a/internal/op/storage.go b/internal/op/storage.go
index 20006ca2cfa..fc96490d2ee 100644
--- a/internal/op/storage.go
+++ b/internal/op/storage.go
@@ -29,7 +29,7 @@ func HasStorage(mountPath string) bool {
 	return storagesMap.Has(utils.FixAndCleanPath(mountPath))
 }
 
-func GetStorageByVirtualPath(virtualPath string) (driver.Driver, error) {
+func GetStorageByMountPath(virtualPath string) (driver.Driver, error) {
 	virtualPath = utils.FixAndCleanPath(virtualPath)
 	storageDriver, ok := storagesMap.Load(virtualPath)
 	if !ok {
@@ -130,7 +130,7 @@ func DisableStorage(ctx context.Context, id uint) error {
 	if storage.Disabled {
 		return errors.Errorf("this storage have disabled")
 	}
-	storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
+	storageDriver, err := GetStorageByMountPath(storage.MountPath)
 	if err != nil {
 		return errors.WithMessage(err, "failed get storage driver")
 	}
@@ -168,7 +168,7 @@ func UpdateStorage(ctx context.Context, storage model.Storage) error {
 	if storage.Disabled {
 		return nil
 	}
-	storageDriver, err := GetStorageByVirtualPath(oldStorage.MountPath)
+	storageDriver, err := GetStorageByMountPath(oldStorage.MountPath)
 	if oldStorage.MountPath != storage.MountPath {
 		// mount path renamed, need to drop the storage
 		storagesMap.Delete(oldStorage.MountPath)
@@ -192,7 +192,7 @@ func DeleteStorageById(ctx context.Context, id uint) error {
 		return errors.WithMessage(err, "failed get storage")
 	}
 	if !storage.Disabled {
-		storageDriver, err := GetStorageByVirtualPath(storage.MountPath)
+		storageDriver, err := GetStorageByMountPath(storage.MountPath)
 		if err != nil {
 			return errors.WithMessage(err, "failed get storage driver")
 		}
@@ -221,11 +221,11 @@ func MustSaveDriverStorage(driver driver.Driver) {
 func saveDriverStorage(driver driver.Driver) error {
 	storage := driver.GetStorage()
 	addition := driver.GetAddition()
-	strs, err := utils.Json.MarshalToString(addition)
+	str, err := utils.Json.MarshalToString(addition)
 	if err != nil {
 		return errors.Wrap(err, "error while marshal addition")
 	}
-	storage.Addition = strs
+	storage.Addition = str
 	err = db.UpdateStorage(storage)
 	if err != nil {
 		return errors.WithMessage(err, "failed update storage in database")
@@ -240,10 +240,10 @@ func getStoragesByPath(path string) []driver.Driver {
 	storages := make([]driver.Driver, 0)
 	curSlashCount := 0
 	storagesMap.Range(func(mountPath string, value driver.Driver) bool {
-		virtualPath := utils.GetActualVirtualPath(mountPath)
+		mountPath = utils.GetActualMountPath(mountPath)
 		// is this path
-		if strings.HasPrefix(path, virtualPath) {
-			slashCount := strings.Count(utils.PathAddSeparatorSuffix(virtualPath), "/")
+		if utils.IsSubPath(path, mountPath) {
+			slashCount := strings.Count(utils.PathAddSeparatorSuffix(mountPath), "/")
 			// not the longest match
 			if slashCount > curSlashCount {
 				storages = storages[:0]
@@ -278,13 +278,12 @@ func GetStorageVirtualFilesByPath(prefix string) []model.Obj {
 	prefix = utils.FixAndCleanPath(prefix)
 	set := mapset.NewSet[string]()
 	for _, v := range storages {
-		virtualPath := utils.GetActualVirtualPath(v.GetStorage().MountPath)
+		mountPath := utils.GetActualMountPath(v.GetStorage().MountPath)
 		// Exclude prefix itself and non prefix
-		if len(prefix) >= len(virtualPath) || !strings.HasPrefix(virtualPath, prefix) {
+		if len(prefix) >= len(mountPath) || !utils.IsSubPath(mountPath, prefix) {
 			continue
 		}
-
-		name := strings.SplitN(strings.TrimPrefix(virtualPath[len(prefix):], "/"), "/", 1)[0]
+		name := strings.SplitN(strings.TrimPrefix(mountPath[len(prefix):], "/"), "/", 2)[0]
 		if set.Add(name) {
 			files = append(files, &model.Object{
 				Name:     name,
@@ -310,7 +309,7 @@ func GetBalancedStorage(path string) driver.Driver {
 	case 1:
 		return storages[0]
 	default:
-		virtualPath := utils.GetActualVirtualPath(storages[0].GetStorage().MountPath)
+		virtualPath := utils.GetActualMountPath(storages[0].GetStorage().MountPath)
 		i, _ := balanceMap.LoadOrStore(virtualPath, 0)
 		i = (i + 1) % storageNum
 		balanceMap.Store(virtualPath, i)
diff --git a/internal/op/user.go b/internal/op/user.go
new file mode 100644
index 00000000000..a0bbc3edc36
--- /dev/null
+++ b/internal/op/user.go
@@ -0,0 +1,115 @@
+package op
+
+import (
+	"time"
+
+	"github.com/Xhofe/go-cache"
+	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/errs"
+	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/pkg/singleflight"
+	"github.com/alist-org/alist/v3/pkg/utils"
+)
+
+var userCache = cache.NewMemCache(cache.WithShards[*model.User](2))
+var userG singleflight.Group[*model.User]
+var guestUser *model.User
+var adminUser *model.User
+
+func GetAdmin() (*model.User, error) {
+	if adminUser == nil {
+		user, err := db.GetUserByRole(model.ADMIN)
+		if err != nil {
+			return nil, err
+		}
+		adminUser = user
+	}
+	return adminUser, nil
+}
+
+func GetGuest() (*model.User, error) {
+	if guestUser == nil {
+		user, err := db.GetUserByRole(model.GUEST)
+		if err != nil {
+			return nil, err
+		}
+		guestUser = user
+	}
+	return guestUser, nil
+}
+
+func GetUserByRole(role int) (*model.User, error) {
+	return db.GetUserByRole(role)
+}
+
+func GetUserByName(username string) (*model.User, error) {
+	if username == "" {
+		return nil, errs.EmptyUsername
+	}
+	if user, ok := userCache.Get(username); ok {
+		return user, nil
+	}
+	user, err, _ := userG.Do(username, func() (*model.User, error) {
+		_user, err := db.GetUserByName(username)
+		if err != nil {
+			return nil, err
+		}
+		userCache.Set(username, _user, cache.WithEx[*model.User](time.Hour))
+		return _user, nil
+	})
+	return user, err
+}
+
+func GetUserById(id uint) (*model.User, error) {
+	return db.GetUserById(id)
+}
+
+func GetUsers(pageIndex, pageSize int) (users []model.User, count int64, err error) {
+	return db.GetUsers(pageIndex, pageSize)
+}
+
+func CreateUser(u *model.User) error {
+	u.BasePath = utils.FixAndCleanPath(u.BasePath)
+	return db.CreateUser(u)
+}
+
+func DeleteUserById(id uint) error {
+	old, err := db.GetUserById(id)
+	if err != nil {
+		return err
+	}
+	if old.IsAdmin() || old.IsGuest() {
+		return errs.DeleteAdminOrGuest
+	}
+	userCache.Del(old.Username)
+	return db.DeleteUserById(id)
+}
+
+func UpdateUser(u *model.User) error {
+	old, err := db.GetUserById(u.ID)
+	if err != nil {
+		return err
+	}
+	if u.IsAdmin() {
+		adminUser = nil
+	}
+	if u.IsGuest() {
+		guestUser = nil
+	}
+	userCache.Del(old.Username)
+	u.BasePath = utils.FixAndCleanPath(u.BasePath)
+	return db.UpdateUser(u)
+}
+
+func Cancel2FAByUser(u *model.User) error {
+	u.OtpSecret = ""
+	return UpdateUser(u)
+}
+
+func Cancel2FAById(id uint) error {
+	user, err := db.GetUserById(id)
+	if err != nil {
+		return err
+	}
+	return Cancel2FAByUser(user)
+}
diff --git a/internal/search/build.go b/internal/search/build.go
index 19c4a806d61..53b944f72fb 100644
--- a/internal/search/build.go
+++ b/internal/search/build.go
@@ -8,7 +8,6 @@ import (
 	"sync/atomic"
 	"time"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/fs"
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/op"
@@ -105,7 +104,7 @@ func BuildIndex(ctx context.Context, indexPaths, ignorePaths []string, maxDepth
 			Quit <- struct{}{}
 		}
 	}()
-	admin, err := db.GetAdmin()
+	admin, err := op.GetAdmin()
 	if err != nil {
 		return err
 	}
diff --git a/internal/search/search.go b/internal/search/search.go
index d9c208d02e2..c1a23b85ca8 100644
--- a/internal/search/search.go
+++ b/internal/search/search.go
@@ -5,9 +5,9 @@ import (
 	"fmt"
 
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/search/searcher"
 	log "github.com/sirupsen/logrus"
 )
@@ -88,7 +88,7 @@ func BatchIndex(ctx context.Context, objs []ObjWithParent) error {
 }
 
 func init() {
-	db.RegisterSettingItemHook(conf.SearchIndex, func(item *model.SettingItem) error {
+	op.RegisterSettingItemHook(conf.SearchIndex, func(item *model.SettingItem) error {
 		log.Debugf("searcher init, mode: %s", item.Value)
 		return Init(item.Value)
 	})
diff --git a/internal/search/util.go b/internal/search/util.go
index 709dab29c9f..cfb98c45f25 100644
--- a/internal/search/util.go
+++ b/internal/search/util.go
@@ -6,7 +6,6 @@ import (
 	"github.com/alist-org/alist/v3/drivers/alist_v3"
 	"github.com/alist-org/alist/v3/drivers/base"
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/setting"
@@ -26,7 +25,7 @@ func WriteProgress(progress *model.IndexProgress) {
 	if err != nil {
 		log.Errorf("marshal progress error: %+v", err)
 	}
-	err = db.SaveSettingItem(model.SettingItem{
+	err = op.SaveSettingItem(&model.SettingItem{
 		Key:   conf.IndexProgress,
 		Value: p,
 		Type:  conf.TypeText,
diff --git a/internal/setting/setting.go b/internal/setting/setting.go
index 2861380ab8b..cd77874bec8 100644
--- a/internal/setting/setting.go
+++ b/internal/setting/setting.go
@@ -3,18 +3,18 @@ package setting
 import (
 	"strconv"
 
-	"github.com/alist-org/alist/v3/internal/db"
+	"github.com/alist-org/alist/v3/internal/op"
 )
 
 func GetStr(key string, defaultValue ...string) string {
-	val, ok := db.GetSettingsMap().Load(key)
-	if !ok {
+	val, _ := op.GetSettingItemByKey(key)
+	if val == nil {
 		if len(defaultValue) > 0 {
 			return defaultValue[0]
 		}
 		return ""
 	}
-	return val
+	return val.Value
 }
 
 func GetInt(key string, defaultVal int) int {
diff --git a/pkg/utils/balance.go b/pkg/utils/balance.go
index 265c45f0681..e6df65c92cd 100644
--- a/pkg/utils/balance.go
+++ b/pkg/utils/balance.go
@@ -8,8 +8,8 @@ func IsBalance(str string) bool {
 	return strings.Contains(str, balance)
 }
 
-// GetActualVirtualPath remove balance suffix
-func GetActualVirtualPath(virtualPath string) string {
+// GetActualMountPath remove balance suffix
+func GetActualMountPath(virtualPath string) string {
 	bIndex := strings.LastIndex(virtualPath, ".balance")
 	if bIndex != -1 {
 		virtualPath = virtualPath[:bIndex]
diff --git a/pkg/utils/path.go b/pkg/utils/path.go
index 27328fbb795..45fe200e00d 100644
--- a/pkg/utils/path.go
+++ b/pkg/utils/path.go
@@ -4,6 +4,8 @@ import (
 	"net/url"
 	stdpath "path"
 	"strings"
+
+	"github.com/alist-org/alist/v3/internal/errs"
 )
 
 // FixAndCleanPath
@@ -36,6 +38,11 @@ func PathEqual(path1, path2 string) bool {
 	return FixAndCleanPath(path1) == FixAndCleanPath(path2)
 }
 
+func IsSubPath(path string, subPath string) bool {
+	path, subPath = FixAndCleanPath(path), FixAndCleanPath(subPath)
+	return path == subPath || strings.HasPrefix(path, subPath+"/")
+}
+
 func Ext(path string) string {
 	ext := stdpath.Ext(path)
 	if strings.HasPrefix(ext, ".") {
@@ -68,5 +75,8 @@ func EncodePath(path string, all ...bool) string {
 }
 
 func JoinBasePath(basePath, reqPath string) (string, error) {
+	if strings.HasSuffix(reqPath, "..") || strings.Contains(reqPath, "../") {
+		return "", errs.RelativePath
+	}
 	return stdpath.Join(FixAndCleanPath(basePath), FixAndCleanPath(reqPath)), nil
 }
diff --git a/pkg/utils/slice.go b/pkg/utils/slice.go
index c1d1584cd2a..a9733eb2a4a 100644
--- a/pkg/utils/slice.go
+++ b/pkg/utils/slice.go
@@ -1,5 +1,11 @@
 package utils
 
+import (
+	"strings"
+
+	"github.com/pkg/errors"
+)
+
 // SliceEqual check if two slices are equal
 func SliceEqual[T comparable](a, b []T) bool {
 	if len(a) != len(b) {
@@ -44,3 +50,13 @@ func MustSliceConvert[S any, D any](srcS []S, convert func(src S) D) []D {
 	}
 	return res
 }
+
+func MergeErrors(errs ...error) error {
+	errStr := strings.Join(MustSliceConvert(errs, func(err error) string {
+		return err.Error()
+	}), "\n")
+	if errStr != "" {
+		return errors.New(errStr)
+	}
+	return nil
+}
diff --git a/server/handles/aria2.go b/server/handles/aria2.go
index 9948a90a64f..f127bf04da3 100644
--- a/server/handles/aria2.go
+++ b/server/handles/aria2.go
@@ -3,8 +3,8 @@ package handles
 import (
 	"github.com/alist-org/alist/v3/internal/aria2"
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
 )
@@ -24,7 +24,7 @@ func SetAria2(c *gin.Context) {
 		{Key: conf.Aria2Uri, Value: req.Uri, Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
 		{Key: conf.Aria2Secret, Value: req.Secret, Type: conf.TypeString, Group: model.ARIA2, Flag: model.PRIVATE},
 	}
-	if err := db.SaveSettingItems(items); err != nil {
+	if err := op.SaveSettingItems(items); err != nil {
 		common.ErrorResp(c, err, 500)
 		return
 	}
diff --git a/server/handles/auth.go b/server/handles/auth.go
index 0ecd52d81be..920b7d75b12 100644
--- a/server/handles/auth.go
+++ b/server/handles/auth.go
@@ -7,8 +7,8 @@ import (
 	"time"
 
 	"github.com/Xhofe/go-cache"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
 	"github.com/pquerna/otp/totp"
@@ -41,7 +41,7 @@ func Login(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	user, err := db.GetUserByName(req.Username)
+	user, err := op.GetUserByName(req.Username)
 	if err != nil {
 		common.ErrorResp(c, err, 400)
 		loginCache.Set(ip, count+1)
@@ -101,7 +101,7 @@ func UpdateCurrent(c *gin.Context) {
 	if req.Password != "" {
 		user.Password = req.Password
 	}
-	if err := db.UpdateUser(user); err != nil {
+	if err := op.UpdateUser(user); err != nil {
 		common.ErrorResp(c, err, 500)
 	} else {
 		common.SuccessResp(c)
@@ -158,7 +158,7 @@ func Verify2FA(c *gin.Context) {
 		return
 	}
 	user.OtpSecret = req.Secret
-	if err := db.UpdateUser(user); err != nil {
+	if err := op.UpdateUser(user); err != nil {
 		common.ErrorResp(c, err, 500)
 	} else {
 		common.SuccessResp(c)
diff --git a/server/handles/fsmanage.go b/server/handles/fsmanage.go
index 1cfa44e92ae..7b95af5baf8 100644
--- a/server/handles/fsmanage.go
+++ b/server/handles/fsmanage.go
@@ -4,10 +4,10 @@ import (
 	"fmt"
 	stdpath "path"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/fs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/sign"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/server/common"
@@ -32,7 +32,7 @@ func FsMkdir(c *gin.Context) {
 		return
 	}
 	if !user.CanWrite() {
-		meta, err := db.GetNearestMeta(stdpath.Dir(reqPath))
+		meta, err := op.GetNearestMeta(stdpath.Dir(reqPath))
 		if err != nil {
 			if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 				common.ErrorResp(c, err, 500, true)
diff --git a/server/handles/fsread.go b/server/handles/fsread.go
index aaa90e811c6..4d4d617adb7 100644
--- a/server/handles/fsread.go
+++ b/server/handles/fsread.go
@@ -6,10 +6,10 @@ import (
 	"strings"
 	"time"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/fs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/sign"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/server/common"
@@ -61,7 +61,7 @@ func FsList(c *gin.Context) {
 		common.ErrorResp(c, err, 403)
 		return
 	}
-	meta, err := db.GetNearestMeta(reqPath)
+	meta, err := op.GetNearestMeta(reqPath)
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500, true)
@@ -118,7 +118,7 @@ func FsDirs(c *gin.Context) {
 		}
 		reqPath = tmp
 	}
-	meta, err := db.GetNearestMeta(reqPath)
+	meta, err := op.GetNearestMeta(reqPath)
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500, true)
@@ -233,7 +233,7 @@ func FsGet(c *gin.Context) {
 		common.ErrorResp(c, err, 403)
 		return
 	}
-	meta, err := db.GetNearestMeta(reqPath)
+	meta, err := op.GetNearestMeta(reqPath)
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500)
@@ -295,7 +295,7 @@ func FsGet(c *gin.Context) {
 	if err == nil {
 		related = filterRelated(sameLevelFiles, obj)
 	}
-	parentMeta, _ := db.GetNearestMeta(parentPath)
+	parentMeta, _ := op.GetNearestMeta(parentPath)
 	common.SuccessResp(c, FsGetResp{
 		ObjResp: ObjResp{
 			Name:     obj.GetName(),
@@ -344,7 +344,7 @@ func FsOther(c *gin.Context) {
 		common.ErrorResp(c, err, 403)
 		return
 	}
-	meta, err := db.GetNearestMeta(req.Path)
+	meta, err := op.GetNearestMeta(req.Path)
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500)
diff --git a/server/handles/meta.go b/server/handles/meta.go
index 348259074c6..e7454d9df5a 100644
--- a/server/handles/meta.go
+++ b/server/handles/meta.go
@@ -6,9 +6,8 @@ import (
 	"strconv"
 	"strings"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/alist-org/alist/v3/pkg/utils"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
 	log "github.com/sirupsen/logrus"
@@ -22,7 +21,7 @@ func ListMetas(c *gin.Context) {
 	}
 	req.Validate()
 	log.Debugf("%+v", req)
-	metas, total, err := db.GetMetas(req.Page, req.PerPage)
+	metas, total, err := op.GetMetas(req.Page, req.PerPage)
 	if err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
@@ -44,8 +43,7 @@ func CreateMeta(c *gin.Context) {
 		common.ErrorStrResp(c, fmt.Sprintf("%s is illegal: %s", r, err.Error()), 400)
 		return
 	}
-	req.Path = utils.FixAndCleanPath(req.Path)
-	if err := db.CreateMeta(&req); err != nil {
+	if err := op.CreateMeta(&req); err != nil {
 		common.ErrorResp(c, err, 500, true)
 	} else {
 		common.SuccessResp(c)
@@ -63,8 +61,7 @@ func UpdateMeta(c *gin.Context) {
 		common.ErrorStrResp(c, fmt.Sprintf("%s is illegal: %s", r, err.Error()), 400)
 		return
 	}
-	req.Path = utils.FixAndCleanPath(req.Path)
-	if err := db.UpdateMeta(&req); err != nil {
+	if err := op.UpdateMeta(&req); err != nil {
 		common.ErrorResp(c, err, 500, true)
 	} else {
 		common.SuccessResp(c)
@@ -89,7 +86,7 @@ func DeleteMeta(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	if err := db.DeleteMetaById(uint(id)); err != nil {
+	if err := op.DeleteMetaById(uint(id)); err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
 	}
@@ -103,7 +100,7 @@ func GetMeta(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	meta, err := db.GetMetaById(uint(id))
+	meta, err := op.GetMetaById(uint(id))
 	if err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
diff --git a/server/handles/search.go b/server/handles/search.go
index 8b4d72d695a..8881731bd60 100644
--- a/server/handles/search.go
+++ b/server/handles/search.go
@@ -4,9 +4,9 @@ import (
 	"path"
 	"strings"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/search"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/server/common"
@@ -53,7 +53,7 @@ func Search(c *gin.Context) {
 		if !strings.HasPrefix(node.Parent, user.BasePath) {
 			continue
 		}
-		meta, err := db.GetNearestMeta(node.Parent)
+		meta, err := op.GetNearestMeta(node.Parent)
 		if err != nil && !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			continue
 		}
diff --git a/server/handles/setting.go b/server/handles/setting.go
index 00430302db4..f778b1803c5 100644
--- a/server/handles/setting.go
+++ b/server/handles/setting.go
@@ -5,8 +5,8 @@ import (
 	"strings"
 
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/sign"
 	"github.com/alist-org/alist/v3/pkg/utils/random"
 	"github.com/alist-org/alist/v3/server/common"
@@ -17,7 +17,7 @@ import (
 func ResetToken(c *gin.Context) {
 	token := random.Token()
 	item := model.SettingItem{Key: "token", Value: token, Type: conf.TypeString, Group: model.SINGLE, Flag: model.PRIVATE}
-	if err := db.SaveSettingItem(item); err != nil {
+	if err := op.SaveSettingItem(&item); err != nil {
 		common.ErrorResp(c, err, 500)
 		return
 	}
@@ -29,14 +29,14 @@ func GetSetting(c *gin.Context) {
 	key := c.Query("key")
 	keys := c.Query("keys")
 	if key != "" {
-		item, err := db.GetSettingItemByKey(key)
+		item, err := op.GetSettingItemByKey(key)
 		if err != nil {
 			common.ErrorResp(c, err, 400)
 			return
 		}
 		common.SuccessResp(c, item)
 	} else {
-		items, err := db.GetSettingItemInKeys(strings.Split(keys, ","))
+		items, err := op.GetSettingItemInKeys(strings.Split(keys, ","))
 		if err != nil {
 			common.ErrorResp(c, err, 400)
 			return
@@ -51,7 +51,7 @@ func SaveSettings(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	if err := db.SaveSettingItems(req); err != nil {
+	if err := op.SaveSettingItems(req); err != nil {
 		common.ErrorResp(c, err, 500)
 	} else {
 		common.SuccessResp(c)
@@ -65,7 +65,7 @@ func ListSettings(c *gin.Context) {
 	var settings []model.SettingItem
 	var err error
 	if groupsStr == "" && groupStr == "" {
-		settings, err = db.GetSettingItems()
+		settings, err = op.GetSettingItems()
 	} else {
 		var groupStrings []string
 		if groupsStr != "" {
@@ -82,7 +82,7 @@ func ListSettings(c *gin.Context) {
 			}
 			groups = append(groups, group)
 		}
-		settings, err = db.GetSettingItemsInGroups(groups)
+		settings, err = op.GetSettingItemsInGroups(groups)
 	}
 	if err != nil {
 		common.ErrorResp(c, err, 400)
@@ -93,7 +93,7 @@ func ListSettings(c *gin.Context) {
 
 func DeleteSetting(c *gin.Context) {
 	key := c.Query("key")
-	if err := db.DeleteSettingItemByKey(key); err != nil {
+	if err := op.DeleteSettingItemByKey(key); err != nil {
 		common.ErrorResp(c, err, 500)
 		return
 	}
@@ -101,5 +101,5 @@ func DeleteSetting(c *gin.Context) {
 }
 
 func PublicSettings(c *gin.Context) {
-	common.SuccessResp(c, db.GetPublicSettingsMap())
+	common.SuccessResp(c, op.GetPublicSettingsMap())
 }
diff --git a/server/handles/user.go b/server/handles/user.go
index cbee8f33be6..22d4e87f5e4 100644
--- a/server/handles/user.go
+++ b/server/handles/user.go
@@ -3,8 +3,8 @@ package handles
 import (
 	"strconv"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
 	log "github.com/sirupsen/logrus"
@@ -18,7 +18,7 @@ func ListUsers(c *gin.Context) {
 	}
 	req.Validate()
 	log.Debugf("%+v", req)
-	users, total, err := db.GetUsers(req.Page, req.PerPage)
+	users, total, err := op.GetUsers(req.Page, req.PerPage)
 	if err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
@@ -39,7 +39,7 @@ func CreateUser(c *gin.Context) {
 		common.ErrorStrResp(c, "admin or guest user can not be created", 400, true)
 		return
 	}
-	if err := db.CreateUser(&req); err != nil {
+	if err := op.CreateUser(&req); err != nil {
 		common.ErrorResp(c, err, 500, true)
 	} else {
 		common.SuccessResp(c)
@@ -52,7 +52,7 @@ func UpdateUser(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	user, err := db.GetUserById(req.ID)
+	user, err := op.GetUserById(req.ID)
 	if err != nil {
 		common.ErrorResp(c, err, 500)
 		return
@@ -67,7 +67,7 @@ func UpdateUser(c *gin.Context) {
 	if req.OtpSecret == "" {
 		req.OtpSecret = user.OtpSecret
 	}
-	if err := db.UpdateUser(&req); err != nil {
+	if err := op.UpdateUser(&req); err != nil {
 		common.ErrorResp(c, err, 500)
 	} else {
 		common.SuccessResp(c)
@@ -81,7 +81,7 @@ func DeleteUser(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	if err := db.DeleteUserById(uint(id)); err != nil {
+	if err := op.DeleteUserById(uint(id)); err != nil {
 		common.ErrorResp(c, err, 500)
 		return
 	}
@@ -95,7 +95,7 @@ func GetUser(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	user, err := db.GetUserById(uint(id))
+	user, err := op.GetUserById(uint(id))
 	if err != nil {
 		common.ErrorResp(c, err, 500, true)
 		return
@@ -110,7 +110,7 @@ func Cancel2FAById(c *gin.Context) {
 		common.ErrorResp(c, err, 400)
 		return
 	}
-	if err := db.Cancel2FAById(uint(id)); err != nil {
+	if err := op.Cancel2FAById(uint(id)); err != nil {
 		common.ErrorResp(c, err, 500)
 		return
 	}
diff --git a/server/middlewares/auth.go b/server/middlewares/auth.go
index c22f282574c..7ad404258b3 100644
--- a/server/middlewares/auth.go
+++ b/server/middlewares/auth.go
@@ -2,8 +2,8 @@ package middlewares
 
 import (
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/setting"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
@@ -15,7 +15,7 @@ import (
 func Auth(c *gin.Context) {
 	token := c.GetHeader("Authorization")
 	if token == setting.GetStr(conf.Token) {
-		admin, err := db.GetAdmin()
+		admin, err := op.GetAdmin()
 		if err != nil {
 			common.ErrorResp(c, err, 500)
 			c.Abort()
@@ -27,7 +27,7 @@ func Auth(c *gin.Context) {
 		return
 	}
 	if token == "" {
-		guest, err := db.GetGuest()
+		guest, err := op.GetGuest()
 		if err != nil {
 			common.ErrorResp(c, err, 500)
 			c.Abort()
@@ -44,7 +44,7 @@ func Auth(c *gin.Context) {
 		c.Abort()
 		return
 	}
-	user, err := db.GetUserByName(userClaims.Username)
+	user, err := op.GetUserByName(userClaims.Username)
 	if err != nil {
 		common.ErrorResp(c, err, 401)
 		c.Abort()
diff --git a/server/middlewares/down.go b/server/middlewares/down.go
index 411d053ceaf..48e9ea724a6 100644
--- a/server/middlewares/down.go
+++ b/server/middlewares/down.go
@@ -4,9 +4,9 @@ import (
 	"strings"
 
 	"github.com/alist-org/alist/v3/internal/conf"
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/internal/setting"
 	"github.com/alist-org/alist/v3/internal/sign"
 	"github.com/alist-org/alist/v3/pkg/utils"
@@ -18,7 +18,7 @@ import (
 func Down(c *gin.Context) {
 	rawPath := parsePath(c.Param("path"))
 	c.Set("path", rawPath)
-	meta, err := db.GetNearestMeta(rawPath)
+	meta, err := op.GetNearestMeta(rawPath)
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500, true)
diff --git a/server/middlewares/fsup.go b/server/middlewares/fsup.go
index 3e503cc6744..809e8f299eb 100644
--- a/server/middlewares/fsup.go
+++ b/server/middlewares/fsup.go
@@ -4,9 +4,9 @@ import (
 	"net/url"
 	stdpath "path"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/errs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/server/common"
 	"github.com/gin-gonic/gin"
 	"github.com/pkg/errors"
@@ -27,7 +27,7 @@ func FsUp(c *gin.Context) {
 		common.ErrorResp(c, err, 403)
 		return
 	}
-	meta, err := db.GetNearestMeta(stdpath.Dir(path))
+	meta, err := op.GetNearestMeta(stdpath.Dir(path))
 	if err != nil {
 		if !errors.Is(errors.Cause(err), errs.MetaNotFound) {
 			common.ErrorResp(c, err, 500, true)
diff --git a/server/webdav.go b/server/webdav.go
index ddd95028467..6c97266e600 100644
--- a/server/webdav.go
+++ b/server/webdav.go
@@ -4,8 +4,8 @@ import (
 	"context"
 	"net/http"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils"
 	"github.com/alist-org/alist/v3/server/webdav"
 	"github.com/gin-gonic/gin"
@@ -45,7 +45,7 @@ func ServeWebDAV(c *gin.Context) {
 }
 
 func WebDAVAuth(c *gin.Context) {
-	guest, _ := db.GetGuest()
+	guest, _ := op.GetGuest()
 	username, password, ok := c.Request.BasicAuth()
 	if !ok {
 		if c.Request.Method == "OPTIONS" {
@@ -58,7 +58,7 @@ func WebDAVAuth(c *gin.Context) {
 		c.Abort()
 		return
 	}
-	user, err := db.GetUserByName(username)
+	user, err := op.GetUserByName(username)
 	if err != nil || user.ValidatePassword(password) != nil {
 		if c.Request.Method == "OPTIONS" {
 			c.Set("user", guest)
diff --git a/server/webdav/file.go b/server/webdav/file.go
index 4840f12667c..c053a4cbc5c 100644
--- a/server/webdav/file.go
+++ b/server/webdav/file.go
@@ -10,9 +10,9 @@ import (
 	"path"
 	"path/filepath"
 
-	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/fs"
 	"github.com/alist-org/alist/v3/internal/model"
+	"github.com/alist-org/alist/v3/internal/op"
 )
 
 // slashClean is equivalent to but slightly more efficient than
@@ -85,7 +85,7 @@ func walkFS(ctx context.Context, depth int, name string, info model.Obj, walkFn
 	if depth == 1 {
 		depth = 0
 	}
-	meta, _ := db.GetNearestMeta(name)
+	meta, _ := op.GetNearestMeta(name)
 	// Read directory names.
 	objs, err := fs.List(context.WithValue(ctx, "meta", meta), name)
 	//f, err := fs.OpenFile(ctx, name, os.O_RDONLY, 0)


Test patch:
diff --git a/internal/db/meta_test.go b/internal/db/meta_test.go
deleted file mode 100644
index c44b85c60fb..00000000000
--- a/internal/db/meta_test.go
+++ /dev/null
@@ -1,60 +0,0 @@
-package db
-
-import (
-	"testing"
-
-	"github.com/alist-org/alist/v3/internal/errs"
-	"github.com/alist-org/alist/v3/internal/model"
-	"github.com/pkg/errors"
-	"gorm.io/driver/sqlite"
-	"gorm.io/gorm"
-)
-
-func init() {
-	db, err := gorm.Open(sqlite.Open("file::memory:?cache=shared"), &gorm.Config{})
-	if err != nil {
-		panic("failed to connect database")
-	}
-	Init(db)
-}
-
-func TestCreateMeta(t *testing.T) {
-	metas := []model.Meta{
-		{Path: "/"},
-		{Path: "/a"},
-		{Path: "/a/b"},
-		{Path: "/a/b/c"},
-	}
-	for _, meta := range metas {
-		err := CreateMeta(&meta)
-		if err != nil {
-			t.Errorf("failed to create meta: %+v", err)
-		}
-	}
-}
-
-func TestUpdateMeta(t *testing.T) {
-	meta := model.Meta{ID: 1, Path: "/b"}
-	err := UpdateMeta(&meta)
-	if err != nil {
-		t.Errorf("failed to update meta: %+v", err)
-	}
-}
-
-func TestGetNearestMeta1(t *testing.T) {
-	meta, err := GetNearestMeta("/b/c/d")
-	if err != nil {
-		t.Errorf("failed to get nearest meta: %+v", err)
-	}
-	if meta.Path != "/b" {
-		t.Errorf("unexpected meta: %+v", meta)
-	}
-}
-
-func TestGetNearestMeta2(t *testing.T) {
-	meta, err := GetNearestMeta("/c/d/e")
-	if errors.Cause(err) != errs.MetaNotFound {
-		t.Errorf("unexpected error: %+v", err)
-		t.Errorf("unexpected meta: %+v", meta)
-	}
-}
diff --git a/internal/op/storage_test.go b/internal/op/storage_test.go
index 390fb0d007d..f3d0b4e64b7 100644
--- a/internal/op/storage_test.go
+++ b/internal/op/storage_test.go
@@ -4,10 +4,12 @@ import (
 	"context"
 	"testing"
 
+	"github.com/alist-org/alist/v3/internal/conf"
 	"github.com/alist-org/alist/v3/internal/db"
 	"github.com/alist-org/alist/v3/internal/model"
 	"github.com/alist-org/alist/v3/internal/op"
 	"github.com/alist-org/alist/v3/pkg/utils"
+	mapset "github.com/deckarep/golang-set/v2"
 	"gorm.io/driver/sqlite"
 	"gorm.io/gorm"
 )
@@ -17,6 +19,7 @@ func init() {
 	if err != nil {
 		panic("failed to connect database")
 	}
+	conf.Conf = conf.DefaultConfig()
 	db.Init(dB)
 }
 
@@ -25,9 +28,9 @@ func TestCreateStorage(t *testing.T) {
 		storage model.Storage
 		isErr   bool
 	}{
-		{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: false},
-		{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder":"."}`}, isErr: true},
-		{storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder":"."}`}, isErr: true},
+		{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder_path":"."}`}, isErr: false},
+		{storage: model.Storage{Driver: "Local", MountPath: "/local", Addition: `{"root_folder_path":"."}`}, isErr: true},
+		{storage: model.Storage{Driver: "None", MountPath: "/none", Addition: `{"root_folder_path":"."}`}, isErr: true},
 	}
 	for _, storage := range storages {
 		_, err := op.CreateStorage(context.Background(), storage.storage)
@@ -58,23 +61,26 @@ func TestGetStorageVirtualFilesByPath(t *testing.T) {
 
 func TestGetBalancedStorage(t *testing.T) {
 	setupStorages(t)
-	storage := op.GetBalancedStorage("/a/d/e")
-	if storage.GetStorage().MountPath != "/a/d/e" {
-		t.Errorf("expected: /a/d/e, got: %+v", storage.GetStorage().MountPath)
+	set := mapset.NewSet[string]()
+	for i := 0; i < 5; i++ {
+		storage := op.GetBalancedStorage("/a/d/e1")
+		set.Add(storage.GetStorage().MountPath)
 	}
-	storage = op.GetBalancedStorage("/a/d/e")
-	if storage.GetStorage().MountPath != "/a/d/e.balance" {
-		t.Errorf("expected: /a/d/e.balance, got: %+v", storage.GetStorage().MountPath)
+	expected := mapset.NewSet([]string{"/a/d/e1", "/a/d/e1.balance"}...)
+	if !expected.Equal(set) {
+		t.Errorf("expected: %+v, got: %+v", expected, set)
 	}
 }
 
 func setupStorages(t *testing.T) {
 	var storages = []model.Storage{
-		{Driver: "Local", MountPath: "/a/b", Order: 0, Addition: `{"root_folder":"."}`},
-		{Driver: "Local", MountPath: "/a/c", Order: 1, Addition: `{"root_folder":"."}`},
-		{Driver: "Local", MountPath: "/a/d", Order: 2, Addition: `{"root_folder":"."}`},
-		{Driver: "Local", MountPath: "/a/d/e", Order: 3, Addition: `{"root_folder":"."}`},
-		{Driver: "Local", MountPath: "/a/d/e.balance", Order: 4, Addition: `{"root_folder":"."}`},
+		{Driver: "Local", MountPath: "/a/b", Order: 0, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/adc", Order: 0, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/a/c", Order: 1, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/a/d", Order: 2, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/a/d/e1", Order: 3, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/a/d/e", Order: 4, Addition: `{"root_folder_path":"."}`},
+		{Driver: "Local", MountPath: "/a/d/e1.balance", Order: 4, Addition: `{"root_folder_path":"."}`},
 	}
 	for _, storage := range storages {
 		_, err := op.CreateStorage(context.Background(), storage)