Files
yjearth/src/main/java/com/yj/earth/datasource/DatabaseManager.java
2025-10-27 17:18:33 +08:00

409 lines
16 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.yj.earth.datasource;
import com.yj.earth.design.*;
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.springframework.core.io.ClassPathResource;
import org.yaml.snakeyaml.Yaml;
import java.io.IOException;
import java.io.InputStream;
import java.lang.reflect.Field;
import java.lang.reflect.Modifier;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.sql.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
@Data
@Slf4j
public class DatabaseManager {
public enum DatabaseType {
SQLITE, MYSQL
}
private static final String EXCLUDE_SERIAL_FIELD = "serialVersionUID";
private static final List<Class<?>> ENTITY_CLASSES;
private static final String FOLDER_NAME = "yjearth";
private static final String DB_FILE_NAME = "app.db";
private static String sqliteDbFilePath;
private static boolean isSqliteInitialized = false;
private static String mysqlUrl;
private static String mysqlUsername;
private static String mysqlPassword;
private static boolean isMysqlInitialized = false;
private static final String SQLITE_DRIVER = "org.sqlite.JDBC";
private static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MYSQL_TABLE_ENGINE = "ENGINE=InnoDB DEFAULT CHARSET=utf8mb4";
static {
List<Class<?>> classes = new ArrayList<>();
classes.add(User.class);
classes.add(Role.class);
classes.add(Source.class);
classes.add(RoleSource.class);
classes.add(FileInfo.class);
classes.add(ModelLibrary.class);
classes.add(MilitaryLibrary.class);
classes.add(IconLibrary.class);
classes.add(BusinessConfig.class);
classes.add(WebSource.class);
classes.add(PbfInfo.class);
classes.add(Device.class);
classes.add(PoiInfo.class);
ENTITY_CLASSES = Collections.unmodifiableList(classes);
}
public static void initDatabase(DatabaseType dbType) {
if (dbType == null) {
log.error("数据库类型不能为空");
return;
}
if (isInitialized(dbType)) {
log.info("{}数据库已初始化、无需重复执行", dbType.name());
return;
}
try {
loadConfig(dbType);
if (!validateConfig(dbType)) {
log.error("{}数据库配置无效、初始化失败", dbType.name());
return;
}
loadDriver(dbType);
preProcess(dbType);
createTablesForEntities(dbType);
markInitialized(dbType, true);
log.info("{}数据库初始化成功({}", dbType.name(), getInitSuccessMsg(dbType));
} catch (ClassNotFoundException e) {
log.error("{}驱动类未找到、请检查依赖: {}", dbType.name(), e.getMessage(), e);
} catch (SQLException e) {
log.error("{}数据库操作失败: {}", dbType.name(), e.getMessage(), e);
} catch (Exception e) {
log.error("{}数据库初始化未知异常: {}", dbType.name(), e.getMessage(), e);
}
}
public static String getUnderlineName(String camelCaseName) {
if (camelCaseName == null || camelCaseName.isEmpty()) {
throw new IllegalArgumentException("命名转换的入参名称不能为空");
}
StringBuilder underlineName = new StringBuilder();
underlineName.append(Character.toLowerCase(camelCaseName.charAt(0)));
for (int i = 1; i < camelCaseName.length(); i++) {
char currentChar = camelCaseName.charAt(i);
if (Character.isUpperCase(currentChar)) {
underlineName.append("_").append(Character.toLowerCase(currentChar));
} else {
underlineName.append(currentChar);
}
}
return underlineName.toString();
}
public static String getSqliteDbFilePath() {
return sqliteDbFilePath;
}
private static void loadConfig(DatabaseType dbType) throws IOException {
if (dbType != DatabaseType.MYSQL) return;
try (InputStream yamlInput = new ClassPathResource("application.yml").getInputStream()) {
Yaml yaml = new Yaml();
Map<String, Object> yamlData = yaml.load(yamlInput);
Map<String, Object> springMap = (Map<String, Object>) yamlData.get("spring");
Map<String, Object> datasourceMap = (Map<String, Object>) springMap.get("datasource");
Map<String, Object> mysqlMap = (Map<String, Object>) datasourceMap.get("mysql");
mysqlUrl = getConfigValue(mysqlMap, "url");
mysqlUsername = getConfigValue(mysqlMap, "username");
mysqlPassword = getConfigValue(mysqlMap, "password");
}
}
private static boolean validateConfig(DatabaseType dbType) {
if (dbType == DatabaseType.SQLITE) return true;
if (mysqlUrl == null || mysqlUrl.isEmpty()) {
log.error("MySQL配置缺失: spring.datasource.mysql.url");
return false;
}
if (mysqlUsername == null || mysqlUsername.isEmpty()) {
log.error("MySQL配置缺失: spring.datasource.mysql.username");
return false;
}
if (!mysqlUrl.startsWith("jdbc:mysql://")) {
log.error("MySQL URL格式错误、需以[jdbc:mysql://]开头、当前: {}", mysqlUrl);
return false;
}
return true;
}
private static void loadDriver(DatabaseType dbType) throws ClassNotFoundException {
if (dbType == DatabaseType.SQLITE) {
Class.forName(SQLITE_DRIVER);
log.info("SQLite驱动加载成功");
} else {
Class.forName(MYSQL_DRIVER);
log.info("MySQL驱动加载成功");
}
}
private static void preProcess(DatabaseType dbType) throws IOException {
if (dbType != DatabaseType.SQLITE) return;
Path systemCacheDir = getRecommendedCacheDirectory();
if (systemCacheDir == null) {
throw new IOException("无法获取有效的系统缓存目录、无法创建SQLite文件");
}
Path appDir = systemCacheDir.resolve(FOLDER_NAME);
Path dbFile = appDir.resolve(DB_FILE_NAME);
sqliteDbFilePath = dbFile.toAbsolutePath().toString();
if (!Files.exists(appDir)) {
Files.createDirectories(appDir);
log.info("创建SQLite应用目录: {}", appDir);
}
if (!Files.isWritable(appDir)) {
throw new IOException("无权限写入SQLite目录: " + appDir);
}
if (!Files.exists(dbFile)) {
Files.createFile(dbFile);
log.info("创建SQLite新文件: {}", sqliteDbFilePath);
} else {
log.info("SQLite文件已存在: {}", sqliteDbFilePath);
}
}
private static void createTablesForEntities(DatabaseType dbType) throws SQLException {
if (ENTITY_CLASSES.isEmpty()) {
log.warn("未配置需要创建表的实体类、跳过批量建表");
return;
}
try (Connection connection = getConnection(dbType)) {
for (Class<?> entityClass : ENTITY_CLASSES) {
createTableIfNotExists(connection, dbType, entityClass);
}
}
}
private static void createTableIfNotExists(Connection connection, DatabaseType dbType, Class<?> entityClass) throws SQLException {
String tableName = getUnderlineName(entityClass.getSimpleName());
if (isTableExists(connection, dbType, tableName)) {
log.info("{}表[{}]已存在、跳过创建", dbType.name(), tableName);
return;
}
String createSql = generateCreateTableSql(dbType, entityClass, tableName);
try (Statement statement = connection.createStatement()) {
statement.execute(createSql);
log.info("{}表[{}]创建成功、执行SQL: {}", dbType.name(), tableName, createSql);
}
}
private static Connection getConnection(DatabaseType dbType) throws SQLException {
if (dbType == DatabaseType.SQLITE) {
return DriverManager.getConnection("jdbc:sqlite:" + sqliteDbFilePath);
} else {
return DriverManager.getConnection(mysqlUrl, mysqlUsername, mysqlPassword);
}
}
private static boolean isTableExists(Connection connection, DatabaseType dbType, String tableName) throws SQLException {
if (dbType == DatabaseType.SQLITE) {
try (ResultSet rs = connection.getMetaData().getTables(null, null, tableName, new String[]{"TABLE"})) {
return rs.next();
}
} else {
String dbName = extractDbNameFromMysqlUrl(mysqlUrl);
String checkSql = "SELECT 1 FROM information_schema.tables WHERE table_schema = ? AND table_name = ? LIMIT 1";
try (PreparedStatement pstmt = connection.prepareStatement(checkSql)) {
pstmt.setString(1, dbName);
pstmt.setString(2, tableName);
try (ResultSet rs = pstmt.executeQuery()) {
return rs.next();
}
}
}
}
private static String generateCreateTableSql(DatabaseType dbType, Class<?> entityClass, String tableName) {
StringBuilder sqlBuilder = new StringBuilder("CREATE TABLE IF NOT EXISTS ").append(tableName).append(" (");
Field[] fields = entityClass.getDeclaredFields();
List<String> columnDefinitions = new ArrayList<>();
for (Field field : fields) {
if (EXCLUDE_SERIAL_FIELD.equals(field.getName()) || Modifier.isStatic(field.getModifiers())) {
continue;
}
field.setAccessible(true);
String columnName = getUnderlineName(field.getName());
String dbTypeStr = mapJavaTypeToDbType(dbType, field.getType());
StringBuilder columnDef = new StringBuilder(columnName).append(" ").append(dbTypeStr);
if ("id".equals(field.getName())) {
columnDef.append(dbType == DatabaseType.SQLITE ? " PRIMARY KEY" : " PRIMARY KEY AUTO_INCREMENT");
}
columnDefinitions.add(columnDef.toString());
}
for (int i = 0; i < columnDefinitions.size(); i++) {
sqlBuilder.append(columnDefinitions.get(i));
if (i != columnDefinitions.size() - 1) {
sqlBuilder.append(", ");
}
}
if (dbType == DatabaseType.MYSQL) {
sqlBuilder.append(") ").append(MYSQL_TABLE_ENGINE);
} else {
sqlBuilder.append(")");
}
return sqlBuilder.toString();
}
private static String mapJavaTypeToDbType(DatabaseType dbType, Class<?> javaType) {
return dbType == DatabaseType.SQLITE ? mapJavaTypeToSqlite(javaType) : mapJavaTypeToMysql(javaType);
}
private static String mapJavaTypeToSqlite(Class<?> javaType) {
if (javaType == int.class || javaType == Integer.class || javaType == long.class || javaType == Long.class || javaType == short.class || javaType == Short.class) {
return "INTEGER";
} else if (javaType == float.class || javaType == Float.class || javaType == double.class || javaType == Double.class) {
return "REAL";
} else if (javaType == boolean.class || javaType == Boolean.class) {
return "INTEGER";
} else if (javaType == String.class) {
return "TEXT";
} else if (javaType == byte[].class) {
return "BLOB";
} else {
return "TEXT";
}
}
public static Path getRecommendedCacheDirectory() {
String os = System.getProperty("os.name").toLowerCase();
if (os.contains("win")) {
String appData = System.getenv("APPDATA");
if (appData != null && !appData.isEmpty()) {
Path path = Paths.get(appData);
if (Files.exists(path) && Files.isWritable(path)) {
return path;
}
}
} else if (os.contains("nix") || os.contains("nux")) {
String userHome = System.getProperty("user.home");
if (userHome != null && !userHome.isEmpty()) {
Path path = Paths.get(userHome).resolve(".cache");
try {
if (!Files.exists(path)) {
Files.createDirectories(path);
}
if (Files.isWritable(path)) {
return path;
}
} catch (IOException e) {
log.error("无法访问Linux .cache目录: {}", e.getMessage(), e);
}
}
}
return null;
}
private static String mapJavaTypeToMysql(Class<?> javaType) {
if (javaType == int.class || javaType == Integer.class) {
return "INT";
} else if (javaType == long.class || javaType == Long.class) {
return "BIGINT";
} else if (javaType == float.class || javaType == Float.class) {
return "FLOAT";
} else if (javaType == double.class || javaType == Double.class) {
return "DOUBLE";
} else if (javaType == boolean.class || javaType == Boolean.class) {
return "TINYINT(1)";
} else if (javaType == String.class) {
return "VARCHAR(500)";
} else if (javaType == java.util.Date.class || javaType == java.sql.Date.class) {
return "DATE";
} else if (javaType == java.sql.Timestamp.class || javaType == java.time.LocalDateTime.class) {
return "DATETIME";
} else if (javaType == byte[].class) {
return "BLOB";
} else {
return "VARCHAR(1000)";
}
}
private static String extractDbNameFromMysqlUrl(String url) {
if (url == null) return null;
String urlWithoutPrefix = url.replace("jdbc:mysql://", "");
String urlWithoutParams = urlWithoutPrefix.split("\\?")[0];
return urlWithoutParams.substring(urlWithoutParams.lastIndexOf("/") + 1);
}
private static String getConfigValue(Map<String, Object> configMap, String key) {
Object value = configMap.get(key);
return value == null ? null : value.toString().trim();
}
private static boolean isInitialized(DatabaseType dbType) {
return dbType == DatabaseType.SQLITE ? isSqliteInitialized : isMysqlInitialized;
}
private static void markInitialized(DatabaseType dbType, boolean status) {
if (dbType == DatabaseType.SQLITE) {
isSqliteInitialized = status;
} else {
isMysqlInitialized = status;
}
}
private static String getInitSuccessMsg(DatabaseType dbType) {
return dbType == DatabaseType.SQLITE ?
"文件路径: " + sqliteDbFilePath :
"数据库: " + extractDbNameFromMysqlUrl(mysqlUrl) + "、用户: " + mysqlUsername;
}
public static String getActiveDataSource() throws IOException {
// 读取配置文件
try (InputStream yamlInput = new ClassPathResource("application.yml").getInputStream()) {
Yaml yaml = new Yaml();
Map<String, Object> yamlData = yaml.load(yamlInput);
if (yamlData.containsKey("spring")) {
Object springObj = yamlData.get("spring");
if (springObj instanceof Map) {
Map<String, Object> springMap = (Map<String, Object>) springObj;
if (springMap.containsKey("datasource")) {
Object datasourceObj = springMap.get("datasource");
if (datasourceObj instanceof Map) {
Map<String, Object> datasourceMap = (Map<String, Object>) datasourceObj;
if (datasourceMap.containsKey("active")) {
Object activeObj = datasourceMap.get("active");
if (activeObj != null) {
return activeObj.toString().trim();
}
}
}
}
}
}
} catch (Exception e) {
log.error("读取配置文件出错");
}
return "sqlite";
}
}