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> 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> 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 yamlData = yaml.load(yamlInput); Map springMap = (Map) yamlData.get("spring"); Map datasourceMap = (Map) springMap.get("datasource"); Map mysqlMap = (Map) 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 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 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 yamlData = yaml.load(yamlInput); if (yamlData.containsKey("spring")) { Object springObj = yamlData.get("spring"); if (springObj instanceof Map) { Map springMap = (Map) springObj; if (springMap.containsKey("datasource")) { Object datasourceObj = springMap.get("datasource"); if (datasourceObj instanceof Map) { Map datasourceMap = (Map) datasourceObj; if (datasourceMap.containsKey("active")) { Object activeObj = datasourceMap.get("active"); if (activeObj != null) { return activeObj.toString().trim(); } } } } } } } catch (Exception e) { log.error("读取配置文件出错"); } return "sqlite"; } }