Browse Source

Merge branch 'main' of http://gitlab.pubdata.cn/hlm/multi-platform-media-manage

Ethanfly 12 giờ trước cách đây
mục cha
commit
57d1e157ba

+ 46 - 0
database/migrations/fix_user_day_statistics_timestamp_format.sql

@@ -0,0 +1,46 @@
+-- 修复 user_day_statistics 表的 created_at 和 updated_at 时间格式
+-- 将 TIMESTAMP 类型改为 DATETIME 类型,确保时间格式为 2026-01-12 12:22:22
+-- 执行日期: 2026-01-29
+
+USE media_manager;
+
+-- 步骤1: 备份现有数据(建议先备份,以防万一)
+-- CREATE TABLE user_day_statistics_backup_20260129 AS SELECT * FROM user_day_statistics;
+
+-- 步骤2: 设置会话时区为东八区(确保时间转换正确)
+SET time_zone = '+08:00';
+
+-- 步骤3: 修改 created_at 字段类型为 DATETIME
+-- MySQL 会自动将 TIMESTAMP 转换为 DATETIME,保持时间值不变
+-- 先移除默认值约束(MySQL 8.0+ 需要)
+ALTER TABLE user_day_statistics 
+MODIFY COLUMN created_at DATETIME NULL;
+
+-- 重新添加默认值约束
+ALTER TABLE user_day_statistics 
+MODIFY COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP;
+
+-- 步骤4: 修改 updated_at 字段类型为 DATETIME
+ALTER TABLE user_day_statistics 
+MODIFY COLUMN updated_at DATETIME NULL;
+
+-- 重新添加默认值约束
+ALTER TABLE user_day_statistics 
+MODIFY COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP;
+
+-- 步骤5: 验证修改结果
+-- 查看表结构
+DESCRIBE user_day_statistics;
+
+-- 查看几条数据的时间格式(应该显示为 2026-01-12 12:22:22 格式)
+SELECT 
+    id, 
+    account_id, 
+    record_date, 
+    DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at_formatted,
+    DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at_formatted,
+    created_at,
+    updated_at
+FROM user_day_statistics 
+ORDER BY id DESC 
+LIMIT 10;

+ 2 - 2
database/schema.sql

