diff --git a/build.gradle b/build.gradle
index 97e7f44..b22e124 100644
--- a/build.gradle
+++ b/build.gradle
@@ -103,6 +103,11 @@ dependencies {
implementation 'me.friwi:jcefmaven:122.1.10'
implementation 'com.alphacephei:vosk:0.3.45'
implementation 'net.java.dev.jna:jna:5.13.0'
+ implementation 'com.h2database:h2:2.2.220'
+ implementation 'org.xerial:sqlite-jdbc:3.41.2.1'
+ implementation 'mysql:mysql-connector-java:8.0.33'
+ implementation 'org.postgresql:postgresql:42.6.0'
+
implementation 'net.java.dev.jna:jna-platform:5.13.0'
implementation 'org.apache.commons:commons-math3:3.6.1'
implementation 'com.google.guava:guava:31.1-jre'
diff --git a/javascript/DatabaseTool.html b/javascript/DatabaseTool.html
new file mode 100644
index 0000000..8720d00
--- /dev/null
+++ b/javascript/DatabaseTool.html
@@ -0,0 +1,793 @@
+
+
+
+
+
+ 数据库管理工具 - Axis Innovators Box
+
+
+
+
+
+
AI
+
+
Axis Innovators Box
+
数据库管理工具
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/main/java/com/axis/innovators/box/Log4j2OutputStream.java b/src/main/java/com/axis/innovators/box/Log4j2OutputStream.java
index 6f95af6..17476a4 100644
--- a/src/main/java/com/axis/innovators/box/Log4j2OutputStream.java
+++ b/src/main/java/com/axis/innovators/box/Log4j2OutputStream.java
@@ -6,33 +6,121 @@ import org.apache.logging.log4j.Logger;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.io.PrintStream;
+import java.nio.charset.StandardCharsets;
/**
- * 将输出传递给 Log4j2 的日志记录器
+ * 将输出传递给 Log4j2 的日志记录器,同时保持控制台输出
+ * 修复问题:控制台输出被Log4j2覆盖
* @author tzdwindows 7
*/
public class Log4j2OutputStream extends OutputStream {
private static final Logger logger = LogManager.getLogger();
+
+ // 恢复静态变量
public static final ByteArrayOutputStream systemOutContent = new ByteArrayOutputStream();
public static final ByteArrayOutputStream systemErrContent = new ByteArrayOutputStream();
+
+ private final ByteArrayOutputStream buffer = new ByteArrayOutputStream();
+ private final boolean isErrorStream;
+ private final PrintStream originalStream; // 保存原始的控制台流
+
+ public Log4j2OutputStream(boolean isErrorStream, PrintStream originalStream) {
+ this.isErrorStream = isErrorStream;
+ this.originalStream = originalStream;
+ }
+
@Override
public void write(int b) {
- systemOutContent.write(b);
- logger.info(String.valueOf((char) b));
+ // 写入原始控制台
+ originalStream.write(b);
+
+ buffer.write(b);
+ // 将内容同时写入对应的静态变量
+ if (isErrorStream) {
+ systemErrContent.write(b);
+ } else {
+ systemOutContent.write(b);
+ }
+
+ // 遇到换行符时刷新缓冲区到日志
+ if (b == '\n') {
+ flush();
+ }
}
@Override
public void write(byte[] b, int off, int len) {
- systemOutContent.write(b, off, len);
- String message = new String(b, off, len).trim();
- logger.info(message);
+ // 写入原始控制台
+ originalStream.write(b, off, len);
+
+ buffer.write(b, off, len);
+ // 将内容同时写入对应的静态变量
+ if (isErrorStream) {
+ systemErrContent.write(b, off, len);
+ } else {
+ systemOutContent.write(b, off, len);
+ }
+
+ // 检查是否包含换行符
+ for (int i = off; i < off + len; i++) {
+ if (b[i] == '\n') {
+ flush();
+ break;
+ }
+ }
+ }
+
+ @Override
+ public void flush() {
+ originalStream.flush();
+
+ String message = buffer.toString(StandardCharsets.UTF_8).trim();
+ if (!message.isEmpty()) {
+ if (isErrorStream) {
+ logger.error(message);
+ } else {
+ logger.info(message);
+ }
+ }
+ buffer.reset(); // 清空缓冲区
+ }
+
+ @Override
+ public void close() {
+ flush();
+ originalStream.close();
}
/**
- * 重定向 System.out 和 System.err 到 Log4j2
+ * 重定向 System.out 和 System.err 到 Log4j2,同时保持控制台输出
*/
public static void redirectSystemStreams() {
- System.setOut(new PrintStream(new Log4j2OutputStream(), true));
- System.setErr(new PrintStream(new Log4j2OutputStream(), true));
+ // 保存原始流
+ PrintStream originalOut = System.out;
+ PrintStream originalErr = System.err;
+
+ // System.out 使用 INFO 级别,同时输出到原始控制台
+ System.setOut(new PrintStream(new Log4j2OutputStream(false, originalOut), true, StandardCharsets.UTF_8));
+ // System.err 使用 ERROR 级别,同时输出到原始控制台
+ System.setErr(new PrintStream(new Log4j2OutputStream(true, originalErr), true, StandardCharsets.UTF_8));
+ }
+
+ /**
+ * 清空静态缓冲区内容
+ */
+ public static void clearBuffers() {
+ systemOutContent.reset();
+ systemErrContent.reset();
+ }
+
+ /**
+ * 获取输出内容
+ */
+ public static String getSystemOutContent() {
+ return systemOutContent.toString(StandardCharsets.UTF_8);
+ }
+
+ public static String getSystemErrContent() {
+ return systemErrContent.toString(StandardCharsets.UTF_8);
}
}
\ No newline at end of file
diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java
index 6ecd79e..5272059 100644
--- a/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java
+++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindow.java
@@ -11,9 +11,11 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams;
+import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.*;
+import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;
import javax.swing.*;
@@ -425,6 +427,24 @@ public class BrowserWindow extends JFrame {
}
});
+ client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
+ @Override
+ public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
+ if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(
+ BrowserWindow.this,
+ message_text,
+ "警告",
+ JOptionPane.INFORMATION_MESSAGE
+ );
+ });
+ callback.Continue(true, "");
+ return true;
+ }
+ return false;
+ }
+ });
// 3. 拦截所有新窗口(关键修复点!)
diff --git a/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java
index b68c87c..ee132d8 100644
--- a/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java
+++ b/src/main/java/com/axis/innovators/box/browser/BrowserWindowJDialog.java
@@ -11,9 +11,11 @@ import org.cef.browser.CefBrowser;
import org.cef.browser.CefFrame;
import org.cef.browser.CefMessageRouter;
import org.cef.callback.CefContextMenuParams;
+import org.cef.callback.CefJSDialogCallback;
import org.cef.callback.CefMenuModel;
import org.cef.callback.CefQueryCallback;
import org.cef.handler.*;
+import org.cef.misc.BoolRef;
import org.cef.network.CefRequest;
import org.json.JSONObject;
@@ -432,6 +434,26 @@ public class BrowserWindowJDialog extends JDialog {
}
});
+ // 添加 alert 弹窗监控处理
+ client.addJSDialogHandler(new CefJSDialogHandlerAdapter() {
+ @Override
+ public boolean onJSDialog(CefBrowser browser, String origin_url, CefJSDialogHandler.JSDialogType dialog_type, String message_text, String default_prompt_text, CefJSDialogCallback callback, BoolRef suppress_message) {
+ if (dialog_type == CefJSDialogHandler.JSDialogType.JSDIALOGTYPE_ALERT) {
+ SwingUtilities.invokeLater(() -> {
+ JOptionPane.showMessageDialog(
+ BrowserWindowJDialog.this,
+ message_text,
+ "警告",
+ JOptionPane.INFORMATION_MESSAGE
+ );
+ });
+ callback.Continue(true, "");
+ return true;
+ }
+ return false;
+ }
+ });
+
// 3. 拦截所有新窗口(关键修复点!)
diff --git a/src/main/java/com/axis/innovators/box/browser/MainApplication.java b/src/main/java/com/axis/innovators/box/browser/MainApplication.java
index 95ec6aa..3198055 100644
--- a/src/main/java/com/axis/innovators/box/browser/MainApplication.java
+++ b/src/main/java/com/axis/innovators/box/browser/MainApplication.java
@@ -1,6 +1,7 @@
package com.axis.innovators.box.browser;
import com.axis.innovators.box.browser.util.CodeExecutor;
+import com.axis.innovators.box.browser.util.DatabaseConnectionManager;
import com.axis.innovators.box.tools.FolderCreator;
import com.google.gson.JsonObject;
import com.google.gson.JsonParser;
@@ -11,6 +12,7 @@ import org.cef.callback.CefQueryCallback;
import org.cef.handler.CefMessageRouterHandlerAdapter;
import org.graalvm.polyglot.Context;
import org.graalvm.polyglot.Value;
+import org.json.JSONArray;
import org.json.JSONObject;
import org.tzd.lm.LM;
@@ -20,6 +22,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
+import java.sql.*;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutorService;
@@ -351,4 +354,1023 @@ public class MainApplication {
.withDefaultOperations()
.build();
}
+
+ public static void popupDataBaseWindow() {
+ // 预加载常用 JDBC 驱动(警告级别,不阻塞 UI)
+ try {
+ try { Class.forName("org.h2.Driver"); } catch (ClassNotFoundException ignored) { System.err.println("WARN: org.h2.Driver 未找到"); }
+ try { Class.forName("org.sqlite.JDBC"); } catch (ClassNotFoundException ignored) { System.err.println("WARN: org.sqlite.JDBC 未找到"); }
+ try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException ignored) { System.err.println("WARN: org.postgresql.Driver 未找到"); }
+ try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException ignored) { System.err.println("WARN: com.mysql.cj.jdbc.Driver 未找到"); }
+ try { Class.forName("oracle.jdbc.OracleDriver"); } catch (ClassNotFoundException ignored) { System.err.println("WARN: oracle.jdbc.OracleDriver 未找到"); }
+ } catch (Throwable t) {
+ System.err.println("预加载 JDBC 驱动时发生异常: " + t.getMessage());
+ }
+
+ AtomicReference window = new AtomicReference<>();
+ SwingUtilities.invokeLater(() -> {
+ WindowRegistry.getInstance().createNewWindow("main", builder ->
+ window.set(builder.title("Axis Innovators Box 数据库管理工具")
+ .icon(new ImageIcon(Objects.requireNonNull(MainApplication.class.getClassLoader().getResource("icons/logo.png"))).getImage())
+ .size(1487, 836)
+ .htmlPath(FolderCreator.getJavaScriptFolder() + "\\" + "DatabaseTool.html")
+ .operationHandler(createOperationHandler())
+ .build())
+ );
+
+ if (window.get() == null) {
+ System.err.println("popupDataBaseWindow: window 创建失败,window.get() == null");
+ return;
+ }
+
+ CefMessageRouter msgRouter = window.get().getMsgRouter();
+ if (msgRouter != null) {
+ msgRouter.addHandler(new CefMessageRouterHandlerAdapter() {
+ @Override
+ public boolean onQuery(CefBrowser browser, CefFrame frame, long queryId,
+ String request, boolean persistent, CefQueryCallback callback) {
+ try {
+ JSONObject requestJson = new JSONObject(request);
+ String type = requestJson.optString("type", "");
+
+ // 补默认端口与标准化 driver 名称
+ String drv = requestJson.optString("driver", "").toLowerCase();
+ if (!drv.isEmpty()) {
+ String port = requestJson.optString("port", "").trim();
+ if (port.isEmpty()) {
+ switch (drv) {
+ case "mysql":
+ requestJson.put("port", "3306");
+ break;
+ case "postgresql":
+ case "postgres":
+ requestJson.put("port", "5432");
+ break;
+ case "oracle":
+ requestJson.put("port", "1521");
+ break;
+ }
+ }
+ if ("postgres".equals(drv)) requestJson.put("driver", "postgresql");
+ }
+
+ // 安全标识符校验 pattern(表名/列名等仅允许常见字符)
+ final java.util.regex.Pattern SAFE_IDENT = java.util.regex.Pattern.compile("^[A-Za-z0-9_\\.\\$]+$");
+
+ switch (type) {
+ // 已有功能:继续使用现有处理函数(假定这些方法在类中定义)
+ case "connectDatabase":
+ handleDatabaseConnect(requestJson, callback);
+ break;
+ case "createLocalDatabase":
+ handleCreateLocalDatabase(requestJson, callback);
+ break;
+ case "disconnectDatabase":
+ handleDisconnectDatabase(requestJson, callback);
+ break;
+ case "executeQuery":
+ handleExecuteQuery(requestJson, callback);
+ break;
+ case "getTables":
+ handleGetTables(requestJson, callback);
+ break;
+ case "getTableData":
+ handleGetTableData(requestJson, callback);
+ break;
+ case "getTableStructure":
+ handleGetTableStructure(requestJson, callback);
+ break;
+ case "updateTheme":
+ handleUpdateTheme(requestJson, callback);
+ break;
+ case "getFonts":
+ handleGetFonts(requestJson, callback);
+ break;
+
+ // 新增后端支持:analyzeQuery(EXPLAIN / EXPLAIN ANALYZE)
+ case "analyzeQuery": {
+ String connectionId = requestJson.optString("connectionId", "");
+ String query = requestJson.optString("query", "").trim();
+ if (connectionId.isEmpty() || query.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","connectionId 或 query 为空").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+ // 依据驱动选择 EXPLAIN 语法
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ String drvName = info == null ? "" : (info.driver == null ? "" : info.driver.toLowerCase());
+ String explainSql = "EXPLAIN " + query;
+ if ("postgresql".equals(drvName)) {
+ explainSql = "EXPLAIN ANALYZE " + query;
+ }
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(explainSql)) {
+ org.json.JSONArray out = new org.json.JSONArray();
+ while (rs.next()) {
+ // EXPLAIN 输出常为单列文本
+ out.put(rs.getString(1));
+ }
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("explain", out);
+ callback.success(resp.toString());
+ } catch (SQLException ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","分析失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ // exportData -> 导出为 CSV 或 JSON,写入用户目录下 .axis_innovators_box/exports/
+ case "exportData": {
+ String connectionId = requestJson.optString("connectionId", "");
+ String table = requestJson.optString("table", "");
+ String format = requestJson.optString("format", "csv").toLowerCase();
+ if (connectionId.isEmpty() || table.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","connectionId 或 table 为空").toString());
+ break;
+ }
+ if (!SAFE_IDENT.matcher(table).matches()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","非法表名").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+
+ java.nio.file.Path exportDir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "exports");
+ try { java.nio.file.Files.createDirectories(exportDir); } catch (Exception e) { /* ignore */ }
+
+ String filenameBase = table + "_" + System.currentTimeMillis();
+ java.nio.file.Path outPath = exportDir.resolve(filenameBase + (format.equals("json") ? ".json" : ".csv"));
+
+ String query = "SELECT * FROM " + table;
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery(query)) {
+
+ ResultSetMetaData md = rs.getMetaData();
+ int cols = md.getColumnCount();
+
+ if ("json".equals(format)) {
+ org.json.JSONArray arr = new org.json.JSONArray();
+ while (rs.next()) {
+ org.json.JSONObject obj = new org.json.JSONObject();
+ for (int i = 1; i <= cols; i++) {
+ Object val = rs.getObject(i);
+ obj.put(md.getColumnLabel(i), val == null ? org.json.JSONObject.NULL : val);
+ }
+ arr.put(obj);
+ }
+ java.nio.file.Files.write(outPath, arr.toString(2).getBytes(java.nio.charset.StandardCharsets.UTF_8));
+ } else {
+ // CSV
+ try (java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(outPath, java.nio.charset.StandardCharsets.UTF_8)) {
+ // header
+ for (int i = 1; i <= cols; i++) {
+ if (i > 1) writer.write(",");
+ writer.write("\"" + md.getColumnLabel(i).replace("\"","\"\"") + "\"");
+ }
+ writer.write("\n");
+ while (rs.next()) {
+ for (int i = 1; i <= cols; i++) {
+ if (i > 1) writer.write(",");
+ Object val = rs.getObject(i);
+ String cell = val == null ? "" : String.valueOf(val);
+ writer.write("\"" + cell.replace("\"","\"\"") + "\"");
+ }
+ writer.write("\n");
+ }
+ }
+ }
+
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("path", outPath.toAbsolutePath().toString());
+ resp.put("message","导出成功");
+ callback.success(resp.toString());
+ } catch (SQLException | java.io.IOException ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","导出失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ // importCsv -> 从给定 path 导入 CSV,要求首行为列名且列名匹配表字段
+ case "importCsv": {
+ String connectionId = requestJson.optString("connectionId", "");
+ String table = requestJson.optString("table", "");
+ String path = requestJson.optString("path", "");
+ if (connectionId.isEmpty() || table.isEmpty() || path.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","参数不完整").toString());
+ break;
+ }
+ if (!SAFE_IDENT.matcher(table).matches()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","非法表名").toString());
+ break;
+ }
+ java.nio.file.Path csvPath = java.nio.file.Paths.get(path);
+ if (!java.nio.file.Files.exists(csvPath)) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","CSV 文件不存在").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+
+ // 读取首行作为列头
+ try (java.io.BufferedReader br = java.nio.file.Files.newBufferedReader(csvPath, java.nio.charset.StandardCharsets.UTF_8)) {
+ String headerLine = br.readLine();
+ if (headerLine == null) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","CSV 为空").toString());
+ break;
+ }
+ // 简单 CSV 解析(支持双引号),但要求列名没有逗号内部双引号结构复杂情形
+ String[] columns = headerLine.split(",");
+ for (int i = 0; i < columns.length; i++) {
+ columns[i] = columns[i].trim().replaceAll("^\"|\"$", ""); // 去掉可能的两端引号
+ if (!SAFE_IDENT.matcher(columns[i]).matches()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","非法列名: " + columns[i]).toString());
+ return true;
+ }
+ }
+ // 构建 INSERT SQL
+ StringBuilder placeholders = new StringBuilder();
+ for (int i = 0; i < columns.length; i++) {
+ if (i > 0) placeholders.append(",");
+ placeholders.append("?");
+ }
+ String insertSql = "INSERT INTO " + table + " (" + String.join(",", columns) + ") VALUES (" + placeholders.toString() + ")";
+ conn.setAutoCommit(false);
+ int imported = 0;
+ try (java.sql.PreparedStatement pstmt = conn.prepareStatement(insertSql)) {
+ String line;
+ while ((line = br.readLine()) != null) {
+ // 简单分割(不处理复杂引号内部逗号)
+ String[] parts = line.split(",", -1);
+ for (int i = 0; i < columns.length; i++) {
+ String cell = i < parts.length ? parts[i].trim().replaceAll("^\"|\"$", "") : "";
+ pstmt.setString(i + 1, cell.isEmpty() ? null : cell);
+ }
+ pstmt.addBatch();
+ if (++imported % 500 == 0) pstmt.executeBatch();
+ }
+ pstmt.executeBatch();
+ conn.commit();
+ } catch (SQLException ex) {
+ conn.rollback();
+ throw ex;
+ } finally {
+ conn.setAutoCommit(true);
+ }
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("imported", imported);
+ resp.put("message", "导入完成");
+ callback.success(resp.toString());
+ } catch (Exception ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","导入失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ // generateEr -> 收集表、列信息并返回 JSON(前端可据此生成 ER 图)
+ case "generateEr": {
+ String connectionId = requestJson.optString("connectionId", "");
+ if (connectionId.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","connectionId 为空").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+ try {
+ DatabaseMetaData meta = conn.getMetaData();
+ org.json.JSONArray tablesArr = new org.json.JSONArray();
+
+ try (ResultSet rsTables = meta.getTables(conn.getCatalog(), null, null, new String[]{"TABLE"})) {
+ while (rsTables.next()) {
+ String tbl = rsTables.getString("TABLE_NAME");
+ org.json.JSONObject tObj = new org.json.JSONObject();
+ tObj.put("name", tbl);
+ org.json.JSONArray cols = new org.json.JSONArray();
+ try (ResultSet rsCols = meta.getColumns(conn.getCatalog(), null, tbl, null)) {
+ while (rsCols.next()) {
+ org.json.JSONObject c = new org.json.JSONObject();
+ c.put("name", rsCols.getString("COLUMN_NAME"));
+ c.put("type", rsCols.getString("TYPE_NAME"));
+ c.put("size", rsCols.getInt("COLUMN_SIZE"));
+ c.put("nullable", rsCols.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
+ cols.put(c);
+ }
+ }
+ tObj.put("columns", cols);
+ tablesArr.put(tObj);
+ }
+ }
+
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("er", new JSONObject().put("tables", new JSONArray(tablesArr.toString())));
+ callback.success(resp.toString());
+ } catch (SQLException ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","生成 ER 失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ // analyzePerformance -> 尝试返回当前数据库会话/进程信息(简单实现,依据数据库类型)
+ case "analyzePerformance": {
+ String connectionId = requestJson.optString("connectionId", "");
+ if (connectionId.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","connectionId 为空").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ String drvName = info == null ? "" : (info.driver == null ? "" : info.driver.toLowerCase());
+ try {
+ org.json.JSONArray out = new org.json.JSONArray();
+ if ("postgresql".equals(drvName)) {
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery("SELECT pid, usename, state, query FROM pg_stat_activity LIMIT 50")) {
+ while (rs.next()) {
+ org.json.JSONObject r = new org.json.JSONObject();
+ r.put("pid", rs.getObject("pid"));
+ r.put("user", rs.getString("usename"));
+ r.put("state", rs.getString("state"));
+ r.put("query", rs.getString("query"));
+ out.put(r);
+ }
+ }
+ } else if ("mysql".equals(drvName)) {
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery("SHOW PROCESSLIST")) {
+ while (rs.next()) {
+ org.json.JSONObject r = new org.json.JSONObject();
+ r.put("Id", rs.getObject("Id"));
+ r.put("User", rs.getString("User"));
+ r.put("Host", rs.getString("Host"));
+ r.put("db", rs.getString("db"));
+ r.put("Command", rs.getString("Command"));
+ r.put("Time", rs.getString("Time"));
+ r.put("State", rs.getString("State"));
+ r.put("Info", rs.getString("Info"));
+ out.put(r);
+ }
+ }
+ } else {
+ // 通用替代:返回当前时间与简单连接信息
+ org.json.JSONObject r = new org.json.JSONObject();
+ r.put("now", java.time.Instant.now().toString());
+ r.put("message","未实现针对该数据库的详细性能查询,返回通用信息");
+ out.put(r);
+ }
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("data", new JSONArray(out.toString()));
+ callback.success(resp.toString());
+ } catch (SQLException ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","性能分析失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ // listUsers -> 列出数据库用户(尝试常用查询)
+ case "listUsers": {
+ String connectionId = requestJson.optString("connectionId", "");
+ if (connectionId.isEmpty()) {
+ callback.failure(400, new JSONObject().put("status","error").put("message","connectionId 为空").toString());
+ break;
+ }
+ Connection conn = DatabaseConnectionManager.getConnection(connectionId);
+ if (conn == null || conn.isClosed()) {
+ callback.failure(500, new JSONObject().put("status","error").put("message","连接不存在或已关闭").toString());
+ break;
+ }
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ String drvName = info == null ? "" : (info.driver == null ? "" : info.driver.toLowerCase());
+ try {
+ org.json.JSONArray out = new org.json.JSONArray();
+ if ("postgresql".equals(drvName)) {
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery("SELECT usename FROM pg_user")) {
+ while (rs.next()) {
+ out.put(rs.getString(1));
+ }
+ }
+ } else if ("mysql".equals(drvName)) {
+ try (Statement st = conn.createStatement();
+ ResultSet rs = st.executeQuery("SELECT User, Host FROM mysql.user")) {
+ while (rs.next()) {
+ org.json.JSONObject u = new org.json.JSONObject();
+ u.put("user", rs.getString("User"));
+ u.put("host", rs.getString("Host"));
+ out.put(u);
+ }
+ }
+ } else {
+ // H2 / SQLite: 列出连接用户或简单返回空
+ out.put("not_supported_for_db");
+ }
+ JSONObject resp = new JSONObject();
+ resp.put("status","success");
+ resp.put("users", new JSONArray(out.toString()));
+ callback.success(resp.toString());
+ } catch (SQLException ex) {
+ JSONObject err = new JSONObject();
+ err.put("status","error");
+ err.put("message","列出用户失败: " + ex.getMessage());
+ callback.failure(500, err.toString());
+ }
+ break;
+ }
+
+ default: {
+ JSONObject err = new JSONObject();
+ err.put("status", "error");
+ err.put("message", "未知的操作类型: " + type);
+ callback.failure(400, err.toString());
+ }
+ }
+ return true;
+ } catch (org.json.JSONException je) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "请求解析失败: " + je.getMessage());
+ callback.failure(400, error.toString());
+ return true;
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", e.getMessage() == null ? e.toString() : e.getMessage());
+ callback.failure(500, error.toString());
+ return true;
+ }
+ }
+
+ @Override
+ public void onQueryCanceled(CefBrowser browser, CefFrame frame, long queryId) {
+ // 查询取消,可选地记录日志
+ }
+ }, true);
+ } else {
+ System.err.println("popupDataBaseWindow: msgRouter 为 null,消息路由无法注册");
+ }
+ });
+ }
+
+ // 处理数据库连接
+ private static void handleDatabaseConnect(JSONObject request, CefQueryCallback callback) {
+ try {
+ String driver = request.optString("driver", "mysql");
+ String host = request.optString("host", "localhost");
+ String port = request.optString("port", "3306");
+ String database = request.optString("database", "");
+ String username = request.optString("username", "");
+ String password = request.optString("password", "");
+
+ // 验证必要参数
+ if (database.isEmpty()) {
+ throw new IllegalArgumentException("数据库名不能为空");
+ }
+
+ // 建立真实数据库连接
+ String connectionId = DatabaseConnectionManager.connect(driver, host, port, database, username, password);
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("message", "数据库连接成功");
+ response.put("connectionId", connectionId);
+
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ response.put("database", info.database);
+ response.put("driver", info.driver);
+
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "连接失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ }
+ }
+
+ // 处理创建本地数据库
+ private static void handleCreateLocalDatabase(JSONObject request, CefQueryCallback callback) {
+ try {
+ String driver = request.optString("driver", "sqlite");
+ String dbName = request.optString("dbName", "my_database");
+
+ if (dbName.isEmpty()) {
+ throw new IllegalArgumentException("数据库名称不能为空");
+ }
+
+ // 创建本地数据库
+ String connectionId = DatabaseConnectionManager.createLocalDatabase(driver, dbName);
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("message", "本地数据库创建成功");
+ response.put("connectionId", connectionId);
+ response.put("database", dbName);
+ response.put("driver", driver);
+
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "创建数据库失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ }
+ }
+
+ // 处理断开数据库连接
+ private static void handleDisconnectDatabase(JSONObject request, CefQueryCallback callback) {
+ try {
+ String connectionId = request.optString("connectionId", "");
+
+ if (connectionId.isEmpty()) {
+ throw new IllegalArgumentException("连接ID不能为空");
+ }
+
+ DatabaseConnectionManager.disconnect(connectionId);
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("message", "数据库连接已断开");
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "断开连接失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ }
+ }
+
+ // 处理SQL查询执行
+ private static void handleExecuteQuery(JSONObject request, CefQueryCallback callback) {
+ Connection connection = null;
+ Statement statement = null;
+ ResultSet resultSet = null;
+
+ try {
+ String query = request.optString("query", "").trim();
+ String connectionId = request.optString("connectionId", "");
+
+ if (connectionId.isEmpty()) {
+ throw new IllegalArgumentException("连接ID不能为空");
+ }
+ if (query.isEmpty()) {
+ throw new IllegalArgumentException("SQL查询不能为空");
+ }
+
+ connection = DatabaseConnectionManager.getConnection(connectionId);
+ if (connection == null || connection.isClosed()) {
+ throw new SQLException("数据库连接已断开或不存在");
+ }
+
+ long startTime = System.currentTimeMillis();
+
+ JSONObject response = new JSONObject();
+
+ // 判断查询类型
+ boolean isSelect = query.toLowerCase().startsWith("select");
+ boolean isUpdate = query.toLowerCase().startsWith("update") ||
+ query.toLowerCase().startsWith("insert") ||
+ query.toLowerCase().startsWith("delete");
+
+ if (isSelect) {
+ statement = connection.createStatement();
+ resultSet = statement.executeQuery(query);
+
+ // 获取元数据
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ // 构建列信息
+ JSONArray columns = new JSONArray();
+ for (int i = 1; i <= columnCount; i++) {
+ columns.put(metaData.getColumnName(i));
+ }
+
+ // 构建数据
+ JSONArray data = new JSONArray();
+ int rowCount = 0;
+ while (resultSet.next()) {
+ JSONObject row = new JSONObject();
+ for (int i = 1; i <= columnCount; i++) {
+ String columnName = metaData.getColumnName(i);
+ Object value = resultSet.getObject(i);
+ row.put(columnName, value != null ? value.toString() : null);
+ }
+ data.put(row);
+ rowCount++;
+ }
+
+ long endTime = System.currentTimeMillis();
+ double executionTime = (endTime - startTime) / 1000.0;
+
+ response.put("status", "success");
+ response.put("executionTime", String.format("%.3fs", executionTime));
+ response.put("rowCount", rowCount);
+ response.put("columns", columns);
+ response.put("data", data);
+
+ } else if (isUpdate) {
+ statement = connection.createStatement();
+ int affectedRows = statement.executeUpdate(query);
+
+ long endTime = System.currentTimeMillis();
+ double executionTime = (endTime - startTime) / 1000.0;
+
+ response.put("status", "success");
+ response.put("executionTime", String.format("%.3fs", executionTime));
+ response.put("affectedRows", affectedRows);
+ response.put("message", "操作成功,影响 " + affectedRows + " 行");
+
+ } else {
+ // 其他类型的查询(CREATE, DROP, ALTER等)
+ statement = connection.createStatement();
+ boolean hasResults = statement.execute(query);
+
+ long endTime = System.currentTimeMillis();
+ double executionTime = (endTime - startTime) / 1000.0;
+
+ response.put("status", "success");
+ response.put("executionTime", String.format("%.3fs", executionTime));
+ response.put("message", "查询执行成功");
+
+ if (hasResults) {
+ resultSet = statement.getResultSet();
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ JSONArray columns = new JSONArray();
+ for (int i = 1; i <= columnCount; i++) {
+ columns.put(metaData.getColumnName(i));
+ }
+
+ JSONArray data = new JSONArray();
+ int rowCount = 0;
+ while (resultSet.next()) {
+ JSONObject row = new JSONObject();
+ for (int i = 1; i <= columnCount; i++) {
+ String columnName = metaData.getColumnName(i);
+ Object value = resultSet.getObject(i);
+ row.put(columnName, value != null ? value.toString() : null);
+ }
+ data.put(row);
+ rowCount++;
+ }
+
+ response.put("rowCount", rowCount);
+ response.put("columns", columns);
+ response.put("data", data);
+ }
+ }
+
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "查询执行失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ } finally {
+ // 关闭资源
+ try {
+ if (resultSet != null) resultSet.close();
+ if (statement != null) statement.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // 处理获取表列表
+ private static void handleGetTables(JSONObject request, CefQueryCallback callback) {
+ Connection connection = null;
+ ResultSet resultSet = null;
+
+ try {
+ String connectionId = request.optString("connectionId", "");
+
+ if (connectionId.isEmpty()) {
+ throw new IllegalArgumentException("连接ID不能为空");
+ }
+
+ connection = DatabaseConnectionManager.getConnection(connectionId);
+ if (connection == null || connection.isClosed()) {
+ throw new SQLException("数据库连接已断开或不存在");
+ }
+
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ JSONArray tables = new JSONArray();
+
+ // 根据数据库类型获取表信息
+ String catalog = null;
+ String schema = null;
+
+ switch (info.driver.toLowerCase()) {
+ case "mysql":
+ catalog = info.database;
+ break;
+ case "postgresql":
+ schema = "public";
+ break;
+ case "sqlite":
+ case "h2":
+ // SQLite和H2不需要特定的catalog或schema
+ break;
+ }
+
+ DatabaseMetaData metaData = connection.getMetaData();
+ resultSet = metaData.getTables(catalog, schema, null, new String[]{"TABLE"});
+
+ while (resultSet.next()) {
+ String tableName = resultSet.getString("TABLE_NAME");
+ String tableType = resultSet.getString("TABLE_TYPE");
+
+ // 获取表的行数
+ int rowCount = 0;
+ try (Statement countStmt = connection.createStatement();
+ ResultSet countRs = countStmt.executeQuery("SELECT COUNT(*) FROM " + tableName)) {
+ if (countRs.next()) {
+ rowCount = countRs.getInt(1);
+ }
+ } catch (SQLException e) {
+ // 如果无法获取行数,忽略错误
+ }
+
+ JSONObject table = new JSONObject();
+ table.put("name", tableName);
+ table.put("type", tableType);
+ table.put("rows", rowCount);
+ tables.put(table);
+ }
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("tables", tables);
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "获取表列表失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ } finally {
+ try {
+ if (resultSet != null) resultSet.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // 处理获取表数据
+ private static void handleGetTableData(JSONObject request, CefQueryCallback callback) {
+ Connection connection = null;
+ Statement statement = null;
+ ResultSet resultSet = null;
+
+ try {
+ String tableName = request.optString("tableName", "");
+ String connectionId = request.optString("connectionId", "");
+ int limit = request.optInt("limit", 50);
+ int offset = request.optInt("offset", 0);
+
+ if (connectionId.isEmpty()) {
+ throw new IllegalArgumentException("连接ID不能为空");
+ }
+ if (tableName.isEmpty()) {
+ throw new IllegalArgumentException("表名不能为空");
+ }
+
+ connection = DatabaseConnectionManager.getConnection(connectionId);
+ if (connection == null || connection.isClosed()) {
+ throw new SQLException("数据库连接已断开或不存在");
+ }
+
+ // 构建分页查询
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ String query;
+
+ switch (info.driver.toLowerCase()) {
+ case "mysql":
+ query = String.format("SELECT * FROM `%s` LIMIT %d OFFSET %d", tableName, limit, offset);
+ break;
+ case "postgresql":
+ query = String.format("SELECT * FROM \"%s\" LIMIT %d OFFSET %d", tableName, limit, offset);
+ break;
+ default:
+ query = String.format("SELECT * FROM %s LIMIT %d OFFSET %d", tableName, limit, offset);
+ }
+
+ statement = connection.createStatement();
+ resultSet = statement.executeQuery(query);
+
+ // 获取元数据
+ ResultSetMetaData metaData = resultSet.getMetaData();
+ int columnCount = metaData.getColumnCount();
+
+ // 构建列信息
+ JSONArray columns = new JSONArray();
+ for (int i = 1; i <= columnCount; i++) {
+ columns.put(metaData.getColumnName(i));
+ }
+
+ // 构建数据
+ JSONArray data = new JSONArray();
+ while (resultSet.next()) {
+ JSONObject row = new JSONObject();
+ for (int i = 1; i <= columnCount; i++) {
+ String columnName = metaData.getColumnName(i);
+ Object value = resultSet.getObject(i);
+ row.put(columnName, value != null ? value.toString() : null);
+ }
+ data.put(row);
+ }
+
+ // 获取总行数
+ int total = 0;
+ try (Statement countStmt = connection.createStatement();
+ ResultSet countRs = countStmt.executeQuery("SELECT COUNT(*) FROM " + tableName)) {
+ if (countRs.next()) {
+ total = countRs.getInt(1);
+ }
+ } catch (SQLException e) {
+ // 如果无法获取总行数,使用当前数据行数
+ total = data.length();
+ }
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("tableName", tableName);
+ response.put("columns", columns);
+ response.put("data", data);
+ response.put("total", total);
+ response.put("offset", offset);
+ response.put("limit", limit);
+
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "获取表数据失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ } finally {
+ try {
+ if (resultSet != null) resultSet.close();
+ if (statement != null) statement.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // 处理获取表结构
+ private static void handleGetTableStructure(JSONObject request, CefQueryCallback callback) {
+ Connection connection = null;
+ ResultSet resultSet = null;
+
+ try {
+ String tableName = request.optString("tableName", "");
+ String connectionId = request.optString("connectionId", "");
+
+ if (connectionId.isEmpty()) {
+ throw new IllegalArgumentException("连接ID不能为空");
+ }
+ if (tableName.isEmpty()) {
+ throw new IllegalArgumentException("表名不能为空");
+ }
+
+ connection = DatabaseConnectionManager.getConnection(connectionId);
+ if (connection == null || connection.isClosed()) {
+ throw new SQLException("数据库连接已断开或不存在");
+ }
+
+ DatabaseConnectionManager.DatabaseInfo info = DatabaseConnectionManager.getConnectionInfo(connectionId);
+ DatabaseMetaData metaData = connection.getMetaData();
+
+ String catalog = null;
+ String schema = null;
+
+ switch (info.driver.toLowerCase()) {
+ case "mysql":
+ catalog = info.database;
+ break;
+ case "postgresql":
+ schema = "public";
+ break;
+ }
+
+ resultSet = metaData.getColumns(catalog, schema, tableName, null);
+
+ JSONArray columns = new JSONArray();
+ while (resultSet.next()) {
+ JSONObject column = new JSONObject();
+ column.put("name", resultSet.getString("COLUMN_NAME"));
+ column.put("type", resultSet.getString("TYPE_NAME"));
+ column.put("size", resultSet.getInt("COLUMN_SIZE"));
+ column.put("nullable", resultSet.getInt("NULLABLE") == DatabaseMetaData.columnNullable);
+ column.put("defaultValue", resultSet.getString("COLUMN_DEF"));
+ columns.put(column);
+ }
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("tableName", tableName);
+ response.put("columns", columns);
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "获取表结构失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ } finally {
+ try {
+ if (resultSet != null) resultSet.close();
+ } catch (SQLException e) {
+ e.printStackTrace();
+ }
+ }
+ }
+
+ // 处理主题更新(保持不变)
+ private static void handleUpdateTheme(JSONObject request, CefQueryCallback callback) {
+ try {
+ String theme = request.optString("theme", "light");
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("theme", theme);
+ response.put("message", "主题已更新为: " + theme);
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "主题更新失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ }
+ }
+
+ // 处理字体获取(保持不变)
+ private static void handleGetFonts(JSONObject request, CefQueryCallback callback) {
+ try {
+ JSONArray fonts = new JSONArray();
+ String[] fontList = {
+ "Segoe UI", "Microsoft YaHei", "SimSun", "Arial",
+ "Helvetica", "Times New Roman", "Courier New", "Verdana"
+ };
+
+ for (String font : fontList) {
+ fonts.put(font);
+ }
+
+ JSONObject response = new JSONObject();
+ response.put("status", "success");
+ response.put("fonts", fonts);
+ callback.success(response.toString());
+
+ } catch (Exception e) {
+ JSONObject error = new JSONObject();
+ error.put("status", "error");
+ error.put("message", "获取字体失败: " + e.getMessage());
+ callback.failure(500, error.toString());
+ }
+ }
}
diff --git a/src/main/java/com/axis/innovators/box/browser/util/DatabaseConnectionManager.java b/src/main/java/com/axis/innovators/box/browser/util/DatabaseConnectionManager.java
new file mode 100644
index 0000000..9d87dd0
--- /dev/null
+++ b/src/main/java/com/axis/innovators/box/browser/util/DatabaseConnectionManager.java
@@ -0,0 +1,397 @@
+package com.axis.innovators.box.browser.util;
+
+import java.sql.*;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Properties;
+
+/**
+ * 数据库连接管理器
+ * @author tzdwindows 7
+ */
+public class DatabaseConnectionManager {
+ private static final java.util.Map connections = new java.util.concurrent.ConcurrentHashMap<>();
+ private static final java.util.Map connectionInfo = new java.util.concurrent.ConcurrentHashMap<>();
+
+ public static class DatabaseInfo {
+ public String driver;
+ public String url;
+ public String host;
+ public String port;
+ public String database;
+ public String username;
+
+ public DatabaseInfo(String driver, String url, String host, String port, String database, String username) {
+ this.driver = driver;
+ this.url = url;
+ this.host = host;
+ this.port = port;
+ this.database = database;
+ this.username = username;
+ }
+ }
+
+ public static String connect(String driver, String host, String port,
+ String database, String username, String password) throws SQLException {
+ String connectionId = "conn_" + System.currentTimeMillis();
+
+ String drv = driver == null ? "" : driver.toLowerCase();
+
+ // 规范化 database 路径(特别是 Windows 反斜杠问题)
+ if (database != null) {
+ database = database.replace("\\", "/");
+ } else {
+ database = "";
+ }
+
+ // 先显式加载驱动,避免因为 classloader 问题找不到驱动
+ try {
+ switch (drv) {
+ case "mysql":
+ Class.forName("com.mysql.cj.jdbc.Driver");
+ break;
+ case "postgresql":
+ Class.forName("org.postgresql.Driver");
+ break;
+ case "sqlite":
+ Class.forName("org.sqlite.JDBC");
+ break;
+ case "oracle":
+ Class.forName("oracle.jdbc.OracleDriver");
+ break;
+ case "h2":
+ Class.forName("org.h2.Driver");
+ break;
+ default:
+ // 不抛出,使后续 URL 构造仍可检查类型
+ }
+ } catch (ClassNotFoundException e) {
+ throw new SQLException("JDBC 驱动未找到,请确认对应驱动已加入 classpath: " + e.getMessage(), e);
+ }
+
+ String url = buildConnectionUrl(driver, host, port, database);
+
+ Connection connection;
+ Properties props = new Properties();
+ if (username != null && !username.isEmpty()) props.setProperty("user", username);
+ if (password != null && !password.isEmpty()) props.setProperty("password", password);
+
+ switch (drv) {
+ case "mysql":
+ props.setProperty("useSSL", "false");
+ props.setProperty("serverTimezone", "UTC");
+ connection = DriverManager.getConnection(url, props);
+ break;
+ case "postgresql":
+ connection = DriverManager.getConnection(url, props);
+ break;
+ case "sqlite":
+ // sqlite 不需要 props,URL 已经是文件路径(已做过替换)
+ connection = DriverManager.getConnection(url);
+ break;
+ case "oracle":
+ connection = DriverManager.getConnection(url, props);
+ break;
+ case "h2":
+ // H2 使用默认用户 sa / 空密码(如果需要可调整)
+ connection = DriverManager.getConnection(url, "sa", "");
+ break;
+ default:
+ throw new SQLException("不支持的数据库类型: " + driver);
+ }
+
+ connections.put(connectionId, connection);
+ connectionInfo.put(connectionId, new DatabaseInfo(driver, url, host, port, database, username));
+ return connectionId;
+ }
+
+ public static void disconnect(String connectionId) throws SQLException {
+ Connection connection = connections.get(connectionId);
+ if (connection != null && !connection.isClosed()) {
+ connection.close();
+ }
+ connections.remove(connectionId);
+ connectionInfo.remove(connectionId);
+ }
+
+ public static Connection getConnection(String connectionId) {
+ return connections.get(connectionId);
+ }
+
+ public static DatabaseInfo getConnectionInfo(String connectionId) {
+ return connectionInfo.get(connectionId);
+ }
+
+ private static String buildConnectionUrl(String driver, String host, String port, String database) {
+ String drv = driver == null ? "" : driver.toLowerCase();
+ switch (drv) {
+ case "mysql":
+ return "jdbc:mysql://" + host + ":" + port + "/" + database;
+ case "postgresql":
+ return "jdbc:postgresql://" + host + ":" + port + "/" + database;
+ case "sqlite":
+ // 对于 SQLite,database 可能是绝对路径或相对文件名,先把反斜杠替成正斜杠
+ if (database == null || database.isEmpty()) {
+ return "jdbc:sqlite::memory:";
+ }
+ String normalized = database.replace("\\", "/");
+ // 如果看起来像相对文件名(不含冒号也不以 / 开头),则当作相对于用户目录的路径
+ if (!normalized.contains(":") && !normalized.startsWith("/")) {
+ String userHome = System.getProperty("user.home").replace("\\", "/");
+ normalized = userHome + "/" + normalized;
+ }
+ return "jdbc:sqlite:" + normalized;
+ case "oracle":
+ return "jdbc:oracle:thin:@" + host + ":" + port + ":" + database;
+ case "h2":
+ // H2 文件路径同样做反斜杠处理
+ if (database == null || database.isEmpty()) {
+ String userHome = System.getProperty("user.home").replace("\\", "/");
+ return "jdbc:h2:file:" + userHome + "/.axis_innovators_box/databases/h2db";
+ } else {
+ String norm = database.replace("\\", "/");
+ // 如果传入仅是名字(无斜杠或冒号),则存到用户目录下
+ if (!norm.contains("/") && !norm.contains(":")) {
+ String userHome = System.getProperty("user.home").replace("\\", "/");
+ norm = userHome + "/.axis_innovators_box/databases/" + norm;
+ }
+ return "jdbc:h2:file:" + norm;
+ }
+ default:
+ throw new IllegalArgumentException("不支持的数据库类型: " + driver);
+ }
+ }
+
+
+ /**
+ * 在服务器上创建数据库(MySQL / PostgreSQL / Oracle(示例))
+ * @param driver mysql | postgresql | oracle
+ * @param host 数据库主机
+ * @param port 端口
+ * @param dbName 要创建的数据库名(或 schema 名)
+ * @param adminUser 管理员用户名(用于创建数据库)
+ * @param adminPassword 管理员密码
+ * @return 如果创建成功返回一个简短消息,否则抛出 SQLException
+ * @throws SQLException
+ */
+ public static String createDatabaseOnServer(String driver, String host, String port,
+ String dbName, String adminUser, String adminPassword) throws SQLException {
+ if (driver == null) throw new SQLException("driver 不能为空");
+ String drv = driver.toLowerCase().trim();
+
+ // 简单校验 dbName(避免注入)——只允许字母数字下划线
+ if (dbName == null || !dbName.matches("[A-Za-z0-9_]+")) {
+ throw new SQLException("不合法的数据库名: " + dbName);
+ }
+
+ try {
+ switch (drv) {
+ case "mysql":
+ // 加载驱动(如果尚未加载)
+ try { Class.forName("com.mysql.cj.jdbc.Driver"); } catch (ClassNotFoundException e) {
+ throw new SQLException("MySQL 驱动未找到,请加入 mysql-connector-java 到 classpath", e);
+ }
+ // 连接到服务器的默认库(不指定数据库)以执行 CREATE DATABASE
+ String mysqlUrl = "jdbc:mysql://" + host + ":" + port + "/?useSSL=false&serverTimezone=UTC";
+ try (Connection conn = DriverManager.getConnection(mysqlUrl, adminUser, adminPassword);
+ Statement st = conn.createStatement()) {
+ String sql = "CREATE DATABASE `" + dbName + "` CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci";
+ st.executeUpdate(sql);
+ }
+ return "MySQL 数据库创建成功: " + dbName;
+
+ case "postgresql":
+ case "postgres":
+ try { Class.forName("org.postgresql.Driver"); } catch (ClassNotFoundException e) {
+ throw new SQLException("Postgres 驱动未找到,请加入 postgresql 到 classpath", e);
+ }
+ // 连接到默认 postgres 数据库以创建新数据库
+ String pgUrl = "jdbc:postgresql://" + host + ":" + port + "/postgres";
+ try (Connection conn = DriverManager.getConnection(pgUrl, adminUser, adminPassword);
+ Statement st = conn.createStatement()) {
+ String sql = "CREATE DATABASE " + dbName + " WITH ENCODING 'UTF8'";
+ st.executeUpdate(sql);
+ }
+ return "PostgreSQL 数据库创建成功: " + dbName;
+
+ case "oracle":
+ // Oracle 数据库“创建数据库”通常由 DBA 完成(复杂),这里示例创建用户/模式(更常见)
+ try { Class.forName("oracle.jdbc.OracleDriver"); } catch (ClassNotFoundException e) {
+ throw new SQLException("Oracle 驱动未找到,请把 ojdbc.jar 加入 classpath", e);
+ }
+ // 需使用具有足够权限的账户(通常为 sys or system),并且 URL 需要正确(SID / ServiceName)
+ // 下面示例假设通过 SID 连接: jdbc:oracle:thin:@host:port:SID
+ String oracleUrl = "jdbc:oracle:thin:@" + host + ":" + port + ":" + "ORCL"; // 把 ORCL 换成实际 SID
+ try (Connection conn = DriverManager.getConnection(oracleUrl, adminUser, adminPassword);
+ Statement st = conn.createStatement()) {
+ // 创建 user(schema)示例
+ String pwd = adminPassword; // 实际应使用独立密码,不推荐用 adminPassword
+ String createUser = "CREATE USER " + dbName + " IDENTIFIED BY \"" + pwd + "\"";
+ String grant = "GRANT CONNECT, RESOURCE TO " + dbName;
+ st.executeUpdate(createUser);
+ st.executeUpdate(grant);
+ } catch (SQLException ex) {
+ // Oracle 操作更容易失败,给出提示
+ throw new SQLException("Oracle: 无法创建用户/模式,请检查权限和 URL(通常需由 DBA 操作): " + ex.getMessage(), ex);
+ }
+ return "Oracle 用户/模式创建成功(注意:真正的 DB 实例通常由 DBA 管理): " + dbName;
+
+ default:
+ throw new SQLException("不支持的数据库类型: " + driver);
+ }
+ } catch (SQLException se) {
+ // 透传 SQLException,调用方会拿到 message 并反馈给前端
+ throw se;
+ } catch (Exception e) {
+ throw new SQLException("创建数据库时发生异常: " + e.getMessage(), e);
+ }
+ }
+ public static String createLocalDatabase(String driver, String dbName) throws SQLException {
+ switch (driver.toLowerCase()) {
+ case "sqlite":
+ // 创建目录并构造规范化路径(确保路径使用正斜杠)
+ String dbFileName = dbName.endsWith(".db") ? dbName : (dbName + ".db");
+ java.nio.file.Path dbDir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
+ try {
+ java.nio.file.Files.createDirectories(dbDir);
+ } catch (Exception e) {
+ throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
+ }
+ String dbPath = dbDir.resolve(dbFileName).toAbsolutePath().toString().replace("\\", "/");
+
+ // 显式加载 sqlite 驱动(避免 No suitable driver)
+ try {
+ Class.forName("org.sqlite.JDBC");
+ } catch (ClassNotFoundException e) {
+ throw new SQLException("未找到 sqlite 驱动,请确认 sqlite-jdbc 已加入 classpath", e);
+ }
+
+ // 直接使用 connect 构建连接(connect 中会通过 buildConnectionUrl 处理 path)
+ String connectionId = connect("sqlite", "", "", dbPath, "", "");
+
+ // 创建示例表
+ createSampleTables(connectionId);
+
+ return connectionId;
+
+ case "h2":
+ java.nio.file.Path h2Dir = java.nio.file.Paths.get(System.getProperty("user.home"), ".axis_innovators_box", "databases");
+ try {
+ java.nio.file.Files.createDirectories(h2Dir);
+ } catch (Exception e) {
+ throw new SQLException("无法创建数据库目录: " + e.getMessage(), e);
+ }
+ String h2Path = h2Dir.resolve(dbName).toAbsolutePath().toString().replace("\\", "/");
+ String h2Url = "jdbc:h2:file:" + h2Path;
+
+ try {
+ Class.forName("org.h2.Driver");
+ } catch (ClassNotFoundException e) {
+ throw new SQLException("未找到 H2 驱动,请确认 h2.jar 已加入 classpath", e);
+ }
+
+ Connection h2Conn = DriverManager.getConnection(h2Url, "sa", "");
+ String h2ConnectionId = "conn_" + System.currentTimeMillis();
+ connections.put(h2ConnectionId, h2Conn);
+ connectionInfo.put(h2ConnectionId, new DatabaseInfo("h2", h2Url, "localhost", "", dbName, "sa"));
+
+ createSampleTables(h2ConnectionId);
+
+ return h2ConnectionId;
+
+ default:
+ throw new SQLException("不支持创建本地数据库类型: " + driver);
+ }
+ }
+
+ private static void createSampleTables(String connectionId) throws SQLException {
+ Connection conn = getConnection(connectionId);
+ DatabaseInfo info = getConnectionInfo(connectionId);
+
+ if ("sqlite".equals(info.driver) || "h2".equals(info.driver)) {
+ try (Statement stmt = conn.createStatement()) {
+ // 创建用户表
+ stmt.execute("CREATE TABLE IF NOT EXISTS users (" +
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "username VARCHAR(50) NOT NULL UNIQUE, " +
+ "email VARCHAR(100) NOT NULL, " +
+ "password VARCHAR(100) NOT NULL, " +
+ "status VARCHAR(20) DEFAULT 'active', " +
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+ ")");
+
+ // 创建产品表
+ stmt.execute("CREATE TABLE IF NOT EXISTS products (" +
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "name VARCHAR(100) NOT NULL, " +
+ "description TEXT, " +
+ "price DECIMAL(10,2) NOT NULL, " +
+ "stock INTEGER DEFAULT 0, " +
+ "category VARCHAR(50), " +
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP" +
+ ")");
+
+ // 创建订单表
+ stmt.execute("CREATE TABLE IF NOT EXISTS orders (" +
+ "id INTEGER PRIMARY KEY AUTOINCREMENT, " +
+ "user_id INTEGER, " +
+ "product_id INTEGER, " +
+ "quantity INTEGER NOT NULL, " +
+ "total_price DECIMAL(10,2) NOT NULL, " +
+ "status VARCHAR(20) DEFAULT 'pending', " +
+ "created_at DATETIME DEFAULT CURRENT_TIMESTAMP, " +
+ "FOREIGN KEY (user_id) REFERENCES users(id), " +
+ "FOREIGN KEY (product_id) REFERENCES products(id)" +
+ ")");
+
+ // 插入示例数据
+ insertSampleData(conn);
+ }
+ }
+ }
+
+ private static void insertSampleData(Connection conn) throws SQLException {
+ // 检查是否已有数据
+ try (Statement checkStmt = conn.createStatement();
+ ResultSet rs = checkStmt.executeQuery("SELECT COUNT(*) FROM users")) {
+ if (rs.next() && rs.getInt(1) == 0) {
+ // 插入用户数据
+ try (PreparedStatement pstmt = conn.prepareStatement(
+ "INSERT INTO users (username, email, password) VALUES (?, ?, ?)")) {
+ String[][] users = {
+ {"admin", "admin@example.com", "password123"},
+ {"user1", "user1@example.com", "password123"},
+ {"user2", "user2@example.com", "password123"}
+ };
+
+ for (String[] user : users) {
+ pstmt.setString(1, user[0]);
+ pstmt.setString(2, user[1]);
+ pstmt.setString(3, user[2]);
+ pstmt.executeUpdate();
+ }
+ }
+
+ // 插入产品数据
+ try (PreparedStatement pstmt = conn.prepareStatement(
+ "INSERT INTO products (name, description, price, stock, category) VALUES (?, ?, ?, ?, ?)")) {
+ Object[][] products = {
+ {"笔记本电脑", "高性能笔记本电脑", 5999.99, 50, "电子"},
+ {"智能手机", "最新款智能手机", 3999.99, 100, "电子"},
+ {"办公椅", "舒适办公椅", 299.99, 30, "家居"},
+ {"咖啡机", "全自动咖啡机", 899.99, 20, "家电"}
+ };
+
+ for (Object[] product : products) {
+ pstmt.setString(1, (String) product[0]);
+ pstmt.setString(2, (String) product[1]);
+ pstmt.setDouble(3, (Double) product[2]);
+ pstmt.setInt(4, (Integer) product[3]);
+ pstmt.setString(5, (String) product[4]);
+ pstmt.executeUpdate();
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java b/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java
index 2f60d8d..831fc27 100644
--- a/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java
+++ b/src/main/java/com/axis/innovators/box/register/RegistrationSettingsItem.java
@@ -43,32 +43,33 @@ public class RegistrationSettingsItem extends WindowsJDialog {
private final AxisInnovatorsBox mainWindow;
static {
- RegistrationSettingsItem registrationSettingsItem = new RegistrationSettingsItem();
- JPanel pluginPanel = createPluginSettingsPanel();
- JPanel generalPanel = createGeneralSettingsPanel();
- JPanel aboutPanel = createAboutPanel();
- JPanel themePanel = createThemePanel();
-
- registrationSettingsItem.addSettings(
- generalPanel, language.getText("settings.2.title"),
- null, language.getText("settings.2.tip"), "system:settings_appearance_item"
- );
- registrationSettingsItem.addSettings(
- pluginPanel, language.getText("settings.1.title"),
- null, language.getText("settings.1.tip"), "system:settings_plugins_item"
- );
- registrationSettingsItem.addSettings(
- themePanel, language.getText("settings.4.title"),
- null, language.getText("settings.4.tip"), "system:settings_theme_item"
- );
- registrationSettingsItem.addSettings(
- aboutPanel, language.getText("settings.3.title"),
- null, language.getText("settings.3.tip"), "system:settings_information_item"
- );
-
- registrationSettingsItemList.add(
- registrationSettingsItem
- );
+ //RegistrationSettingsItem registrationSettingsItem = new RegistrationSettingsItem();
+ //JPanel pluginPanel = createPluginSettingsPanel();
+ //JPanel generalPanel = createGeneralSettingsPanel();
+ //JPanel aboutPanel = createAboutPanel();
+ //JPanel themePanel = createThemePanel();
+//
+ //registrationSettingsItem.addSettings(
+ // generalPanel, language.getText("settings.2.title"),
+ // null, language.getText("settings.2.tip"), "system:settings_appearance_item"
+ //);
+ //registrationSettingsItem.addSettings(
+ // pluginPanel, language.getText("settings.1.title"),
+ // null, language.getText("settings.1.tip"), "system:settings_plugins_item"
+ //);
+ //registrationSettingsItem.addSettings(
+ // themePanel, language.getText("settings.4.title"),
+ // null, language.getText("settings.4.tip"), "system:settings_theme_item"
+ //);
+ //registrationSettingsItem.addSettings(
+ // aboutPanel, language.getText("settings.3.title"),
+ // null, language.getText("settings.3.tip"), "system:settings_information_item"
+ //);
+//
+ //registrationSettingsItemList.add(
+ // registrationSettingsItem
+ //);
+ overloading();
}
public static void overloading() {
@@ -624,7 +625,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
MainWindow mainWindow = getMainWindow();
if (mainWindow != null) {
mainWindow.setBackgroundWithGlassEffect(bgImage, blurAmount,1.0f);
- logger.info("图片背景已应用,模糊度: " + blurAmount);
+ logger.info("图片背景已应用,模糊度: {}", blurAmount);
// 保存设置到 StateManager
String backgroundPath = (String) backgroundPreview.getClientProperty("backgroundPath");
@@ -957,7 +958,7 @@ public class RegistrationSettingsItem extends WindowsJDialog {
settingsManager.saveState("background.path", backgroundPath);
settingsManager.saveState("background.blur", blurAmount);
settingsManager.saveState("background.enabled", true);
- logger.info("背景设置已保存: " + backgroundPath + ", 模糊度: " + blurAmount);
+ logger.info("背景设置已保存: {}, 模糊度: {}", backgroundPath, blurAmount);
}
}
diff --git a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java
index 4ad99a4..e910bbe 100644
--- a/src/main/java/com/axis/innovators/box/register/RegistrationTool.java
+++ b/src/main/java/com/axis/innovators/box/register/RegistrationTool.java
@@ -82,6 +82,16 @@ public class RegistrationTool {
}
}));
+ programmingToolsCategory.addTool(new MainWindow.ToolItem("数据库管理工具", "programming/programming_dark.png",
+ "用于管理数据库" +
+ "\n作者:tzdwindows 7", ++id, new AbstractAction() {
+ @Override
+ public void actionPerformed(ActionEvent e) {
+ // Window owner = SwingUtilities.windowForComponent((Component) e.getSource());
+ MainApplication.popupDataBaseWindow();
+ }
+ }));
+
MainWindow.ToolCategory aICategory = new MainWindow.ToolCategory("AI工具",
"ai/ai.png",
"人工智能/大语言模型");
diff --git a/src/main/java/com/axis/innovators/box/window/MainWindow.java b/src/main/java/com/axis/innovators/box/window/MainWindow.java
index 05dd1c6..be796e6 100644
--- a/src/main/java/com/axis/innovators/box/window/MainWindow.java
+++ b/src/main/java/com/axis/innovators/box/window/MainWindow.java
@@ -938,7 +938,15 @@ public class MainWindow extends JFrame {
// ---------- 工具卡/面板 ----------
private JPanel createToolsPanel(ToolCategory category) {
- JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16));
+ JPanel panel = new JPanel(new WrapLayout(FlowLayout.LEFT, 16, 16)) {
+ @Override
+ public Dimension getPreferredSize() {
+ // 计算容器宽度以恰好容纳3个卡片和间隙
+ int cardWidth = 240; // 卡片宽度
+ int gap = 16;
+ return new Dimension(cardWidth * 3 + gap * 2, super.getPreferredSize().height);
+ }
+ };
panel.setOpaque(false);
panel.setBorder(null);
diff --git a/src/main/resources/icons/programming/JarApiViewer/database_dark.png b/src/main/resources/icons/programming/JarApiViewer/database_dark.png
new file mode 100644
index 0000000..434579c
Binary files /dev/null and b/src/main/resources/icons/programming/JarApiViewer/database_dark.png differ
diff --git a/src/main/resources/icons/programming/database.png b/src/main/resources/icons/programming/database.png
new file mode 100644
index 0000000..64ecbc9
Binary files /dev/null and b/src/main/resources/icons/programming/database.png differ
diff --git a/src/main/resources/icons/programming/database_dark.png b/src/main/resources/icons/programming/database_dark.png
new file mode 100644
index 0000000..434579c
Binary files /dev/null and b/src/main/resources/icons/programming/database_dark.png differ