整体平台上传功能大改造
This commit is contained in:
@@ -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"
|
||||
}
|
||||
Reference in New Issue
Block a user