Forráskód Böngészése

fix: 统一时区设置并修复数据库时间列定义

- 将所有实体类的 CreateDateColumn/UpdateDateColumn 替换为带时区默认值的 Column
- 在 docker-compose 中为 MySQL 和 Node.js 服务设置 Asia/Shanghai 时区
- 在 TypeORM 数据源配置中设置 timezone 为 '+08:00'
- 添加数据库连接时区设置钩子,确保会话级别时区一致
Ethanfly 20 órája
szülő
commit
dca8ece844

+ 3 - 0
server/docker-compose.yml

@@ -9,6 +9,7 @@ services:
       - "3000:3000"
     environment:
       - NODE_ENV=production
+      - TZ=Asia/Shanghai
       - PORT=3000
       - DB_HOST=mysql
       - DB_PORT=3306
@@ -32,7 +33,9 @@ services:
     image: mysql:8.0
     container_name: media-manager-mysql
     restart: unless-stopped
+    command: --default-time-zone=+08:00
     environment:
+      - TZ=Asia/Shanghai
       - MYSQL_ROOT_PASSWORD=${MYSQL_ROOT_PASSWORD:-rootpassword}
       - MYSQL_DATABASE=media_manager
       - MYSQL_USER=media_manager

+ 1 - 2
server/src/models/entities/AccountGroup.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
   ManyToOne,
   OneToMany,
   JoinColumn,
