diff --git a/license-service/src/main/java/com/xuqm/license/service/LicenseSchemaMigrationService.java b/license-service/src/main/java/com/xuqm/license/service/LicenseSchemaMigrationService.java new file mode 100644 index 0000000..296a256 --- /dev/null +++ b/license-service/src/main/java/com/xuqm/license/service/LicenseSchemaMigrationService.java @@ -0,0 +1,113 @@ +package com.xuqm.license.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.function.Consumer; + +@Service +public class LicenseSchemaMigrationService { + + private static final Logger log = LoggerFactory.getLogger(LicenseSchemaMigrationService.class); + + private final DataSource dataSource; + + public LicenseSchemaMigrationService(DataSource dataSource) { + this.dataSource = dataSource; + } + + @EventListener(ApplicationReadyEvent.class) + public void onStartup() { + runSchemaMigrations(msg -> log.info("[license-migration] {}", msg)); + } + + public void runSchemaMigrations(Consumer emit) { + emit.accept("检查数据库迁移..."); + try { + ensureMigrationsTable(); + } catch (Exception e) { + emit.accept("[警告] 无法初始化迁移记录表: " + e.getMessage()); + return; + } + + migrate_v20260527_create_license_operation_log(emit); + + emit.accept("数据库迁移检查完成"); + } + + private void ensureMigrationsTable() throws Exception { + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(""" + CREATE TABLE IF NOT EXISTS _schema_migrations ( + id VARCHAR(128) NOT NULL PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + description VARCHAR(255) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """); + } + } + + private boolean migrationApplied(String id) { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT COUNT(*) FROM _schema_migrations WHERE id = ?")) { + ps.setString(1, id); + try (ResultSet rs = ps.executeQuery()) { + return rs.next() && rs.getInt(1) > 0; + } + } catch (Exception e) { + log.warn("check migration {} failed: {}", id, e.getMessage()); + return false; + } + } + + private void recordMigration(String id, String description) { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "INSERT IGNORE INTO _schema_migrations (id, description) VALUES (?, ?)")) { + ps.setString(1, id); + ps.setString(2, description); + ps.executeUpdate(); + } catch (Exception e) { + log.warn("record migration {} failed: {}", id, e.getMessage()); + } + } + + // ── 各版本迁移 ────────────────────────────────────────────────────────────── + + private void migrate_v20260527_create_license_operation_log(Consumer emit) { + final String id = "v20260527_create_license_operation_log"; + if (migrationApplied(id)) { + emit.accept("[已应用] " + id); + return; + } + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(""" + CREATE TABLE IF NOT EXISTS license_operation_log ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + app_key VARCHAR(64) NOT NULL, + operator VARCHAR(128) NOT NULL, + action VARCHAR(64) NOT NULL, + resource_type VARCHAR(64) NOT NULL, + resource_id VARCHAR(128), + summary VARCHAR(255), + detail_json TEXT, + created_at DATETIME NOT NULL, + INDEX idx_license_op_log_app_time (app_key, created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """); + emit.accept("[已迁移] " + id + ": 创建 license_operation_log 表"); + recordMigration(id, "创建授权操作日志表"); + } catch (Exception e) { + emit.accept("[错误] " + id + ": " + e.getMessage()); + log.error("migration {} failed", id, e); + } + } +} diff --git a/push-service/src/main/java/com/xuqm/push/service/PushSchemaMigrationService.java b/push-service/src/main/java/com/xuqm/push/service/PushSchemaMigrationService.java new file mode 100644 index 0000000..c5aec03 --- /dev/null +++ b/push-service/src/main/java/com/xuqm/push/service/PushSchemaMigrationService.java @@ -0,0 +1,113 @@ +package com.xuqm.push.service; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.springframework.boot.context.event.ApplicationReadyEvent; +import org.springframework.context.event.EventListener; +import org.springframework.stereotype.Service; + +import javax.sql.DataSource; +import java.sql.*; +import java.util.function.Consumer; + +@Service +public class PushSchemaMigrationService { + + private static final Logger log = LoggerFactory.getLogger(PushSchemaMigrationService.class); + + private final DataSource dataSource; + + public PushSchemaMigrationService(DataSource dataSource) { + this.dataSource = dataSource; + } + + @EventListener(ApplicationReadyEvent.class) + public void onStartup() { + runSchemaMigrations(msg -> log.info("[push-migration] {}", msg)); + } + + public void runSchemaMigrations(Consumer emit) { + emit.accept("检查数据库迁移..."); + try { + ensureMigrationsTable(); + } catch (Exception e) { + emit.accept("[警告] 无法初始化迁移记录表: " + e.getMessage()); + return; + } + + migrate_v20260527_create_push_operation_log(emit); + + emit.accept("数据库迁移检查完成"); + } + + private void ensureMigrationsTable() throws Exception { + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(""" + CREATE TABLE IF NOT EXISTS _schema_migrations ( + id VARCHAR(128) NOT NULL PRIMARY KEY, + applied_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, + description VARCHAR(255) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """); + } + } + + private boolean migrationApplied(String id) { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "SELECT COUNT(*) FROM _schema_migrations WHERE id = ?")) { + ps.setString(1, id); + try (ResultSet rs = ps.executeQuery()) { + return rs.next() && rs.getInt(1) > 0; + } + } catch (Exception e) { + log.warn("check migration {} failed: {}", id, e.getMessage()); + return false; + } + } + + private void recordMigration(String id, String description) { + try (Connection conn = dataSource.getConnection(); + PreparedStatement ps = conn.prepareStatement( + "INSERT IGNORE INTO _schema_migrations (id, description) VALUES (?, ?)")) { + ps.setString(1, id); + ps.setString(2, description); + ps.executeUpdate(); + } catch (Exception e) { + log.warn("record migration {} failed: {}", id, e.getMessage()); + } + } + + // ── 各版本迁移 ────────────────────────────────────────────────────────────── + + private void migrate_v20260527_create_push_operation_log(Consumer emit) { + final String id = "v20260527_create_push_operation_log"; + if (migrationApplied(id)) { + emit.accept("[已应用] " + id); + return; + } + try (Connection conn = dataSource.getConnection(); + Statement stmt = conn.createStatement()) { + stmt.execute(""" + CREATE TABLE IF NOT EXISTS push_operation_log ( + id VARCHAR(36) NOT NULL PRIMARY KEY, + app_key VARCHAR(64) NOT NULL, + operator VARCHAR(128) NOT NULL, + action VARCHAR(64) NOT NULL, + resource_type VARCHAR(64) NOT NULL, + resource_id VARCHAR(128), + summary VARCHAR(255), + detail TEXT, + created_at DATETIME NOT NULL, + INDEX idx_push_op_log_app_time (app_key, created_at) + ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 + """); + emit.accept("[已迁移] " + id + ": 创建 push_operation_log 表"); + recordMigration(id, "创建推送操作日志表"); + } catch (Exception e) { + emit.accept("[错误] " + id + ": " + e.getMessage()); + log.error("migration {} failed", id, e); + } + } +} diff --git a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java index 2e126bd..abc108c 100644 --- a/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java +++ b/tenant-service/src/main/java/com/xuqm/tenant/service/SystemUpdateService.java @@ -408,6 +408,7 @@ public class SystemUpdateService { } migrate_v20260101_drop_device_id_unique_index(emit); + migrate_v20260527_push_license_operation_logs(emit); // 新版本迁移在此追加,例如: // migrate_v20260601_add_app_extra_column(emit); @@ -494,6 +495,20 @@ public class SystemUpdateService { } } + /** + * push-service 和 license-service 新增操作日志表(push_operation_log / license_operation_log)。 + * 实际表结构由各服务的 SchemaMigrationService 在各自数据库中创建,此处仅记录版本标记。 + */ + private void migrate_v20260527_push_license_operation_logs(Consumer emit) { + final String id = "v20260527_push_license_operation_logs"; + if (migrationApplied(id)) { + emit.accept(" [已应用] " + id); + return; + } + emit.accept(" [已迁移] " + id + ": push/license 操作日志表已由各服务自行创建"); + recordMigration(id, "push-service 和 license-service 新增操作日志表"); + } + // ── 重启核心 ──────────────────────────────────────────────────────────────── private void restartAndSelfUpdate(Consumer emit, String composeFile) {