409 lines
16 KiB
Java
409 lines
16 KiB
Java
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";
|
||
}
|
||
}
|