init
continuous-integration/drone/push Build is failing

This commit is contained in:
曾志威
2026-09-18 17:08:54 +08:00
parent 0776148d02
commit c12915bc25
5 changed files with 113 additions and 9 deletions
+2 -1
View File
@@ -19,6 +19,7 @@ steps:
commands:
- mkdir -p dist
- go build -trimpath -ldflags="-s -w" -o dist/syncdb ./cmd/syncdb
- GOOS=windows GOARCH=amd64 go build -trimpath -ldflags="-s -w" -o dist/syncdb.exe ./cmd/syncdb
- name: docker
image: plugins/docker
@@ -30,4 +31,4 @@ steps:
from_secret: registry_password
when:
event:
- tag
- tag
+5 -2
View File
@@ -1,4 +1,4 @@
.PHONY: test build docker
.PHONY: test build build-windows docker
test:
go test ./...
@@ -7,4 +7,7 @@ build:
go build -trimpath -ldflags="-s -w" -o dist/syncdb ./cmd/syncdb
docker:
docker build -t syncdb:dev .
docker build -t syncdb:dev .
build-windows:
GOOS=windows GOARCH=amd64 CGO_ENABLED=0 go build -trimpath -ldflags="-s -w" -o dist/syncdb.exe ./cmd/syncdb
+43 -3
View File
@@ -6,8 +6,48 @@ syncdb 使用 Go 提供 REST API、同步引擎和 Web 服务,前端构建产
详细设计见 [docs/design.md](docs/design.md),交互原型见 [docs/prototype.html](docs/prototype.html)。
```bash
syncdb --config config.yaml
## Windows 启动
开发阶段可直接双击项目根目录的 `build-and-run.cmd`,或在 PowerShell 中运行:
```powershell
.\build-and-run.cmd
```
Drone CI 负责前端构建、Go 测试、跨平台编译、制品打包和 Docker 镜像发布。
脚本需要 Go 1.23 或更高版本,自动切换到项目目录,将包含前端资源的程序编译到 `dist/syncdb.exe`,编译成功后立即启动并自动打开浏览器。编译或启动失败会保留窗口显示错误;不会在编译失败时启动旧程序。重新打包前请先停止已运行的实例,脚本不会自动终止其他进程。环境变量 `SYNCDB_ADDR`、`SYNCDB_OPEN_BROWSER` 会原样继承。
双击 `syncdb.exe` 即可启动服务,并自动使用默认浏览器打开 `http://127.0.0.1:8080/`。保留控制台窗口以维持服务运行;按 `Ctrl+C` 可停止服务。
在项目根目录使用 PowerShell 构建:
```powershell
go build -trimpath -ldflags="-s -w" -o dist/syncdb.exe ./cmd/syncdb
```
生成后双击 `dist/syncdb.exe`。前端已嵌入程序,不需要单独启动前端服务。
可通过环境变量 `SYNCDB_ADDR` 修改监听地址(默认 `:8080`);浏览器会打开实际监听端口。设置 `SYNCDB_OPEN_BROWSER=false` 可禁用自动打开浏览器。自动打开仅在 Windows 下生效,端口监听失败时不会打开浏览器;浏览器启动失败不影响服务,可手动访问控制台打印的地址。
Linux/macOS 可运行 `./syncdb`,然后手动打开浏览器。Linux/macOS 构建环境也可使用 `make build-windows` 生成 Windows amd64 可执行文件。
## Web 控制台
浏览器控制台直接调用同源 API,无需 Node.js、前端构建或外部 CDN。支持中文、英文、日文切换及窄屏布局。
- **同步概览**:当前数据源、任务、执行中记录和导出数量,以及最近执行记录。
- **数据源**:添加 MySQL / MongoDB 元信息,搜索列表并调用连接测试接口。
- **同步任务**:选择源与目标、同步模式、冲突策略和表 / 集合;查看详情并确认登记运行。
- **执行记录**:查看状态、行数、起止时间及包含错误信息的原始记录详情。
- **SQL 导出**:选择 MySQL 数据源登记导出;只有后端返回文件后才显示下载入口。
- **Binlog / SQL 审计**:展示当前接口状态和审计记录;尚未配置的功能明确提示不可用。
- **复制账号权限**:查看和复制后端提供的 SQL 模板,不在页面执行 SQL。局域网 HTTP 环境若禁止剪贴板访问,可手动选择复制。
列表支持搜索和手动刷新,表单提交期间防止重复点击,失败时保留输入并提示重试。
### 当前开发限制
配置和记录仅保存在内存中,重启后清空。数据库适配器、同步执行器、SQL 导出引擎与 Binlog 读取器尚未实现:连接测试返回未配置,登记运行仅创建 `RUNNING` 记录,导出仅创建 `PENDING` 记录,不会实际连接数据库、同步数据或生成文件。界面不会将这些操作显示为实际执行成功,也不提供后端尚不支持的编辑、删除或取消操作。
默认监听 `:8080`,允许局域网访问;开发服务未提供身份认证,请仅在可信网络使用,不要向公网暴露。
Drone CI 负责 Go 检查、Linux / Windows 编译和 Docker 镜像发布,静态页面通过 `go:embed` 随程序发布。
+25 -2
View File
@@ -7,10 +7,13 @@ import (
"fmt"
"io/fs"
"log"
"net"
"net/http"
"os"
"os/exec"
"os/signal"
"path/filepath"
"runtime"
"strings"
"sync"
"syscall"
@@ -89,14 +92,34 @@ func main() {
mux.HandleFunc("GET /api/replications/accounts", s.accountSQL)
mux.Handle("/", http.FileServer(http.FS(mustSub(webFiles, "web"))))
httpServer := &http.Server{Addr: env("SYNCDB_ADDR", ":8080"), Handler: logging(mux), ReadHeaderTimeout: 10 * time.Second}
listener, err := net.Listen("tcp", httpServer.Addr)
if err != nil {
log.Fatal(err)
}
host, port, err := net.SplitHostPort(listener.Addr().String())
if err != nil {
log.Fatal(err)
}
if address := net.ParseIP(host); address != nil && address.IsUnspecified() {
host = "127.0.0.1"
}
browserURL := "http://" + net.JoinHostPort(host, port) + "/"
stop := make(chan os.Signal, 1)
signal.Notify(stop, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(stop)
go func() {
log.Printf("syncdb listening on %s", httpServer.Addr)
if err := httpServer.ListenAndServe(); err != nil && err != http.ErrServerClosed {
log.Printf("syncdb listening on %s", browserURL)
if err := httpServer.Serve(listener); err != nil && err != http.ErrServerClosed {
log.Fatal(err)
}
}()
if runtime.GOOS == "windows" && env("SYNCDB_OPEN_BROWSER", "true") != "false" {
go func() {
if err := exec.Command("rundll32.exe", "url.dll,FileProtocolHandler", browserURL).Run(); err != nil {
log.Printf("could not open browser: %v; open %s manually", err, browserURL)
}
}()
}
<-stop
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
defer cancel()
+38 -1
View File
@@ -1 +1,38 @@
<!doctype html><html lang="zh-CN"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>syncdb</title><style>body{font:16px system-ui;margin:0;background:#f5f7fb;color:#172033}main{max-width:900px;margin:80px auto;padding:32px;background:#fff;border-radius:16px;box-shadow:0 12px 34px #263b6812}h1{color:#3867ff}code{background:#edf1ff;padding:3px 6px;border-radius:4px}</style></head><body><main><h1>syncdb</h1><p>MySQL / MongoDB 数据同步服务</p><p>Go Web 服务已启动,前端资源已嵌入单个二进制文件。</p><p>健康检查:<a href="/api/health"><code>/api/health</code></a></p></main></body></html>
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>syncdb</title>
<link rel="stylesheet" href="/style.css">
<script src="/app.js" defer></script>
</head>
<body>
<div class="app">
<aside class="sidebar">
<a class="brand" href="#overview"><span class="brand-icon">⇄</span>syncdb<span class="dev">DEV</span></a>
<div class="sidebar-caption" data-i18n="workspace"></div>
<nav id="navigation"></nav>
<div class="sidebar-footer"><span class="status-dot"></span><span data-i18n="singleBinary"></span></div>
</aside>
<main>
<header class="topbar">
<div><div class="eyebrow" data-i18n="workspace"></div><h1 id="page-title"></h1></div>
<div class="toolbar">
<span id="health" class="badge"></span>
<select id="language" aria-label="Language"><option value="zh">简体中文</option><option value="en">English</option><option value="ja">日本語</option></select>
<button id="refresh" type="button" data-i18n="refresh"></button>
</div>
</header>
<div class="notice" data-i18n="devNotice"></div>
<div id="load-error" class="notice error" role="alert" hidden></div>
<div class="section-toolbar"><p id="subtitle"></p><div id="page-actions"></div></div>
<label class="search"><span data-i18n="search"></span><input id="search" type="search" autocomplete="off"></label>
<div id="content" aria-live="polite"></div>
<footer><span>syncdb</span><span id="updated"></span></footer>
</main>
</div>
<dialog id="dialog" aria-labelledby="dialog-title"><form id="editor"><header><h2 id="dialog-title"></h2><button type="button" id="close-dialog" data-i18n="close"></button></header><div id="fields" class="form-grid"></div><p id="form-error" class="form-error" role="alert"></p><div class="dialog-actions"><button type="button" id="cancel-dialog" data-i18n="cancel"></button><button type="submit" class="primary" id="save" data-i18n="create"></button></div></form></dialog>
<div id="toast" role="status" hidden></div>
</body>
</html>