From f0e1c7e72c9a26d971b1fd8855a9a548c652b37d Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sat, 14 Feb 2026 15:01:29 +0800 Subject: [PATCH 1/3] =?UTF-8?q?=F0=9F=94=A7=20fix(driver-agent):=20?= =?UTF-8?q?=E4=BF=AE=E5=A4=8D=20Windows=20=E5=90=AF=E5=8A=A8=E9=A9=B1?= =?UTF-8?q?=E5=8A=A8=E4=BB=A3=E7=90=86=E5=BC=B9=E5=87=BA=E7=BB=88=E7=AB=AF?= =?UTF-8?q?=E7=AA=97=E5=8F=A3?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 为 Windows 新增 agent 进程启动参数(HideWindow + CREATE_NO_WINDOW) - optional driver agent 启动路径统一应用进程隐藏配置 - MySQL agent 启动路径同步应用进程隐藏配置 --- internal/db/agent_process_stub.go | 9 +++++++++ internal/db/agent_process_windows.go | 20 ++++++++++++++++++++ internal/db/mysql_agent_impl.go | 1 + internal/db/optional_driver_agent_impl.go | 1 + 4 files changed, 31 insertions(+) create mode 100644 internal/db/agent_process_stub.go create mode 100644 internal/db/agent_process_windows.go diff --git a/internal/db/agent_process_stub.go b/internal/db/agent_process_stub.go new file mode 100644 index 0000000..0b0e3b2 --- /dev/null +++ b/internal/db/agent_process_stub.go @@ -0,0 +1,9 @@ +//go:build !windows + +package db + +import "os/exec" + +func configureAgentProcess(cmd *exec.Cmd) { + _ = cmd +} diff --git a/internal/db/agent_process_windows.go b/internal/db/agent_process_windows.go new file mode 100644 index 0000000..c268037 --- /dev/null +++ b/internal/db/agent_process_windows.go @@ -0,0 +1,20 @@ +//go:build windows + +package db + +import ( + "os/exec" + "syscall" +) + +const windowsCreateNoWindow = 0x08000000 + +func configureAgentProcess(cmd *exec.Cmd) { + if cmd == nil { + return + } + cmd.SysProcAttr = &syscall.SysProcAttr{ + HideWindow: true, + CreationFlags: windowsCreateNoWindow, + } +} diff --git a/internal/db/mysql_agent_impl.go b/internal/db/mysql_agent_impl.go index c5b53f6..b3ffe19 100644 --- a/internal/db/mysql_agent_impl.go +++ b/internal/db/mysql_agent_impl.go @@ -72,6 +72,7 @@ func newMySQLAgentClient(executablePath string) (*mysqlAgentClient, error) { } cmd := exec.Command(pathText) + configureAgentProcess(cmd) stdin, err := cmd.StdinPipe() if err != nil { return nil, fmt.Errorf("创建 MySQL 驱动代理 stdin 失败:%w", err) diff --git a/internal/db/optional_driver_agent_impl.go b/internal/db/optional_driver_agent_impl.go index 18f88ce..c61bd70 100644 --- a/internal/db/optional_driver_agent_impl.go +++ b/internal/db/optional_driver_agent_impl.go @@ -73,6 +73,7 @@ func newOptionalDriverAgentClient(driverType string, executablePath string) (*op } cmd := exec.Command(pathText) + configureAgentProcess(cmd) stdin, err := cmd.StdinPipe() if err != nil { return nil, fmt.Errorf("创建 %s 驱动代理 stdin 失败:%w", driverDisplayName(driverType), err) From 1ba68fcbfe6c46369eab1558301369d22b06f0ed Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sat, 14 Feb 2026 15:17:03 +0800 Subject: [PATCH 2/3] =?UTF-8?q?=F0=9F=90=9B=20fix(release):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=20Debian=2013=20Linux=20=E4=BA=A7=E7=89=A9=20WebKitGT?= =?UTF-8?q?K=20=E4=BE=9D=E8=B5=96=E4=B8=8D=E5=85=BC=E5=AE=B9?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Linux Release 增加 WebKitGTK 4.1 变体(-WebKit41),保留 4.0 产物 - CI 按 WebKit 版本安装依赖,并为 Wails 注入 webkit2_41 构建标签 - 完善驱动代理可执行文件路径校验错误提示(区分不存在/目录) - README 补充 Linux 依赖排障与产物选择说明 - refs #98 --- .github/workflows/release.yml | 60 ++++++++++++++++++++--- README.md | 22 +++++++++ internal/db/mysql_agent_impl.go | 6 ++- internal/db/optional_driver_agent_impl.go | 6 ++- 4 files changed, 85 insertions(+), 9 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 1180a62..01ae239 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -22,26 +22,56 @@ jobs: os_name: MacOS arch_name: Amd64 build_name: gonavi-build-darwin-amd64 + wails_tags: "" + artifact_suffix: "" + build_optional_agents: true + linux_webkit: "" - os: macos-latest platform: darwin/arm64 os_name: MacOS arch_name: Arm64 build_name: gonavi-build-darwin-arm64 + wails_tags: "" + artifact_suffix: "" + build_optional_agents: true + linux_webkit: "" - os: windows-latest platform: windows/amd64 os_name: Windows arch_name: Amd64 build_name: gonavi-build-windows-amd64 + wails_tags: "" + artifact_suffix: "" + build_optional_agents: true + linux_webkit: "" - os: windows-latest platform: windows/arm64 os_name: Windows arch_name: Arm64 build_name: gonavi-build-windows-arm64 + wails_tags: "" + artifact_suffix: "" + build_optional_agents: true + linux_webkit: "" - os: ubuntu-22.04 platform: linux/amd64 os_name: Linux arch_name: Amd64 build_name: gonavi-build-linux-amd64 + wails_tags: "" + artifact_suffix: "" + build_optional_agents: true + linux_webkit: "4.0" + # Debian 13 (trixie) 默认仓库已切到 WebKitGTK 4.1:单独提供 4.1 变体产物 + - os: ubuntu-24.04 + platform: linux/amd64 + os_name: Linux + arch_name: Amd64 + build_name: gonavi-build-linux-amd64-webkit41 + wails_tags: "webkit2_41" + artifact_suffix: "-WebKit41" + build_optional_agents: false + linux_webkit: "4.1" steps: - name: Checkout code @@ -63,7 +93,17 @@ jobs: if: contains(matrix.platform, 'linux') run: | sudo apt-get update - sudo apt-get install -y libgtk-3-dev libwebkit2gtk-4.0-dev libfuse2 + sudo apt-get install -y libgtk-3-dev + + # WebKitGTK 4.1 需要 libsoup3;4.0 使用 libsoup2(通常由 webkit2gtk dev 包拉起) + if [ "${{ matrix.linux_webkit }}" = "4.1" ]; then + sudo apt-get install -y libwebkit2gtk-4.1-dev libsoup-3.0-dev + else + sudo apt-get install -y libwebkit2gtk-4.0-dev + fi + + # AppImage 运行/打包可能需要 FUSE2。不同发行版/版本包名不同,做兼容兜底。 + sudo apt-get install -y libfuse2 || sudo apt-get install -y libfuse2t64 || true # Download linuxdeploy tools for AppImage packaging LINUXDEPLOY_URL="https://github.com/linuxdeploy/linuxdeploy/releases/download/continuous/linuxdeploy-x86_64.AppImage" @@ -94,9 +134,15 @@ jobs: - name: Build shell: bash run: | - wails build -platform ${{ matrix.platform }} -clean -o ${{ matrix.build_name }} -ldflags "-s -w -X GoNavi-Wails/internal/app.AppVersion=${{ github.ref_name }}" + set -euo pipefail + TAG_ARGS=() + if [ -n "${{ matrix.wails_tags }}" ]; then + TAG_ARGS+=(-tags "${{ matrix.wails_tags }}") + fi + wails build -platform ${{ matrix.platform }} -clean -o ${{ matrix.build_name }} "${TAG_ARGS[@]}" -ldflags "-s -w -X GoNavi-Wails/internal/app.AppVersion=${{ github.ref_name }}" - name: Build Optional Driver Agents + if: ${{ matrix.build_optional_agents }} shell: bash run: | set -euo pipefail @@ -160,7 +206,7 @@ jobs: codesign --force --options runtime --deep --sign - "$APP_NAME" DMG_NAME="${{ matrix.build_name }}.dmg" - FINAL_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}.dmg" + FINAL_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.dmg" echo "📦 正在生成 DMG: $DMG_NAME..." create-dmg \ @@ -187,8 +233,8 @@ jobs: $version = $version.Substring(1) } $target = "${{ matrix.build_name }}" - $finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}.exe" - $finalZipName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}.zip" + $finalExeName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.exe" + $finalZipName = "GoNavi-$version-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.zip" if (Test-Path "$target.exe") { $finalExe = "$target.exe" @@ -214,8 +260,8 @@ jobs: VERSION="${VERSION#v}" cd build/bin TARGET="${{ matrix.build_name }}" - TAR_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}.tar.gz" - APPIMAGE_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}.AppImage" + TAR_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.tar.gz" + APPIMAGE_NAME="GoNavi-$VERSION-${{ matrix.os_name }}-${{ matrix.arch_name }}${{ matrix.artifact_suffix }}.AppImage" if [ ! -f "$TARGET" ]; then echo "❌ 未找到构建产物 '$TARGET'!" diff --git a/README.md b/README.md index 2596349..d773de9 100644 --- a/README.md +++ b/README.md @@ -129,6 +129,7 @@ wails build -clean 支持构建: * macOS (AMD64 / ARM64) * Windows (AMD64) +* Linux (AMD64,提供 WebKitGTK 4.0 与 4.1 变体产物) --- @@ -146,6 +147,27 @@ wails build -clean ``` 4. 或者:在 Finder 中右键点击应用图标,按住 `Control` 键选择 **打开**,然后在弹出的窗口中再次点击 **打开**。 +### Linux 启动报错缺少 `libwebkit2gtk` / `libjavascriptcoregtk` + +GoNavi 的 Linux 二进制依赖系统 WebKitGTK 运行库。不同发行版默认版本不同: + +- Debian 13 / Ubuntu 24.04 及更新版本:通常为 WebKitGTK 4.1 +- Ubuntu 22.04 / Debian 12 等:通常为 WebKitGTK 4.0 + +如果启动时报错(如 `libwebkit2gtk-4.0.so.37: cannot open shared object file`),请按系统安装对应依赖后重试: + +```bash +# Debian 13 / Ubuntu 24.04+ +sudo apt-get update +sudo apt-get install -y libgtk-3-0 libwebkit2gtk-4.1-0 libjavascriptcoregtk-4.1-0 + +# Ubuntu 22.04 / Debian 12 +sudo apt-get update +sudo apt-get install -y libgtk-3-0 libwebkit2gtk-4.0-37 libjavascriptcoregtk-4.0-18 +``` + +如果你使用的是 Release 中带 `-WebKit41` 后缀的 Linux 产物,请优先在 Debian 13 / Ubuntu 24.04+ 上使用;普通 Linux 产物更适合 WebKitGTK 4.0 运行环境。 + --- ## 🤝 贡献指南 diff --git a/internal/db/mysql_agent_impl.go b/internal/db/mysql_agent_impl.go index b3ffe19..3ad540d 100644 --- a/internal/db/mysql_agent_impl.go +++ b/internal/db/mysql_agent_impl.go @@ -67,9 +67,13 @@ func newMySQLAgentClient(executablePath string) (*mysqlAgentClient, error) { if pathText == "" { return nil, fmt.Errorf("MySQL 驱动代理路径为空") } - if info, err := os.Stat(pathText); err != nil || info.IsDir() { + info, err := os.Stat(pathText) + if err != nil { return nil, fmt.Errorf("MySQL 驱动代理不存在:%s", pathText) } + if info.IsDir() { + return nil, fmt.Errorf("MySQL 驱动代理路径是目录:%s", pathText) + } cmd := exec.Command(pathText) configureAgentProcess(cmd) diff --git a/internal/db/optional_driver_agent_impl.go b/internal/db/optional_driver_agent_impl.go index c61bd70..cde793a 100644 --- a/internal/db/optional_driver_agent_impl.go +++ b/internal/db/optional_driver_agent_impl.go @@ -68,9 +68,13 @@ func newOptionalDriverAgentClient(driverType string, executablePath string) (*op if pathText == "" { return nil, fmt.Errorf("%s 驱动代理路径为空", driverDisplayName(driverType)) } - if info, err := os.Stat(pathText); err != nil || info.IsDir() { + info, err := os.Stat(pathText) + if err != nil { return nil, fmt.Errorf("%s 驱动代理不存在:%s", driverDisplayName(driverType), pathText) } + if info.IsDir() { + return nil, fmt.Errorf("%s 驱动代理路径是目录:%s", driverDisplayName(driverType), pathText) + } cmd := exec.Command(pathText) configureAgentProcess(cmd) From fda30539b68556034854c1594d5915531dbb422d Mon Sep 17 00:00:00 2001 From: Syngnat Date: Sat, 14 Feb 2026 15:45:02 +0800 Subject: [PATCH 3/3] =?UTF-8?q?=20=F0=9F=90=9B=20fix(highgo):=20=E4=BF=AE?= =?UTF-8?q?=E5=A4=8D=E6=B5=B7=E9=87=8F=E6=95=B0=E6=8D=AE=E6=BA=90=E5=A4=8D?= =?UTF-8?q?=E5=88=B6=E8=A1=A8=E7=BB=93=E6=9E=84=E4=BB=85=E8=BF=94=E5=9B=9E?= =?UTF-8?q?=E6=B3=A8=E9=87=8A?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 识别 HighGo 占位建表语句 - 通过 GetColumns 生成包含字段与主键的建表SQL - 避免右键复制表结构出现空字段 - refs #99 --- internal/app/methods_db.go | 111 +++++++++++++++++++++++++++++++++++++ 1 file changed, 111 insertions(+) diff --git a/internal/app/methods_db.go b/internal/app/methods_db.go index 5d8b382..47587f6 100644 --- a/internal/app/methods_db.go +++ b/internal/app/methods_db.go @@ -465,6 +465,7 @@ func (a *App) DBGetTables(config connection.ConnectionConfig, dbName string) con func (a *App) DBShowCreateTable(config connection.ConnectionConfig, dbName string, tableName string) connection.QueryResult { runConfig := normalizeRunConfig(config, dbName) + dbType := resolveDDLDBType(config) dbInst, err := a.getDatabase(runConfig) if err != nil { @@ -478,10 +479,120 @@ func (a *App) DBShowCreateTable(config connection.ConnectionConfig, dbName strin logger.Error(err, "DBShowCreateTable 获取建表语句失败:%s 表=%s", formatConnSummary(runConfig), tableName) return connection.QueryResult{Success: false, Message: err.Error()} } + if shouldFallbackCreateStatement(dbType, sqlStr) { + columns, colErr := dbInst.GetColumns(schemaName, pureTableName) + if colErr != nil { + logger.Error(colErr, "DBShowCreateTable 兜底加载字段失败:%s 表=%s", formatConnSummary(runConfig), tableName) + return connection.QueryResult{Success: false, Message: colErr.Error()} + } + fallbackDDL, buildErr := buildFallbackCreateStatement(dbType, schemaName, pureTableName, columns) + if buildErr != nil { + logger.Error(buildErr, "DBShowCreateTable 兜底生成 DDL 失败:%s 表=%s", formatConnSummary(runConfig), tableName) + return connection.QueryResult{Success: false, Message: buildErr.Error()} + } + sqlStr = fallbackDDL + } return connection.QueryResult{Success: true, Data: sqlStr} } +func shouldFallbackCreateStatement(dbType string, ddl string) bool { + switch dbType { + case "postgres", "kingbase", "highgo", "vastbase": + default: + return false + } + + trimmed := strings.TrimSpace(ddl) + if trimmed == "" { + return true + } + if hasCreateTableHead(trimmed) { + return false + } + + lower := strings.ToLower(trimmed) + if strings.Contains(lower, "not fully supported") || + strings.Contains(lower, "not directly supported") || + strings.Contains(lower, "not supported") { + return true + } + return true +} + +func hasCreateTableHead(sqlText string) bool { + lines := strings.Split(sqlText, "\n") + for _, line := range lines { + line = strings.TrimSpace(line) + if line == "" { + continue + } + if strings.HasPrefix(line, "--") || strings.HasPrefix(line, "/*") || strings.HasPrefix(line, "*") { + continue + } + return strings.HasPrefix(strings.ToLower(line), "create table") + } + return false +} + +func buildFallbackCreateStatement(dbType string, schemaName string, tableName string, columns []connection.ColumnDefinition) (string, error) { + table := strings.TrimSpace(tableName) + if table == "" { + return "", fmt.Errorf("表名不能为空") + } + if len(columns) == 0 { + return "", fmt.Errorf("未获取到字段定义,无法生成建表语句") + } + + qualifiedTable := quoteTableIdentByType(dbType, schemaName, table) + columnLines := make([]string, 0, len(columns)+1) + primaryKeys := make([]string, 0, 2) + + for _, col := range columns { + colNameRaw := strings.TrimSpace(col.Name) + if colNameRaw == "" { + continue + } + colType := strings.TrimSpace(col.Type) + if colType == "" { + colType = "text" + } + + colName := quoteIdentByType(dbType, colNameRaw) + defParts := []string{fmt.Sprintf("%s %s", colName, colType)} + + if strings.EqualFold(strings.TrimSpace(col.Nullable), "NO") { + defParts = append(defParts, "NOT NULL") + } + if col.Default != nil { + defVal := strings.TrimSpace(*col.Default) + if defVal != "" { + defParts = append(defParts, "DEFAULT "+defVal) + } + } + + columnLines = append(columnLines, " "+strings.Join(defParts, " ")) + if strings.EqualFold(strings.TrimSpace(col.Key), "PRI") { + primaryKeys = append(primaryKeys, colName) + } + } + + if len(columnLines) == 0 { + return "", fmt.Errorf("字段定义为空,无法生成建表语句") + } + if len(primaryKeys) > 0 { + columnLines = append(columnLines, " PRIMARY KEY ("+strings.Join(primaryKeys, ", ")+")") + } + + ddl := strings.Builder{} + ddl.WriteString("CREATE TABLE ") + ddl.WriteString(qualifiedTable) + ddl.WriteString(" (\n") + ddl.WriteString(strings.Join(columnLines, ",\n")) + ddl.WriteString("\n);") + return ddl.String(), nil +} + func (a *App) DBGetColumns(config connection.ConnectionConfig, dbName string, tableName string) connection.QueryResult { runConfig := normalizeRunConfig(config, dbName)