11import { DbTables } from "../../../../constants/index.js" ;
22import { LegacyDbTables } from "./legacyKeys.js" ;
3+ import { StorageFactory } from "../../../../storage/factory/StorageFactory.js" ;
4+ import { toBool } from "../../../../utils/environmentUtils.js" ;
35import {
46 createFsMetaTables ,
57 createFsSearchIndexTables ,
@@ -9,6 +11,7 @@ import {
911 createUploadPartsTables ,
1012 createUploadSessionsTables ,
1113 createVfsTables ,
14+ createMetricsCacheTables ,
1215} from "./schema.js" ;
1316import {
1417 addCustomContentSettings ,
@@ -24,6 +27,98 @@ import {
2427 *
2528 */
2629
30+ function getBooleanFieldNamesFromStorageSchema ( storageType ) {
31+ if ( ! storageType ) return [ ] ;
32+ const meta = StorageFactory . getTypeMetadata ( storageType ) ;
33+ const fields = meta ?. configSchema ?. fields ;
34+ if ( ! Array . isArray ( fields ) ) return [ ] ;
35+ return fields
36+ . filter ( ( f ) => f && typeof f === "object" && typeof f . name === "string" && ( f . type === "boolean" || f . type === "bool" ) )
37+ . map ( ( f ) => f . name )
38+ . filter ( Boolean ) ;
39+ }
40+
41+ function coerceConfigJsonBooleans ( storageType , configJsonObj ) {
42+ if ( ! configJsonObj || typeof configJsonObj !== "object" ) {
43+ return { changed : false , next : configJsonObj } ;
44+ }
45+ const boolKeys = getBooleanFieldNamesFromStorageSchema ( storageType ) ;
46+ if ( ! boolKeys . length ) {
47+ return { changed : false , next : configJsonObj } ;
48+ }
49+
50+ let changed = false ;
51+ for ( const key of boolKeys ) {
52+ if ( ! Object . prototype . hasOwnProperty . call ( configJsonObj , key ) ) continue ;
53+ const raw = configJsonObj [ key ] ;
54+ if ( raw === undefined || raw === null ) continue ;
55+ const nextVal = toBool ( raw , false ) ? 1 : 0 ;
56+ if ( configJsonObj [ key ] !== nextVal ) {
57+ configJsonObj [ key ] = nextVal ;
58+ changed = true ;
59+ }
60+ }
61+
62+ return { changed, next : configJsonObj } ;
63+ }
64+
65+ async function normalizeStorageConfigsBooleanFields ( db ) {
66+ console . log ( "版本34:开始归一化 storage_configs.config_json 中的布尔字段(统一为 0/1)..." ) ;
67+
68+ let rows = [ ] ;
69+ try {
70+ const res = await db
71+ . prepare ( `SELECT id, storage_type, config_json FROM ${ DbTables . STORAGE_CONFIGS } ORDER BY updated_at DESC` )
72+ . all ( ) ;
73+ rows = Array . isArray ( res ?. results ) ? res . results : [ ] ;
74+ } catch ( e ) {
75+ console . warn ( "版本34:读取 storage_configs 失败,跳过布尔字段归一化:" , e ?. message || e ) ;
76+ return { total : 0 , updated : 0 , failed : 0 , skipped : 0 } ;
77+ }
78+
79+ let updated = 0 ;
80+ let skipped = 0 ;
81+ let failed = 0 ;
82+
83+ for ( const row of rows ) {
84+ const id = row ?. id ? String ( row . id ) : "" ;
85+ const storageType = row ?. storage_type ? String ( row . storage_type ) : "" ;
86+ const rawJson = row ?. config_json ;
87+ if ( ! id || ! storageType || ! rawJson ) {
88+ skipped += 1 ;
89+ continue ;
90+ }
91+
92+ let cfgObj ;
93+ try {
94+ cfgObj = typeof rawJson === "string" ? JSON . parse ( rawJson ) : rawJson ;
95+ } catch {
96+ failed += 1 ;
97+ continue ;
98+ }
99+
100+ const { changed, next } = coerceConfigJsonBooleans ( storageType , cfgObj ) ;
101+ if ( ! changed ) {
102+ skipped += 1 ;
103+ continue ;
104+ }
105+
106+ try {
107+ await db
108+ . prepare ( `UPDATE ${ DbTables . STORAGE_CONFIGS } SET config_json = ?, updated_at = CURRENT_TIMESTAMP WHERE id = ?` )
109+ . bind ( JSON . stringify ( next ) , id )
110+ . run ( ) ;
111+ updated += 1 ;
112+ } catch ( e ) {
113+ failed += 1 ;
114+ console . warn ( "版本34:归一化 storage_config 失败:" , { id, storageType, error : e ?. message || e } ) ;
115+ }
116+ }
117+
118+ console . log ( "版本34:storage_configs 布尔字段归一化完成:" , { total : rows . length , updated, skipped, failed } ) ;
119+ return { total : rows . length , updated, skipped, failed } ;
120+ }
121+
27122export async function addTableField ( db , tableName , fieldName , fieldDefinition ) {
28123 try {
29124 const columnInfo = await db . prepare ( `PRAGMA table_info(${ tableName } )` ) . all ( ) ;
@@ -689,6 +784,51 @@ export async function runLegacyMigrationByVersion(db, version) {
689784 }
690785 break ;
691786
787+ case 34 : {
788+ console . log ( "版本34:新增 metrics_cache(用量快照缓存)+ 默认快照刷新任务..." ) ;
789+ try {
790+ await createMetricsCacheTables ( db ) ;
791+ } catch ( e ) {
792+ console . warn ( "版本34:创建 metrics_cache 失败(可忽略,后续会再次尝试):" , e ?. message || e ) ;
793+ }
794+ try {
795+ const intervalSec = 6 * 60 * 60 ;
796+ const nextRunAfterIso = new Date ( Date . now ( ) + intervalSec * 1000 ) . toISOString ( ) ;
797+ await db
798+ . prepare (
799+ `
800+ INSERT INTO ${ DbTables . SCHEDULED_JOBS } (task_id, handler_id, name, description, enabled, schedule_type, interval_sec, next_run_after, config_json)
801+ SELECT ?, ?, ?, ?, 1, 'interval', ?, ?, ?
802+ WHERE NOT EXISTS (
803+ SELECT 1 FROM ${ DbTables . SCHEDULED_JOBS } WHERE task_id = ?
804+ )
805+ ` ,
806+ )
807+ . bind (
808+ "refresh_storage_usage_snapshots" ,
809+ "refresh_storage_usage_snapshots" ,
810+ "刷新存储用量快照(默认)" ,
811+ "定期刷新存储用量数据(已用/总量)。用于上传容量限制判断与管理端展示。" ,
812+ intervalSec ,
813+ nextRunAfterIso ,
814+ JSON . stringify ( { maxItems : 50 , maxConcurrency : 1 } ) ,
815+ "refresh_storage_usage_snapshots" ,
816+ )
817+ . run ( ) ;
818+ } catch ( e ) {
819+ console . warn ( "版本34:写入默认 refresh_storage_usage_snapshots 任务失败(可忽略):" , e ?. message || e ) ;
820+ }
821+
822+ // 归一化 storage_configs.config_json 中的布尔字段(统一为 0/1)
823+ try {
824+ await normalizeStorageConfigsBooleanFields ( db ) ;
825+ } catch ( e ) {
826+ console . warn ( "版本34:归一化 storage_configs 布尔字段失败(可忽略,将由后续保存配置逐步修复):" , e ?. message || e ) ;
827+ }
828+
829+ break ;
830+ }
831+
692832 default :
693833 console . log ( `未知的迁移版本: ${ version } ` ) ;
694834 break ;
0 commit comments