@@ -218,8 +218,8 @@ CREATE TABLE IF NOT EXISTS user_day_statistics (
     avg_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '平均观看时长(秒)',
     total_watch_duration VARCHAR(50) DEFAULT '0' COMMENT '观看总时长(秒)',
     completion_rate VARCHAR(50) DEFAULT '0' COMMENT '视频完播率',
-    created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
-    updated_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
+    created_at DATETIME DEFAULT CURRENT_TIMESTAMP,
+    updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
     UNIQUE KEY uk_account_date (account_id, record_date),
     INDEX idx_account_id (account_id),
     INDEX idx_record_date (record_date),

+ 76 - 0
server/src/scripts/check-timestamp-format.ts

@@ -0,0 +1,76 @@
+import { AppDataSource } from '../models/index.js';
+import { logger } from '../utils/logger.js';
+
+async function checkTimestampFormat() {
+  try {
+    await AppDataSource.initialize();
+    logger.info('[Check] Database connected');
+
+    // 查看表结构
+    const tableStructure = await AppDataSource.query(`
+      SELECT 
+        COLUMN_NAME,
+        DATA_TYPE,
+        COLUMN_TYPE,
+        COLUMN_DEFAULT,
+        IS_NULLABLE
+      FROM INFORMATION_SCHEMA.COLUMNS
+      WHERE TABLE_SCHEMA = DATABASE()
+        AND TABLE_NAME = 'user_day_statistics'
+        AND COLUMN_NAME IN ('created_at', 'updated_at')
+      ORDER BY COLUMN_NAME
+    `);
+
+    logger.info('[Check] Table structure:');
+    for (const col of tableStructure) {
+      logger.info(`[Check]   ${col.COLUMN_NAME}:`);
+      logger.info(`[Check]     DATA_TYPE: ${col.DATA_TYPE}`);
+      logger.info(`[Check]     COLUMN_TYPE: ${col.COLUMN_TYPE}`);
+      logger.info(`[Check]     DEFAULT: ${col.COLUMN_DEFAULT}`);
+    }
+
+    // 查看实际数据
+    logger.info('[Check] Sample data (raw):');
+    const sampleData = await AppDataSource.query(`
+      SELECT 
+        id,
+        account_id,
+        record_date,
+        created_at,
+        updated_at,
+        DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at_formatted,
+        DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at_formatted
+      FROM user_day_statistics
+      ORDER BY id DESC
+      LIMIT 10
+    `);
+
+    for (const row of sampleData) {
+      logger.info(`[Check]   ID ${row.id}:`);
+      logger.info(`[Check]     created_at (raw): ${row.created_at}`);
+      logger.info(`[Check]     created_at (formatted): ${row.created_at_formatted}`);
+      logger.info(`[Check]     updated_at (raw): ${row.updated_at}`);
+      logger.info(`[Check]     updated_at (formatted): ${row.updated_at_formatted}`);
+    }
+
+    // 检查是否有 TIMESTAMP 类型的数据需要转换
+    const timestampCheck = await AppDataSource.query(`
+      SELECT COUNT(*) as count
+      FROM user_day_statistics
+      WHERE created_at IS NOT NULL
+    `);
+
+    logger.info(`[Check] Total records with timestamps: ${timestampCheck[0].count}`);
+
+    process.exit(0);
+  } catch (error) {
+    logger.error('[Check] Error:', error);
+    process.exit(1);
+  } finally {
+    if (AppDataSource.isInitialized) {
+      await AppDataSource.destroy();
+    }
+  }
+}
+
+void checkTimestampFormat();

+ 121 - 0
server/src/scripts/fix-datetime-precision.ts

@@ -0,0 +1,121 @@
+import { AppDataSource } from '../models/index.js';
+import { logger } from '../utils/logger.js';
+
+async function fixDatetimePrecision() {
+  try {
+    await AppDataSource.initialize();
+    logger.info('[Fix] Database connected');
+
+    // 设置时区
+    await AppDataSource.query("SET time_zone = '+08:00'");
+    logger.info('[Fix] Timezone set to +08:00');
+
+    // 检查当前精度
+    const currentStructure = await AppDataSource.query(`
+      SELECT 
+        COLUMN_NAME,
+        COLUMN_TYPE
+      FROM INFORMATION_SCHEMA.COLUMNS
+      WHERE TABLE_SCHEMA = DATABASE()
+        AND TABLE_NAME = 'user_day_statistics'
+        AND COLUMN_NAME IN ('created_at', 'updated_at')
+    `);
+
+    logger.info('[Fix] Current column types:');
+    for (const col of currentStructure) {
+      logger.info(`[Fix]   ${col.COLUMN_NAME}: ${col.COLUMN_TYPE}`);
+    }
+
+    // 修改 created_at 从 datetime(6) 改为 datetime(去掉微秒精度)
+    logger.info('[Fix] Modifying created_at precision...');
+    await AppDataSource.query(`
+      ALTER TABLE user_day_statistics 
+      MODIFY COLUMN created_at DATETIME NULL
+    `);
+    
+    await AppDataSource.query(`
+      ALTER TABLE user_day_statistics 
+      MODIFY COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+    `);
+    logger.info('[Fix] created_at precision fixed');
+
+    // 修改 updated_at 从 datetime(6) 改为 datetime(去掉微秒精度)
+    logger.info('[Fix] Modifying updated_at precision...');
+    await AppDataSource.query(`
+      ALTER TABLE user_day_statistics 
+      MODIFY COLUMN updated_at DATETIME NULL
+    `);
+    
+    await AppDataSource.query(`
+      ALTER TABLE user_day_statistics 
+      MODIFY COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+    `);
+    logger.info('[Fix] updated_at precision fixed');
+
+    // 更新历史数据:确保时间格式正确(去掉微秒部分)
+    logger.info('[Fix] Updating historical data to remove microseconds...');
+    await AppDataSource.query(`
+      UPDATE user_day_statistics 
+      SET created_at = DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s')
+      WHERE created_at IS NOT NULL
+    `);
+    
+    await AppDataSource.query(`
+      UPDATE user_day_statistics 
+      SET updated_at = DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s')
+      WHERE updated_at IS NOT NULL
+    `);
+    logger.info('[Fix] Historical data updated');
+
+    // 验证结果
+    logger.info('[Fix] Verifying results...');
+    const newStructure = await AppDataSource.query(`
+      SELECT 
+        COLUMN_NAME,
+        COLUMN_TYPE,
+        DATA_TYPE
+      FROM INFORMATION_SCHEMA.COLUMNS
+      WHERE TABLE_SCHEMA = DATABASE()
+        AND TABLE_NAME = 'user_day_statistics'
+        AND COLUMN_NAME IN ('created_at', 'updated_at')
+      ORDER BY COLUMN_NAME
+    `);
+
+    logger.info('[Fix] New column types:');
+    for (const col of newStructure) {
+      logger.info(`[Fix]   ${col.COLUMN_NAME}: ${col.COLUMN_TYPE} (${col.DATA_TYPE})`);
+    }
+
+    // 查看样本数据
+    const sampleData = await AppDataSource.query(`
+      SELECT 
+        id,
+        DATE_FORMAT(created_at, '%Y-%m-%d %H:%i:%s') AS created_at_formatted,
+        DATE_FORMAT(updated_at, '%Y-%m-%d %H:%i:%s') AS updated_at_formatted,
+        created_at,
+        updated_at
+      FROM user_day_statistics
+      ORDER BY id DESC
+      LIMIT 5
+    `);
+
+    logger.info('[Fix] Sample data after fix:');
+    for (const row of sampleData) {
+      logger.info(`[Fix]   ID ${row.id}:`);
+      logger.info(`[Fix]     created_at: ${row.created_at} (formatted: ${row.created_at_formatted})`);
+      logger.info(`[Fix]     updated_at: ${row.updated_at} (formatted: ${row.updated_at_formatted})`);
+    }
+
+    logger.info('[Fix] Fix completed successfully!');
+    process.exit(0);
+  } catch (error) {
+    logger.error('[Fix] Fix failed:', error);
+    process.exit(1);
+  } finally {
+    if (AppDataSource.isInitialized) {
+      await AppDataSource.destroy();
+    }
+  }
+}
+
+void fixDatetimePrecision();

+ 120 - 0
server/src/scripts/run-migration-fix-timestamp.ts

@@ -0,0 +1,120 @@
+import { AppDataSource } from '../models/index.js';
+import { logger } from '../utils/logger.js';
+import fs from 'node:fs/promises';
+import path from 'node:path';
+import { fileURLToPath } from 'url';
+
+const __filename = fileURLToPath(import.meta.url);
+const __dirname = path.dirname(__filename);
+
+async function runMigration() {
+  try {
+    // 初始化数据库连接
+    await AppDataSource.initialize();
+    logger.info('[Migration] Database connected');
+
+    // 设置时区
+    await AppDataSource.query("SET time_zone = '+08:00'");
+    logger.info('[Migration] Timezone set to +08:00');
+
+    // 检查当前表结构
+    const currentStructure = await AppDataSource.query(`
+      SELECT 
+        COLUMN_NAME,
+        DATA_TYPE
+      FROM INFORMATION_SCHEMA.COLUMNS
+      WHERE TABLE_SCHEMA = DATABASE()
+        AND TABLE_NAME = 'user_day_statistics'
+        AND COLUMN_NAME IN ('created_at', 'updated_at')
+    `);
+
+    const needsMigration = currentStructure.some((col: any) => col.DATA_TYPE === 'timestamp');
+    
+    if (!needsMigration) {
+      logger.info('[Migration] Table already uses DATETIME type, skipping migration');
+    } else {
+      logger.info('[Migration] Converting TIMESTAMP to DATETIME...');
+
+      // 执行迁移:修改 created_at
+      logger.info('[Migration] Modifying created_at column...');
+      await AppDataSource.query(`
+        ALTER TABLE user_day_statistics 
+        MODIFY COLUMN created_at DATETIME NULL
+      `);
+      
+      await AppDataSource.query(`
+        ALTER TABLE user_day_statistics 
+        MODIFY COLUMN created_at DATETIME DEFAULT CURRENT_TIMESTAMP
+      `);
+      logger.info('[Migration] created_at converted to DATETIME');
+
+      // 执行迁移:修改 updated_at
+      logger.info('[Migration] Modifying updated_at column...');
+      await AppDataSource.query(`
+        ALTER TABLE user_day_statistics 
+        MODIFY COLUMN updated_at DATETIME NULL
+      `);
+      
+      await AppDataSource.query(`
+        ALTER TABLE user_day_statistics 
+        MODIFY COLUMN updated_at DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
+      `);
+      logger.info('[Migration] updated_at converted to DATETIME');
+    }
+
+    // 验证结果:查询表结构
+    logger.info('[Migration] Verifying table structure...');
+    const tableInfo = await AppDataSource.query(`
+      SELECT 
+        COLUMN_NAME,
+        DATA_TYPE,
+        COLUMN_DEFAULT,
+        IS_NULLABLE
+      FROM INFORMATION_SCHEMA.COLUMNS
+      WHERE TABLE_SCHEMA = DATABASE()
+        AND TABLE_NAME = 'user_day_statistics'
+        AND COLUMN_NAME IN ('created_at', 'updated_at')
+      ORDER BY COLUMN_NAME
+    `);
+
+    logger.info('[Migration] Table structure:');
+    for (const col of tableInfo) {
+      logger.info(
+        `[Migration]   ${col.COLUMN_NAME}: ${col.DATA_TYPE} (nullable: ${col.IS_NULLABLE}, default: ${col.COLUMN_DEFAULT || 'NULL'})`
+      );
+    }
+
+    // 验证结果:查询几条数据
+    logger.info('[Migration] Verifying data format...');
+    const sampleData = await AppDataSource.query(`
+      SELECT 
+        id,
+        account_id,
+        record_date,
+        created_at,
+        updated_at
+      FROM user_day_statistics
+      ORDER BY id DESC
+      LIMIT 5
+    `);
+
+    logger.info('[Migration] Sample data:');
+    for (const row of sampleData) {
+      logger.info(
+        `[Migration]   ID ${row.id}: created_at=${row.created_at}, updated_at=${row.updated_at}`
+      );
+    }
+
+    logger.info('[Migration] Migration completed successfully!');
+    process.exit(0);
+  } catch (error) {
+    logger.error('[Migration] Migration failed:', error);
+    process.exit(1);
+  } finally {
+    if (AppDataSource.isInitialized) {
+      await AppDataSource.destroy();
+    }
+  }
+}
+
+void runMigration();