feat(database): 新增 push 和 license 操作日志表

- 在 tenant-service 的系统更新服务中添加新的数据库迁移方法
- 为 push-service 创建 PushSchemaMigrationService 并实现数据库迁移逻辑
- 为 license-service 创建 LicenseSchemaMigrationService 并实现数据库迁移逻辑
- 创建 push_operation_log 表用于记录推送服务操作日志
- 创建 license_operation_log 表用于记录授权服务操作日志
- 实现数据库迁移记录表 _schema_migrations 以跟踪迁移状态
- 添加迁移验证和错误处理机制确保迁移过程可靠性
这个提交包含在:
XuqmGroup 2026-05-27 18:01:31 +08:00
父节点 73dd4814f2
当前提交 db986808f2
共有 3 个文件被更改,包括 241 次插入0 次删除

查看文件

@ -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<String> 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<String> 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);
}
}
}

查看文件

@ -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<String> 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<String> 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);
}
}
}

查看文件

@ -408,6 +408,7 @@ public class SystemUpdateService {
} }
migrate_v20260101_drop_device_id_unique_index(emit); migrate_v20260101_drop_device_id_unique_index(emit);
migrate_v20260527_push_license_operation_logs(emit);
// 新版本迁移在此追加例如 // 新版本迁移在此追加例如
// migrate_v20260601_add_app_extra_column(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<String> 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<String> emit, String composeFile) { private void restartAndSelfUpdate(Consumer<String> emit, String composeFile) {