@@ -24,7 +23,7 @@ export class AccountGroup {
   @Column({ type: 'varchar', length: 255, nullable: true })
   description!: string | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
   // 关联

+ 1 - 2
server/src/models/entities/AnalyticsData.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
   ManyToOne,
   JoinColumn,
   Unique,
@@ -46,7 +45,7 @@ export class AnalyticsData {
   @Column({ type: 'decimal', precision: 10, scale: 2, default: 0 })
   income!: number;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
   // 关联

+ 1 - 2
server/src/models/entities/Comment.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
   ManyToOne,
   JoinColumn,
   Index,
@@ -72,7 +71,7 @@ export class Comment {
   @Column({ type: 'timestamp', nullable: true, name: 'comment_time' })
   commentTime!: Date | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
   // 关联

+ 1 - 2
server/src/models/entities/OperationLog.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
   ManyToOne,
   JoinColumn,
 } from 'typeorm';
@@ -34,7 +33,7 @@ export class OperationLog {
   @Column({ type: 'varchar', length: 255, nullable: true, name: 'user_agent' })
   userAgent!: string | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
   // 关联

+ 7 - 4
server/src/models/entities/PlatformAccount.ts

@@ -2,8 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
-  UpdateDateColumn,
   ManyToOne,
   OneToMany,
   JoinColumn,
@@ -65,10 +63,15 @@ export class PlatformAccount {
   @Column({ type: 'int', nullable: true, name: 'group_id' })
   groupId!: number | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 
   // 关联

+ 7 - 4
server/src/models/entities/PublishTask.ts

@@ -2,8 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
-  UpdateDateColumn,
   ManyToOne,
   OneToMany,
   JoinColumn,
@@ -57,10 +55,15 @@ export class PublishTask {
   @Column({ type: 'timestamp', nullable: true, name: 'published_at' })
   publishedAt!: Date | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 
   // 关联

+ 6 - 2
server/src/models/entities/SystemConfig.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  UpdateDateColumn,
 } from 'typeorm';
 
 @Entity('system_config')
@@ -19,6 +18,11 @@ export class SystemConfig {
   @Column({ type: 'varchar', length: 255, nullable: true })
   description!: string | null;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 }

+ 7 - 4
server/src/models/entities/User.ts

@@ -2,8 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
-  UpdateDateColumn,
   OneToMany,
 } from 'typeorm';
 import type { UserRole, UserStatus } from '@media-manager/shared';
@@ -47,10 +45,15 @@ export class User {
   @Column({ type: 'varchar', length: 50, nullable: true, name: 'last_login_ip' })
   lastLoginIp!: string | null;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 
   // 关联

+ 8 - 3
server/src/models/entities/UserDayStatistics.ts

@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
 
 @Entity('user_day_statistics')
 @Index(['accountId', 'recordDate'], { unique: true })
@@ -48,9 +48,14 @@ export class UserDayStatistics {
   @Column({ name: 'completion_rate', type: 'varchar', length: 50, default: '0', comment: '视频完播率' })
   completionRate!: string;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 }

+ 1 - 2
server/src/models/entities/UserToken.ts

@@ -2,7 +2,6 @@ import {
   Entity,
   PrimaryGeneratedColumn,
   Column,
-  CreateDateColumn,
   ManyToOne,
   JoinColumn,
 } from 'typeorm';
@@ -28,7 +27,7 @@ export class UserToken {
   @Column({ type: 'timestamp', name: 'expires_at' })
   expiresAt!: Date;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
   // 关联

+ 8 - 3
server/src/models/entities/Work.ts

@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, ManyToOne, JoinColumn, Index } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, ManyToOne, JoinColumn, Index } from 'typeorm';
 import { PlatformAccount } from './PlatformAccount.js';
 
 @Entity('works')
@@ -56,10 +56,15 @@ export class Work {
   @Column({ name: 'collect_count', type: 'int', default: 0 })
   collectCount!: number;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 
   @ManyToOne(() => PlatformAccount, { onDelete: 'CASCADE' })

+ 8 - 3
server/src/models/entities/WorkDayStatistics.ts

@@ -1,4 +1,4 @@
-import { Entity, PrimaryGeneratedColumn, Column, CreateDateColumn, UpdateDateColumn, Index } from 'typeorm';
+import { Entity, PrimaryGeneratedColumn, Column, Index } from 'typeorm';
 
 @Entity('work_day_statistics')
 @Index(['workId', 'recordDate'], { unique: true })
@@ -27,9 +27,14 @@ export class WorkDayStatistics {
   @Column({ name: 'collect_count', type: 'int', default: 0, comment: '收藏数' })
   collectCount!: number;
 
-  @CreateDateColumn({ name: 'created_at' })
+  @Column({ type: 'timestamp', name: 'created_at', default: () => 'CURRENT_TIMESTAMP' })
   createdAt!: Date;
 
-  @UpdateDateColumn({ name: 'updated_at' })
+  @Column({
+    type: 'timestamp',
+    name: 'updated_at',
+    default: () => 'CURRENT_TIMESTAMP',
+    onUpdate: 'CURRENT_TIMESTAMP',
+  })
   updatedAt!: Date;
 }

+ 26 - 0
server/src/models/index.ts

@@ -14,6 +14,8 @@ import { Work } from './entities/Work.js';
 import { WorkDayStatistics } from './entities/WorkDayStatistics.js';
 import { UserDayStatistics } from './entities/UserDayStatistics.js';
 
+const MYSQL_CHINA_TIMEZONE = '+08:00';
+
 export const AppDataSource = new DataSource({
   type: 'mysql',
   host: config.database.host,
@@ -24,6 +26,7 @@ export const AppDataSource = new DataSource({
   // 永远关闭 synchronize:避免自动改表/删列(统一走 migrations)
   synchronize: false,
   logging: config.env === 'development',
+  timezone: MYSQL_CHINA_TIMEZONE,
   entities: [
     User,
     UserToken,
@@ -42,9 +45,32 @@ export const AppDataSource = new DataSource({
   charset: 'utf8mb4',
 });
 
+let timezoneHookInstalled = false;
+
+async function ensureMysqlSessionTimezone(): Promise<void> {
+  try {
+    await AppDataSource.query(`SET time_zone = '${MYSQL_CHINA_TIMEZONE}'`);
+  } catch {
+    return;
+  }
+
+  if (timezoneHookInstalled) return;
+
+  const driver: any = AppDataSource.driver as any;
+  const pool: any = driver?.pool;
+
+  if (pool && typeof pool.on === 'function') {
+    pool.on('connection', (connection: any) => {
+      connection.query(`SET time_zone = '${MYSQL_CHINA_TIMEZONE}'`);
+    });
+    timezoneHookInstalled = true;
+  }
+}
+
 export async function initDatabase(): Promise<DataSource> {
   if (!AppDataSource.isInitialized) {
     await AppDataSource.initialize();
+    await ensureMysqlSessionTimezone();
   }
   return AppDataSource;
 }