Files
yjearth/src/main/java/com/yj/earth/datasource/DatabaseManager.java

409 lines
16 KiB
Java
Raw Normal View History

2025-09-08 17:01:50 +08:00
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);
2025-09-22 17:13:22 +08:00
classes.add(ModelLibrary.class);
2025-09-23 16:45:42 +08:00
classes.add(MilitaryLibrary.class);
2025-09-26 13:46:24 +08:00
classes.add(IconLibrary.class);
2025-09-22 17:13:22 +08:00
classes.add(BusinessConfig.class);
2025-09-26 14:38:45 +08:00
classes.add(WebSource.class);
2025-10-09 11:03:15 +08:00
classes.add(PbfInfo.class);
2025-10-22 11:44:18 +08:00
classes.add(Device.class);
2025-10-27 17:18:33 +08:00
classes.add(PoiInfo.class);
2025-09-08 17:01:50 +08:00
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";
}
}
2025-09-22 17:13:22 +08:00
public static Path getRecommendedCacheDirectory() {
2025-09-08 17:01:50 +08:00
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";
}
}