整体平台上传功能大改造

This commit is contained in:
2026-09-09 10:49:57 +08:00
parent d2643a3697
commit de7225b61e
21 changed files with 3835 additions and 2622 deletions
+120
View File
@@ -0,0 +1,120 @@
// 存量文件迁移到分层目录(文件存储分层改造 S9)
//
// 用法(在 go/ 目录下执行):
//
// go run ./cmd/migrate_storage # 默认 dry-run,只打印计划,不改动任何数据
// go run ./cmd/migrate_storage --apply # 真正执行迁移
// go run ./cmd/migrate_storage --tid=234573 # 只迁移指定租户
// go run ./cmd/migrate_storage --limit=10 # 只处理前 N 条(调试用)
//
// 迁移原理:
// - 七牛云:BucketManager.Move 同 bucket 内服务端改名,不重新上传、不消耗流量、与文件大小无关
// - 本地 :os.Rename 同盘改名,不搬数据
//
// 幂等:目标 key 已存在时跳过(Skipped),可重复执行。
//
// 注意:老数据没有「个人/共享」归属信息(tuid 全为 NULL、uid 是上传者),
//
// 因此老数据统一按「租户共享」迁移,个人目录只对新上传的文件生效。
package main
import (
"flag"
"fmt"
"os"
"server/models"
"server/services"
)
func main() {
apply := flag.Bool("apply", false, "真正执行迁移;不传则只打印计划(dry-run)")
tid := flag.Uint64("tid", 0, "只迁移指定租户,0 表示全部")
limit := flag.Int("limit", 0, "只处理前 N 条,0 表示不限制")
flag.Parse()
// 注意:需在 go/ 目录下运行,beego 会自动加载 conf/app.conf
models.Init("")
if models.Orm == nil {
fmt.Println("数据库未初始化")
os.Exit(1)
}
models.EnsureSystemFileStorageColumns()
storageSvc, err := services.GetStorageService()
if err != nil {
fmt.Println("获取存储服务失败:", err)
os.Exit(1)
}
fmt.Printf("当前存储类型: %s\n", storageSvc.Type())
qs := models.Orm.QueryTable(new(models.SystemFile)).Filter("delete_time__isnull", true)
if *tid > 0 {
qs = qs.Filter("tid", *tid)
}
if *limit > 0 {
qs = qs.Limit(*limit)
}
var files []models.SystemFile
if _, err := qs.OrderBy("id").All(&files); err != nil {
fmt.Println("读取文件列表失败:", err)
os.Exit(1)
}
fmt.Printf("待处理文件数: %d(模式: %s)\n\n", len(files), modeName(*apply))
planned, skipped, failed := 0, 0, 0
for i := range files {
f := files[i]
oldKey := f.ObjectKey
if oldKey == "" {
oldKey = services.KeyFromSrc(f.Src, storageSvc)
}
if oldKey == "" {
fmt.Printf("[跳过] id=%d 无法解析原路径: %s\n", f.ID, f.Src)
skipped++
continue
}
newKey := services.TargetKey(&f)
if oldKey == newKey {
skipped++
continue
}
planned++
fmt.Printf("[%s] id=%d tid=%d\n 老: %s\n 新: %s\n", modeName(*apply), f.ID, f.Tid, oldKey, newKey)
if !*apply {
continue
}
if err := storageSvc.Move(oldKey, newKey); err != nil {
fmt.Printf(" !! 迁移失败: %v\n", err)
failed++
continue
}
newSrc := storageSvc.GetPublicURL(newKey)
if _, err := models.Orm.QueryTable(new(models.SystemFile)).
Filter("id", f.ID).
Update(map[string]interface{}{
"src": newSrc,
"object_key": newKey,
"storage": storageSvc.Type(),
}); err != nil {
fmt.Printf(" !! 更新数据库失败(文件已移动,请重跑本脚本): %v\n", err)
failed++
}
}
fmt.Printf("\n==== 汇总 ====\n计划迁移: %d\n跳过: %d\n失败: %d\n", planned, skipped, failed)
if !*apply {
fmt.Println("当前为 dry-run,未改动任何数据。确认无误后加 --apply 执行。")
}
}
func modeName(apply bool) string {
if apply {
return "APPLY"
}
return "DRY-RUN"
}