feat(database): 实现表设计器和数据编辑功能
- 添加表设计器模态框,支持创建和修改表结构 - 实现列、索引、约束的动态添加和编辑功能- 增加数据表行数据的增删改查操作界面 - 添加工具面板的折叠展开功能和快速创建表按钮- 实现表搜索功能,支持按名称过滤表列表 - 更新Java后端模拟数据以支持新的表结构操作- 添加MySQL连接配置的字符集和编码设置 - 增加表设计器的表单控件和响应式布局样式 - 实列属性的完整现表设计器中配置选项 - 添加保存表结构时的数据收集和验证逻辑
This commit is contained in:
File diff suppressed because it is too large
Load Diff
@@ -1,5 +1,6 @@
|
||||
package com.axis.innovators.box.browser;
|
||||
|
||||
import com.axis.innovators.box.AxisInnovatorsBox;
|
||||
import com.axis.innovators.box.browser.util.CodeExecutor;
|
||||
import com.axis.innovators.box.browser.util.DatabaseConnectionManager;
|
||||
import com.axis.innovators.box.tools.FolderCreator;
|
||||
@@ -23,8 +24,7 @@ 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.*;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
@@ -531,24 +531,39 @@ public class MainApplication {
|
||||
}
|
||||
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
|
||||
try (java.io.BufferedWriter writer = java.nio.file.Files.newBufferedWriter(
|
||||
outPath,
|
||||
java.nio.charset.StandardCharsets.UTF_8
|
||||
)) {
|
||||
// 写入 UTF-8 BOM
|
||||
writer.write('\uFEFF');
|
||||
|
||||
for (int i = 1; i <= cols; i++) {
|
||||
if (i > 1) writer.write(",");
|
||||
writer.write("\"" + md.getColumnLabel(i).replace("\"","\"\"") + "\"");
|
||||
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("\"" + cell.replace("\"", "\"\"") + "\"");
|
||||
}
|
||||
writer.write("\n");
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
try {
|
||||
String pathStr = outPath.toAbsolutePath().toString();
|
||||
String os = System.getProperty("os.name").toLowerCase();
|
||||
if (os.contains("win")) {
|
||||
new ProcessBuilder("explorer.exe", "/select," + pathStr).start();
|
||||
}
|
||||
} catch (Exception ignore) {
|
||||
}
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
@@ -811,7 +826,476 @@ public class MainApplication {
|
||||
}
|
||||
break;
|
||||
}
|
||||
case "insertRow": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
JSONObject rowData = requestJson.optJSONObject("rowData");
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || rowData == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建INSERT语句
|
||||
StringBuilder columns = new StringBuilder();
|
||||
StringBuilder placeholders = new StringBuilder();
|
||||
java.util.List<Object> values = new java.util.ArrayList<>();
|
||||
|
||||
java.util.Iterator<String> keys = rowData.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
if (columns.length() > 0) {
|
||||
columns.append(", ");
|
||||
placeholders.append(", ");
|
||||
}
|
||||
columns.append(key);
|
||||
placeholders.append("?");
|
||||
values.add(rowData.get(key));
|
||||
}
|
||||
|
||||
String sql = "INSERT INTO " + tableName + " (" + columns.toString() + ") VALUES (" + placeholders.toString() + ")";
|
||||
|
||||
try (java.sql.PreparedStatement pstmt = conn.prepareStatement(sql, java.sql.Statement.RETURN_GENERATED_KEYS)) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
pstmt.setObject(i + 1, values.get(i));
|
||||
}
|
||||
|
||||
int affectedRows = pstmt.executeUpdate();
|
||||
|
||||
// 获取自增ID(如果有)
|
||||
int newId = -1;
|
||||
try (java.sql.ResultSet generatedKeys = pstmt.getGeneratedKeys()) {
|
||||
if (generatedKeys.next()) {
|
||||
newId = generatedKeys.getInt(1);
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("affectedRows", affectedRows);
|
||||
if (newId != -1) {
|
||||
resp.put("newId", newId);
|
||||
}
|
||||
resp.put("message", "插入成功");
|
||||
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;
|
||||
}
|
||||
|
||||
case "updateRow": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
JSONObject originalData = requestJson.optJSONObject("originalData");
|
||||
JSONObject updatedData = requestJson.optJSONObject("updatedData");
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || originalData == null || updatedData == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建UPDATE语句
|
||||
StringBuilder setClause = new StringBuilder();
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
java.util.List<Object> values = new java.util.ArrayList<>();
|
||||
|
||||
// SET部分
|
||||
java.util.Iterator<String> updateKeys = updatedData.keys();
|
||||
while (updateKeys.hasNext()) {
|
||||
String key = updateKeys.next();
|
||||
if (setClause.length() > 0) {
|
||||
setClause.append(", ");
|
||||
}
|
||||
setClause.append(key).append(" = ?");
|
||||
values.add(updatedData.get(key));
|
||||
}
|
||||
|
||||
// WHERE部分(使用原始数据识别要更新的行)
|
||||
java.util.Iterator<String> originalKeys = originalData.keys();
|
||||
while (originalKeys.hasNext()) {
|
||||
String key = originalKeys.next();
|
||||
if (whereClause.length() > 0) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
whereClause.append(key).append(" = ?");
|
||||
values.add(originalData.get(key));
|
||||
}
|
||||
|
||||
String sql = "UPDATE " + tableName + " SET " + setClause.toString() + " WHERE " + whereClause.toString();
|
||||
|
||||
try (java.sql.PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
pstmt.setObject(i + 1, values.get(i));
|
||||
}
|
||||
|
||||
int affectedRows = pstmt.executeUpdate();
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("affectedRows", affectedRows);
|
||||
resp.put("message", "更新成功");
|
||||
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;
|
||||
}
|
||||
|
||||
case "deleteRow": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
JSONObject rowData = requestJson.optJSONObject("rowData");
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || rowData == null) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建DELETE语句
|
||||
StringBuilder whereClause = new StringBuilder();
|
||||
java.util.List<Object> values = new java.util.ArrayList<>();
|
||||
|
||||
java.util.Iterator<String> keys = rowData.keys();
|
||||
while (keys.hasNext()) {
|
||||
String key = keys.next();
|
||||
if (whereClause.length() > 0) {
|
||||
whereClause.append(" AND ");
|
||||
}
|
||||
whereClause.append(key).append(" = ?");
|
||||
values.add(rowData.get(key));
|
||||
}
|
||||
|
||||
String sql = "DELETE FROM " + tableName + " WHERE " + whereClause.toString();
|
||||
|
||||
try (java.sql.PreparedStatement pstmt = conn.prepareStatement(sql)) {
|
||||
for (int i = 0; i < values.size(); i++) {
|
||||
pstmt.setObject(i + 1, values.get(i));
|
||||
}
|
||||
|
||||
int affectedRows = pstmt.executeUpdate();
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("affectedRows", affectedRows);
|
||||
resp.put("message", "删除成功");
|
||||
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;
|
||||
}
|
||||
|
||||
case "createTable": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
JSONArray columns = requestJson.optJSONArray("columns");
|
||||
JSONObject tableOptions = requestJson.optJSONObject("tableOptions");
|
||||
JSONObject fileSettings = requestJson.optJSONObject("fileSettings");
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || columns == null || columns.length() == 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 构建CREATE TABLE语句
|
||||
StringBuilder sql = new StringBuilder("CREATE TABLE ");
|
||||
sql.append(tableName).append(" (");
|
||||
|
||||
// 添加列定义
|
||||
for (int i = 0; i < columns.length(); i++) {
|
||||
JSONObject column = columns.getJSONObject(i);
|
||||
String colName = column.optString("name", "");
|
||||
String colType = column.optString("type", "VARCHAR(255)");
|
||||
String colAttributes = column.optString("attributes", "");
|
||||
|
||||
if (colName.isEmpty()) {
|
||||
callback.failure(400, new JSONObject().put("status","error").put("message","列名不能为空").toString());
|
||||
return true;
|
||||
}
|
||||
|
||||
if (i > 0) sql.append(", ");
|
||||
sql.append(colName).append(" ").append(colType);
|
||||
|
||||
if (!colAttributes.isEmpty()) {
|
||||
sql.append(" ").append(colAttributes);
|
||||
}
|
||||
}
|
||||
|
||||
sql.append(")");
|
||||
|
||||
// 添加表选项
|
||||
if (tableOptions != null) {
|
||||
String engine = tableOptions.optString("engine", "InnoDB");
|
||||
String charset = tableOptions.optString("charset", "utf8mb4");
|
||||
String collation = tableOptions.optString("collation", "utf8mb4_unicode_ci");
|
||||
String comment = tableOptions.optString("comment", "");
|
||||
|
||||
sql.append(" ENGINE=").append(engine);
|
||||
sql.append(" DEFAULT CHARSET=").append(charset);
|
||||
sql.append(" COLLATE=").append(collation);
|
||||
|
||||
if (!comment.isEmpty()) {
|
||||
sql.append(" COMMENT='").append(comment.replace("'", "''")).append("'");
|
||||
}
|
||||
}
|
||||
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
stmt.executeUpdate(sql.toString());
|
||||
|
||||
// 保存文件设置(如果提供了)
|
||||
if (fileSettings != null) {
|
||||
//saveFileSettings(connectionId, tableName, fileSettings);
|
||||
}
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("message", "表创建成功");
|
||||
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;
|
||||
}
|
||||
|
||||
case "alterTable": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
String newTableName = requestJson.optString("newTableName", "");
|
||||
JSONArray columns = requestJson.optJSONArray("columns");
|
||||
JSONObject tableOptions = requestJson.optJSONObject("tableOptions");
|
||||
JSONObject fileSettings = requestJson.optJSONObject("fileSettings");
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || columns == null || columns.length() == 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
try {
|
||||
// 这里简化处理:在实际应用中,需要更复杂的ALTER TABLE逻辑
|
||||
// 包括检测哪些列需要添加、修改或删除
|
||||
|
||||
// 首先获取当前表结构
|
||||
DatabaseMetaData meta = conn.getMetaData();
|
||||
java.util.List<String> existingColumns = new java.util.ArrayList<>();
|
||||
try (ResultSet rs = meta.getColumns(conn.getCatalog(), null, tableName, null)) {
|
||||
while (rs.next()) {
|
||||
existingColumns.add(rs.getString("COLUMN_NAME"));
|
||||
}
|
||||
}
|
||||
|
||||
// 构建ALTER TABLE语句(简化版)
|
||||
// 注意:实际应用中需要更复杂的逻辑来处理不同的ALTER操作
|
||||
StringBuilder sql = new StringBuilder();
|
||||
|
||||
// 重命名表(如果需要)
|
||||
if (!newTableName.isEmpty() && !newTableName.equals(tableName)) {
|
||||
sql.append("ALTER TABLE ").append(tableName).append(" RENAME TO ").append(newTableName).append("; ");
|
||||
tableName = newTableName; // 更新表名用于后续操作
|
||||
}
|
||||
|
||||
// 这里简化处理:实际应用中需要更复杂的列变更逻辑
|
||||
// 可以添加新列,但不能删除或修改现有列(简化版)
|
||||
for (int i = 0; i < columns.length(); i++) {
|
||||
JSONObject column = columns.getJSONObject(i);
|
||||
String colName = column.optString("name", "");
|
||||
String colType = column.optString("type", "VARCHAR(255)");
|
||||
String colAttributes = column.optString("attributes", "");
|
||||
|
||||
if (colName.isEmpty()) continue;
|
||||
|
||||
if (!existingColumns.contains(colName)) {
|
||||
// 添加新列
|
||||
if (sql.length() > 0 && !sql.toString().endsWith("; ")) {
|
||||
sql.append("; ");
|
||||
}
|
||||
sql.append("ALTER TABLE ").append(tableName).append(" ADD COLUMN ")
|
||||
.append(colName).append(" ").append(colType);
|
||||
|
||||
if (!colAttributes.isEmpty()) {
|
||||
sql.append(" ").append(colAttributes);
|
||||
}
|
||||
}
|
||||
// 注意:在实际应用中,还需要处理修改和删除列的情况
|
||||
}
|
||||
|
||||
// 执行ALTER语句
|
||||
if (sql.length() > 0) {
|
||||
try (Statement stmt = conn.createStatement()) {
|
||||
// 处理多条SQL语句
|
||||
String[] sqlStatements = sql.toString().split(";");
|
||||
for (String sqlStmt : sqlStatements) {
|
||||
if (!sqlStmt.trim().isEmpty()) {
|
||||
stmt.executeUpdate(sqlStmt.trim());
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 更新文件设置(如果提供了)
|
||||
if (fileSettings != null) {
|
||||
//saveFileSettings(connectionId, tableName, fileSettings);
|
||||
}
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("message", "表结构修改成功");
|
||||
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;
|
||||
}
|
||||
/*
|
||||
case "uploadFile": {
|
||||
String connectionId = requestJson.optString("connectionId", "");
|
||||
String tableName = requestJson.optString("tableName", "");
|
||||
String columnName = requestJson.optString("columnName", "");
|
||||
String fileName = requestJson.optString("fileName", "");
|
||||
long fileSize = requestJson.optLong("fileSize", 0);
|
||||
|
||||
if (connectionId.isEmpty() || tableName.isEmpty() || columnName.isEmpty() || fileName.isEmpty()) {
|
||||
callback.failure(400, new JSONObject().put("status","error").put("message","参数不完整").toString());
|
||||
break;
|
||||
}
|
||||
|
||||
// 在实际应用中,这里应该处理文件上传
|
||||
// 由于CEF的限制,文件上传可能需要通过其他方式处理
|
||||
// 这里提供一个模拟实现
|
||||
|
||||
try {
|
||||
// 获取文件存储设置
|
||||
JSONObject fileSettings = getFileSettings(connectionId, tableName);
|
||||
String storageType = fileSettings.optString("storageType", "filesystem");
|
||||
String storagePath = fileSettings.optString("storagePath", "./uploads");
|
||||
long maxFileSize = fileSettings.optLong("maxFileSize", 10 * 1024 * 1024); // 默认10MB
|
||||
JSONArray allowedTypes = fileSettings.optJSONArray("allowedTypes");
|
||||
|
||||
// 检查文件大小
|
||||
if (fileSize > maxFileSize) {
|
||||
callback.failure(400, new JSONObject().put("status","error")
|
||||
.put("message","文件大小超过限制: " + (maxFileSize/1024/1024) + "MB").toString());
|
||||
break;
|
||||
}
|
||||
|
||||
// 检查文件类型
|
||||
if (allowedTypes != null && allowedTypes.length() > 0) {
|
||||
String fileExt = fileName.substring(fileName.lastIndexOf('.') + 1).toLowerCase();
|
||||
boolean allowed = false;
|
||||
|
||||
// 简化文件类型检查
|
||||
for (int i = 0; i < allowedTypes.length(); i++) {
|
||||
String type = allowedTypes.getString(i);
|
||||
if (("image".equals(type) && isImageFile(fileExt)) ||
|
||||
("document".equals(type) && isDocumentFile(fileExt)) ||
|
||||
("audio".equals(type) && isAudioFile(fileExt)) ||
|
||||
("video".equals(type) && isVideoFile(fileExt)) ||
|
||||
("archive".equals(type) && isArchiveFile(fileExt))) {
|
||||
allowed = true;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!allowed) {
|
||||
callback.failure(400, new JSONObject().put("status","error")
|
||||
.put("message","不允许的文件类型").toString());
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
// 生成文件路径
|
||||
String fileId = "file_" + System.currentTimeMillis() + "_" + (int)(Math.random() * 1000);
|
||||
String fileExtension = fileName.substring(fileName.lastIndexOf('.'));
|
||||
String filePath;
|
||||
|
||||
if ("database".equals(storageType)) {
|
||||
// 存储在数据库中 - 返回文件ID
|
||||
filePath = fileId;
|
||||
} else {
|
||||
// 存储在文件系统中
|
||||
java.nio.file.Path uploadDir = java.nio.file.Paths.get(storagePath);
|
||||
try {
|
||||
java.nio.file.Files.createDirectories(uploadDir);
|
||||
} catch (Exception e) {
|
||||
// 忽略创建目录失败
|
||||
}
|
||||
|
||||
filePath = uploadDir.resolve(fileId + fileExtension).toString();
|
||||
|
||||
// 在实际应用中,这里应该保存文件到指定路径
|
||||
// 由于CEF限制,这里只是模拟
|
||||
}
|
||||
|
||||
JSONObject resp = new JSONObject();
|
||||
resp.put("status", "success");
|
||||
resp.put("fileId", fileId);
|
||||
resp.put("path", filePath);
|
||||
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;
|
||||
}
|
||||
*/
|
||||
default: {
|
||||
JSONObject err = new JSONObject();
|
||||
err.put("status", "error");
|
||||
@@ -1263,7 +1747,11 @@ public class MainApplication {
|
||||
// 处理获取表结构
|
||||
private static void handleGetTableStructure(JSONObject request, CefQueryCallback callback) {
|
||||
Connection connection = null;
|
||||
ResultSet resultSet = null;
|
||||
ResultSet rs = null;
|
||||
ResultSet idxRs = null;
|
||||
ResultSet pkRs = null;
|
||||
ResultSet fkRs = null;
|
||||
PreparedStatement ps = null;
|
||||
|
||||
try {
|
||||
String tableName = request.optString("tableName", "");
|
||||
@@ -1287,32 +1775,166 @@ public class MainApplication {
|
||||
String catalog = null;
|
||||
String schema = null;
|
||||
|
||||
switch (info.driver.toLowerCase()) {
|
||||
case "mysql":
|
||||
catalog = info.database;
|
||||
break;
|
||||
case "postgresql":
|
||||
schema = "public";
|
||||
break;
|
||||
if (info != null && info.driver != null) {
|
||||
switch (info.driver.toLowerCase()) {
|
||||
case "mysql":
|
||||
catalog = info.database;
|
||||
break;
|
||||
case "postgresql":
|
||||
schema = "public";
|
||||
break;
|
||||
default:
|
||||
// leave null
|
||||
}
|
||||
}
|
||||
|
||||
resultSet = metaData.getColumns(catalog, schema, tableName, null);
|
||||
|
||||
// 读取列信息
|
||||
rs = metaData.getColumns(catalog, schema, tableName, null);
|
||||
JSONArray columns = new JSONArray();
|
||||
while (resultSet.next()) {
|
||||
while (rs.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"));
|
||||
|
||||
String rawType = rs.getString("TYPE_NAME"); // eg. VARCHAR, INT, DECIMAL
|
||||
int colSize = rs.getInt("COLUMN_SIZE");
|
||||
int decimalDigits = 0;
|
||||
try { decimalDigits = rs.getInt("DECIMAL_DIGITS"); } catch (Exception ignored) {}
|
||||
|
||||
// 生成更友好的类型展示(匹配前端类型选项)
|
||||
String displayType = rawType != null ? rawType : "";
|
||||
if (rawType != null) {
|
||||
String rt = rawType.toUpperCase();
|
||||
if (rt.equals("VARCHAR") || rt.equals("CHAR")) {
|
||||
displayType = rt + "(" + colSize + ")";
|
||||
} else if (rt.equals("DECIMAL") || rt.equals("NUMERIC")) {
|
||||
displayType = "DECIMAL(" + colSize + "," + decimalDigits + ")";
|
||||
} else if (rt.equals("DOUBLE") || rt.equals("FLOAT")) {
|
||||
// 保持为原始类型
|
||||
displayType = rt;
|
||||
} else {
|
||||
// 有时候 TYPE_NAME 已经包含长度(如 VARCHAR(255)),保留原样
|
||||
displayType = rawType;
|
||||
}
|
||||
}
|
||||
|
||||
column.put("name", rs.getString("COLUMN_NAME"));
|
||||
column.put("type", displayType);
|
||||
column.put("rawType", rawType);
|
||||
column.put("size", colSize);
|
||||
column.put("decimalDigits", decimalDigits);
|
||||
int nullableFlag = rs.getInt("NULLABLE");
|
||||
column.put("nullable", nullableFlag == DatabaseMetaData.columnNullable);
|
||||
column.put("defaultValue", rs.getString("COLUMN_DEF"));
|
||||
// 尝试获取是否自增(部分驱动返回 IS_AUTOINCREMENT)
|
||||
try {
|
||||
String isAuto = rs.getString("IS_AUTOINCREMENT");
|
||||
column.put("autoIncrement", "YES".equalsIgnoreCase(isAuto));
|
||||
} catch (Exception ignored) {
|
||||
// ignore
|
||||
}
|
||||
|
||||
columns.put(column);
|
||||
}
|
||||
if (rs != null) { rs.close(); rs = null; }
|
||||
|
||||
// 读取主键(用于在 constraints 中显示)
|
||||
JSONArray constraints = new JSONArray();
|
||||
pkRs = metaData.getPrimaryKeys(catalog, schema, tableName);
|
||||
List<String> pkColumns = new ArrayList<>();
|
||||
while (pkRs != null && pkRs.next()) {
|
||||
String pkName = pkRs.getString("PK_NAME");
|
||||
String pkCol = pkRs.getString("COLUMN_NAME");
|
||||
if (pkCol != null) pkColumns.add(pkCol);
|
||||
}
|
||||
if (!pkColumns.isEmpty()) {
|
||||
JSONObject pkObj = new JSONObject();
|
||||
pkObj.put("name", "PRIMARY");
|
||||
pkObj.put("type", "PRIMARY KEY");
|
||||
pkObj.put("definition", "PRIMARY KEY (" + String.join(",", pkColumns) + ")");
|
||||
constraints.put(pkObj);
|
||||
}
|
||||
if (pkRs != null) { pkRs.close(); pkRs = null; }
|
||||
|
||||
// 读取外键
|
||||
fkRs = metaData.getImportedKeys(catalog, schema, tableName);
|
||||
while (fkRs != null && fkRs.next()) {
|
||||
String fkName = fkRs.getString("FK_NAME");
|
||||
String fkCol = fkRs.getString("FKCOLUMN_NAME");
|
||||
String pkTable = fkRs.getString("PKTABLE_NAME");
|
||||
String pkCol = fkRs.getString("PKCOLUMN_NAME");
|
||||
JSONObject fkObj = new JSONObject();
|
||||
fkObj.put("name", fkName == null ? ("fk_" + fkCol) : fkName);
|
||||
fkObj.put("type", "FOREIGN KEY");
|
||||
fkObj.put("definition", String.format("FOREIGN KEY (%s) REFERENCES %s(%s)", fkCol, pkTable, pkCol));
|
||||
constraints.put(fkObj);
|
||||
}
|
||||
if (fkRs != null) { fkRs.close(); fkRs = null; }
|
||||
|
||||
// 读取索引信息(不重复合并同名索引的列)
|
||||
idxRs = metaData.getIndexInfo(catalog, schema, tableName, false, false);
|
||||
Map<String, JSONObject> idxMap = new LinkedHashMap<>();
|
||||
while (idxRs != null && idxRs.next()) {
|
||||
String idxName = idxRs.getString("INDEX_NAME");
|
||||
if (idxName == null) continue; // driver-specific
|
||||
boolean nonUnique = idxRs.getBoolean("NON_UNIQUE");
|
||||
String colName = idxRs.getString("COLUMN_NAME");
|
||||
if (!idxMap.containsKey(idxName)) {
|
||||
JSONObject idxObj = new JSONObject();
|
||||
idxObj.put("name", idxName);
|
||||
idxObj.put("type", nonUnique ? "INDEX" : "UNIQUE");
|
||||
idxObj.put("columns", colName == null ? "" : colName);
|
||||
idxMap.put(idxName, idxObj);
|
||||
} else {
|
||||
JSONObject idxObj = idxMap.get(idxName);
|
||||
String prevCols = idxObj.optString("columns", "");
|
||||
if (colName != null && !prevCols.contains(colName)) {
|
||||
if (prevCols.isEmpty()) idxObj.put("columns", colName);
|
||||
else idxObj.put("columns", prevCols + "," + colName);
|
||||
}
|
||||
}
|
||||
}
|
||||
JSONArray indexes = new JSONArray();
|
||||
for (JSONObject v : idxMap.values()) indexes.put(v);
|
||||
if (idxRs != null) { idxRs.close(); idxRs = null; }
|
||||
|
||||
// 表级信息(MySQL: ENGINE, COLLATION, COMMENT)
|
||||
String engine = null;
|
||||
String collation = null;
|
||||
String charset = null;
|
||||
String tableComment = null;
|
||||
if (info != null && info.driver != null && "mysql".equalsIgnoreCase(info.driver) && info.database != null) {
|
||||
String sql = "SELECT ENGINE, TABLE_COLLATION, TABLE_COMMENT FROM information_schema.TABLES WHERE TABLE_SCHEMA = ? AND TABLE_NAME = ?";
|
||||
try {
|
||||
ps = connection.prepareStatement(sql);
|
||||
ps.setString(1, info.database);
|
||||
ps.setString(2, tableName);
|
||||
ResultSet tRs = ps.executeQuery();
|
||||
if (tRs.next()) {
|
||||
engine = tRs.getString("ENGINE");
|
||||
collation = tRs.getString("TABLE_COLLATION");
|
||||
tableComment = tRs.getString("TABLE_COMMENT");
|
||||
if (collation != null && collation.contains("_")) {
|
||||
charset = collation.split("_")[0];
|
||||
}
|
||||
}
|
||||
if (tRs != null) { tRs.close(); tRs = null; }
|
||||
} catch (Exception ignored) {
|
||||
// 忽略信息 schema 查询失败的情况(可能权限或 driver 不支持)
|
||||
} finally {
|
||||
if (ps != null) { try { ps.close(); } catch (SQLException ignored) {} ps = null; }
|
||||
}
|
||||
}
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "success");
|
||||
response.put("tableName", tableName);
|
||||
response.put("engine", engine == null ? JSONObject.NULL : engine);
|
||||
response.put("charset", charset == null ? JSONObject.NULL : charset);
|
||||
response.put("collation", collation == null ? JSONObject.NULL : collation);
|
||||
response.put("comment", tableComment == null ? JSONObject.NULL : tableComment);
|
||||
response.put("columns", columns);
|
||||
response.put("indexes", indexes);
|
||||
response.put("constraints", constraints);
|
||||
|
||||
callback.success(response.toString());
|
||||
|
||||
} catch (Exception e) {
|
||||
@@ -1321,29 +1943,33 @@ public class MainApplication {
|
||||
error.put("message", "获取表结构失败: " + e.getMessage());
|
||||
callback.failure(500, error.toString());
|
||||
} finally {
|
||||
try {
|
||||
if (resultSet != null) resultSet.close();
|
||||
} catch (SQLException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
try { if (rs != null) rs.close(); } catch (SQLException ignored) {}
|
||||
try { if (idxRs != null) idxRs.close(); } catch (SQLException ignored) {}
|
||||
try { if (pkRs != null) pkRs.close(); } catch (SQLException ignored) {}
|
||||
try { if (fkRs != null) fkRs.close(); } catch (SQLException ignored) {}
|
||||
try { if (ps != null) ps.close(); } catch (SQLException ignored) {}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
// 处理主题更新(保持不变)
|
||||
// 这里自己实现了一个一般使用事件实现
|
||||
private static void handleUpdateTheme(JSONObject request, CefQueryCallback callback) {
|
||||
try {
|
||||
String theme = request.optString("theme", "light");
|
||||
// 通过 AxisInnovatorsBox 获取当前主题状态
|
||||
boolean isDarkMode = AxisInnovatorsBox.getMain().getRegistrationTopic().isDarkMode();
|
||||
String theme = isDarkMode ? "dark" : "light";
|
||||
|
||||
JSONObject response = new JSONObject();
|
||||
response.put("status", "success");
|
||||
response.put("theme", theme);
|
||||
response.put("message", "主题已更新为: " + 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());
|
||||
error.put("message", "获取主题失败: " + e.getMessage());
|
||||
callback.failure(500, error.toString());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -80,6 +80,9 @@ public class DatabaseConnectionManager {
|
||||
case "mysql":
|
||||
props.setProperty("useSSL", "false");
|
||||
props.setProperty("serverTimezone", "UTC");
|
||||
props.setProperty("allowPublicKeyRetrieval", "true");
|
||||
props.setProperty("useUnicode", "true");
|
||||
props.setProperty("characterEncoding", "UTF-8");
|
||||
connection = DriverManager.getConnection(url, props);
|
||||
break;
|
||||
case "postgresql":
|
||||
|
||||
Reference in New Issue
Block a user