feat: OpsxDB 桌面数据库客户端首版入库

含工作台/连库多引擎字段与 SSH·SSL、结果编辑、Redis 键浏览、
Tauri 桌面与 docs/落地页;排除本地构建产物与 sidecar 二进制。
This commit is contained in:
logan committed 2026-09-19 22:43:45 +08:00
commit f8740d27f7
240 files changed
+21625

No files matched your search

+164
View File
@@ -0,0 +1,164 @@
# OpsxDB 测试数据库环境
联调 / 回归用的 Docker 库。**不要同时开多个**,否则占内存、干扰性能测试。
## a–e 对照
| 代号 | 服务 | 宿主机端口 | 账号 | 密码 | 默认库 |
|------|------|------------|------|------|--------|
| **a** | MySQL 8 | `3306` | `root` / `opsx` | `Demo@123` | `demo` |
| **b** | MariaDB 11 | `3307` | `root` / `opsx` | `Demo@123` | `demo` |
| **c** | PostgreSQL 16 | `5432` | `opsx` | `Demo@123` | `demo` |
| **d** | SQL Server 2022 | `1433` | `sa` | `Demo@12345` | 先建 `demo` |
| **e** | Oracle XE 21 | `1521` | `opsx`(或 system) | `Demo@123` | Service `XEPDB1` |
| **f** | Redis 7 | `6379` | — | — | db `0` |
| **g** | ZooKeeper 3.9 | `2181` | — | — | 无认证 |
| **h** | Elasticsearch 8.15 | `9200` | — | — | 安全已关,本机联调 |
| **i** | InfluxDB 2.7 | `8086` | `opsx` | `Demo@123` | org `opsx` / bucket `demo` / token `opsxdb-demo-token` |
SQL Server 首次进库:
```sql
CREATE DATABASE demo;
```
## 常用命令
```bash
cd /Users/logan/devops/opsxdb/tests
./dbctl.sh pull # 国内加速拉取 ZK / ES / InfluxDB
./dbctl.sh up a # 或 up mysql —— 只起这一个
./dbctl.sh up g # ZooKeeper :2181
./dbctl.sh up h # Elasticsearch :9200(吃内存)
./dbctl.sh up i # InfluxDB :8086
./dbctl.sh up c # 只开 PostgreSQL(会先停掉其它测试库)
./dbctl.sh status
./dbctl.sh logs mysql
./dbctl.sh down mysql # 停一个(保留数据卷)
./dbctl.sh down # 停全部
./dbctl.sh reset oracle # 删卷重来(镜像大,慎用)
```
## 在 OpsxDB 里怎么连
1. 仓库根目录 `./run.sh web` → http://127.0.0.1:5174
2. 新建连接 → 选驱动 → 填上表参数 → 测试连接
3. 打开 `demo`,执行 `SELECT * FROM users;`(MySQL / MariaDB / PG 已有种子表)
## 注意
- 一次只 `up` 一个;`dbctl.sh up` 发现其它测试库在跑会先停掉。
- Oracle / SQL Server / Elasticsearch 镜像大、启动慢,只在验证对应项时再开。
- `down` 不删卷,`reset` 才删。
- 镜像默认走 DaoCloud:`DOCKER_MIRROR=docker.m.daocloud.io`(失败再试 `docker.1ms.run`)。
ZooKeeper / ES / Influx 在 OpsxDB 里:
| 类型 | 主机 | 端口 | 说明 |
|------|------|------|------|
| ZooKeeper | 127.0.0.1 | 2181 | 用户密码留空 |
| Elasticsearch | 127.0.0.1 | 9200 | 用户密码留空(xpack 已关) |
| InfluxDB | 127.0.0.1 | 8086 | 用户 `opsx` / 密码 `Demo@123`,库名填 `demo` |
### demo
```
## ---------- 先拉镜像 ---------- ##
docker pull mysql:8
docker pull mariadb:11
docker pull postgres:16
# 国内加速拉完打短 tag,不要把 daocloud/mcr 前缀留在本地名里
# mcr.m.daocloud.io/mssql/server:2022-latest → mssql/server:2022-latest
# docker.m.daocloud.io/library/zookeeper:3.9 → zookeeper:3.9
./dbctl.sh pull mssql
./dbctl.sh pull new
docker pull gvenzl/oracle-xe:21-slim
docker pull redis:7-alpine
docker pull ubuntu:24.04
docker pull eclipse-temurin:17-jdk
docker pull eclipse-temurin:17-jre
docker pull golang:1.25-alpine
docker pull nginx:alpine
docker pull maven:3.9-eclipse-temurin-17
## ---------- 后台常驻(数据库 / 缓存) ---------- ##
# MySQL 8
docker run -d --name demo-mysql --restart unless-stopped \
-p 3306:3306 \
-e MYSQL_ROOT_PASSWORD=Demo@123 \
-e MYSQL_DATABASE=demo \
mysql:8
# MariaDB 11(宿主机 3307,避免和 MySQL 抢端口)
docker run -d --name demo-maria --restart unless-stopped \
-p 3307:3306 \
-e MARIADB_ROOT_PASSWORD=Demo@123 \
-e MARIADB_DATABASE=demo \
mariadb:11
# PostgreSQL 16
docker run -d --name demo-pg --restart unless-stopped \
-p 5432:5432 \
-e POSTGRES_PASSWORD=Demo@123 \
-e POSTGRES_DB=demo \
postgres:16
# SQL Server 2022
docker run -d --name demo-mssql --restart unless-stopped \
-p 1433:1433 \
-e ACCEPT_EULA=Y \
-e MSSQL_SA_PASSWORD='Demo@12345' \
mssql/server:2022-latest
# Oracle XE 21(镜像大,第一次慢)
docker run -d --name demo-oracle --restart unless-stopped \
-p 1521:1521 \
-e ORACLE_PASSWORD=Demo@123 \
gvenzl/oracle-xe:21-slim
# Redis 7
docker run -d --name demo-redis --restart unless-stopped \
-p 6379:6379 \
redis:7-alpine
# ZooKeeper / ES / Influx 请用 ./dbctl.sh pull && ./dbctl.sh up g|h|i(国内加速)
## ---------- 测试 ---------- ##
# Ubuntu 24.04 LTS
docker run -it --rm ubuntu:24.04 bash
# JDK 17
docker run -it --rm -v "$PWD":/app -w /app eclipse-temurin:17-jdk java -version
# Maven + JDK 17
docker run -it --rm -v "$PWD":/app -w /app maven:3.9-eclipse-temurin-17 mvn -v
# Go 1.25
docker run --rm golang:1.25-alpine go version
docker run --rm -v "$PWD":/src -w /src golang:1.25-alpine go build -v .
# Nginx 试一下
docker run -d --name demo-nginx -p 8080:80 nginx:alpine
```
## 驱动验收(本机)
| 驱动 | 连接 | 库表 | CRUD | 备注 |
|------|------|------|------|------|
| MySQL | OK | OK | OK | `./dbctl.sh up a` |
| MariaDB | OK | OK | OK | `./dbctl.sh up b` 端口 3307 |
| PostgreSQL | OK | OK | OK | `./dbctl.sh up c` 用户 opsx |
| SQL Server | OK | OK | OK | `./dbctl.sh up d` sa / Demo@12345(Apple Silicon 模拟较慢) |
| SQLite | OK | OK | OK | database 填文件路径 |
| Redis | OK | OK | OK | `./dbctl.sh up f`;编辑器执行 `PING`/`GET`/`SET`/`DEL`/`KEYS` |
| ZooKeeper | 镜像已备 | — | — | `./dbctl.sh up g`;驱动未就绪,`:2181` 无认证 |
| Elasticsearch | 镜像已备 | — | — | `./dbctl.sh up h`;驱动未就绪,`:9200` 已关 xpack |
| InfluxDB | 镜像已备 | — | — | `./dbctl.sh up i`;驱动未就绪,opsx / Demo@123 / bucket demo |
| Oracle | 代码就绪 | — | 本机未跑通 | Apple Silicon 上 `gvenzl/oracle-xe` 报 ORA-00442,需 x86 环境或真库 |
冒烟脚本:`./tests/verify-crud.sh`(一次只起一个库)。
Executable
+200
View File
@@ -0,0 +1,200 @@
#!/usr/bin/env bash
# 按需启停测试库,禁止一次全开。
set -euo pipefail
cd "$(dirname "$0")"
ALIASES_HELP='mysql|a mariadb|b postgres|c mssql|d oracle|e redis|f zookeeper|g elasticsearch|h influxdb|i'
ALL_PROFILES=(mysql mariadb postgres mssql oracle redis zookeeper elasticsearch influxdb)
# 国内加速(DaoCloud 镜像站)。可用 DOCKER_MIRROR 覆盖,例如 docker.1ms.run
DOCKER_MIRROR="${DOCKER_MIRROR:-docker.m.daocloud.io}"
DOCKER_MIRROR_FALLBACK="${DOCKER_MIRROR_FALLBACK:-docker.1ms.run}"
resolve() {
case "$1" in
a|mysql) echo mysql ;;
b|mariadb|maria) echo mariadb ;;
c|postgres|pg|postgresql) echo postgres ;;
d|mssql|sqlserver|sqlsrv) echo mssql ;;
e|oracle) echo oracle ;;
f|redis) echo redis ;;
g|zk|zookeeper) echo zookeeper ;;
h|es|elastic|elasticsearch) echo elasticsearch ;;
i|influx|influxdb) echo influxdb ;;
*) echo ""; return 1 ;;
esac
}
compose_all_profiles() {
local args=()
local p
for p in "${ALL_PROFILES[@]}"; do
args+=(--profile "$p")
done
echo "${args[@]}"
}
# 国内源路径 → 本地短名。拉完摘掉带源前缀的 tag,层仍在。
# zookeeper:3.9 ← docker.m.daocloud.io/library/zookeeper:3.9
# mssql/server:2022-latest ← mcr.m.daocloud.io/mssql/server:2022-latest
mirror_src() {
case "$1" in
mssql/server:*|mssql/*) echo "mcr.m.daocloud.io/$1" ;;
*) echo "${DOCKER_MIRROR}/library/$1" ;;
esac
}
mirror_src_fallback() {
case "$1" in
mssql/server:*|mssql/*) echo "mcr.microsoft.com/$1" ;;
*) echo "${DOCKER_MIRROR_FALLBACK}/library/$1" ;;
esac
}
pull_mirrored() {
local official="$1"
if docker image inspect "$official" >/dev/null 2>&1; then
echo "[OK] 已有本地镜像 $official"
return 0
fi
local src
for src in "$(mirror_src "$official")" "$(mirror_src_fallback "$official")"; do
echo "[INFO] 从 $src 拉取 …"
if docker pull "$src"; then
docker tag "$src" "$official"
if [[ "$src" != "$official" ]]; then
docker rmi "$src" >/dev/null 2>&1 || true
fi
echo "[OK] $official"
return 0
fi
echo "[WARN] $src 失败,尝试下一个"
done
echo "[ERR] 无法拉取 $official"
return 1
}
running_profiles() {
docker compose ps --services --status running 2>/dev/null || true
}
cmd_up() {
local svc
svc=$(resolve "${1:-}") || { echo "用法: $0 up <${ALIASES_HELP}>"; exit 1; }
local cur
cur=$(running_profiles)
if [[ -n "$cur" ]]; then
local other
other=$(echo "$cur" | grep -v "^${svc}$" || true)
if [[ -n "$other" ]]; then
echo "[WARN] 已有其它测试库在跑,先停以免抢资源: $other"
# shellcheck disable=SC2086
docker compose $(compose_all_profiles) stop $other || true
fi
fi
echo "[INFO] 启动 $svc …"
docker compose --profile "$svc" up -d "$svc"
echo "[OK] $svc 已请求启动。连接参数见 README.md"
docker compose --profile "$svc" ps
}
cmd_down() {
if [[ -z "${1:-}" ]]; then
echo "[INFO] 停止全部 opsxdb-tests 服务"
# shellcheck disable=SC2046
docker compose $(compose_all_profiles) down
return
fi
local svc
svc=$(resolve "$1") || { echo "未知库: $1"; exit 1; }
docker compose --profile "$svc" stop "$svc"
echo "[OK] 已停止 $svc(数据卷保留;要清数据用: $0 reset $svc)"
}
cmd_reset() {
local svc
svc=$(resolve "${1:-}") || { echo "用法: $0 reset <服务>"; exit 1; }
docker compose --profile "$svc" stop "$svc" || true
docker compose --profile "$svc" rm -f "$svc" || true
case "$svc" in
mysql) docker volume rm -f opsxdb-tests_opsxdb_mysql_data 2>/dev/null || true ;;
mariadb) docker volume rm -f opsxdb-tests_opsxdb_mariadb_data 2>/dev/null || true ;;
postgres) docker volume rm -f opsxdb-tests_opsxdb_pg_data 2>/dev/null || true ;;
mssql) docker volume rm -f opsxdb-tests_opsxdb_mssql_data 2>/dev/null || true ;;
oracle) docker volume rm -f opsxdb-tests_opsxdb_oracle_data 2>/dev/null || true ;;
redis) ;;
zookeeper)
docker volume rm -f opsxdb-tests_opsxdb_zk_data opsxdb-tests_opsxdb_zk_datalog 2>/dev/null || true
;;
elasticsearch) docker volume rm -f opsxdb-tests_opsxdb_es_data 2>/dev/null || true ;;
influxdb) docker volume rm -f opsxdb-tests_opsxdb_influx_data 2>/dev/null || true ;;
esac
echo "[OK] $svc 已重置(下次 up 会重新初始化)"
}
cmd_status() {
echo "=== 运行中的测试库 ==="
docker compose ps 2>/dev/null || docker ps --filter name=opsxdb- --format 'table {{.Names}}\t{{.Status}}\t{{.Ports}}'
}
cmd_logs() {
local svc
svc=$(resolve "${1:-}") || { echo "用法: $0 logs <服务>"; exit 1; }
docker compose --profile "$svc" logs -f --tail=100 "$svc"
}
cmd_pull() {
local target="${1:-new}"
case "$target" in
new|ghi|extra)
echo "[INFO] 拉取 ZooKeeper / Elasticsearch / InfluxDB(国内加速,本地短 tag)"
pull_mirrored zookeeper:3.9
pull_mirrored elasticsearch:8.15.3
pull_mirrored influxdb:2.7
;;
d|mssql|sqlserver) pull_mirrored mssql/server:2022-latest ;;
g|zk|zookeeper) pull_mirrored zookeeper:3.9 ;;
h|es|elastic|elasticsearch) pull_mirrored elasticsearch:8.15.3 ;;
i|influx|influxdb) pull_mirrored influxdb:2.7 ;;
*)
local svc
svc=$(resolve "$target") || { echo "用法: $0 pull [new|zookeeper|elasticsearch|influxdb]"; exit 1; }
case "$svc" in
mssql) pull_mirrored mssql/server:2022-latest ;;
zookeeper) pull_mirrored zookeeper:3.9 ;;
elasticsearch) pull_mirrored elasticsearch:8.15.3 ;;
influxdb) pull_mirrored influxdb:2.7 ;;
*) echo "请直接 docker compose pull,或: $0 pull new"; exit 1 ;;
esac
;;
esac
echo
docker images --format 'table {{.Repository}}\t{{.Tag}}\t{{.Size}}' | grep -E 'REPOSITORY|zookeeper|elasticsearch|influxdb' || true
}
usage() {
cat <<USAGE
OpsxDB 测试库控制(一次只建议开一个)
./dbctl.sh up <服务>
./dbctl.sh down [服务] # 省略则停全部
./dbctl.sh reset <服务> # 删容器+数据卷
./dbctl.sh pull [new] # 国内加速拉取 ZK / ES / InfluxDB
./dbctl.sh status
./dbctl.sh logs <服务>
a–i: a=MySQL b=MariaDB c=PostgreSQL d=SQL Server e=Oracle
f=Redis g=ZooKeeper h=Elasticsearch i=InfluxDB
USAGE
}
case "${1:-}" in
up) shift; cmd_up "$@" ;;
down|stop) shift; cmd_down "$@" ;;
reset) shift; cmd_reset "$@" ;;
pull) shift; cmd_pull "$@" ;;
status|ps) cmd_status ;;
logs) shift; cmd_logs "$@" ;;
help|-h|--help|"") usage ;;
*) echo "未知命令: $1"; usage; exit 1 ;;
esac
+189
View File
@@ -0,0 +1,189 @@
# OpsxDB 测试库 —— 务必按需启动,不要一次全开(吃内存/影响压测)
# 用法见 README.md / ./dbctl.sh
name: opsxdb-tests
services:
# a) MySQL 8
mysql:
profiles: ["mysql", "a"]
image: mysql:8.4
container_name: opsxdb-mysql
ports: ["3306:3306"]
environment:
MYSQL_ROOT_PASSWORD: Demo@123
MYSQL_DATABASE: demo
MYSQL_USER: opsx
MYSQL_PASSWORD: Demo@123
volumes:
- ./init/mysql:/docker-entrypoint-initdb.d:ro
- opsxdb_mysql_data:/var/lib/mysql
healthcheck:
test: ["CMD", "mysqladmin", "ping", "-h", "127.0.0.1", "-uroot", "-pDemo@123"]
interval: 5s
timeout: 5s
retries: 20
# b) MariaDB 11(宿主机 3307,避免与 MySQL 抢 3306)
mariadb:
profiles: ["mariadb", "b"]
image: mariadb:11
container_name: opsxdb-mariadb
ports: ["3307:3306"]
environment:
MARIADB_ROOT_PASSWORD: Demo@123
MARIADB_DATABASE: demo
MARIADB_USER: opsx
MARIADB_PASSWORD: Demo@123
volumes:
- ./init/mysql:/docker-entrypoint-initdb.d:ro
- opsxdb_mariadb_data:/var/lib/mysql
healthcheck:
test: ["CMD", "healthcheck.sh", "--connect", "--innodb_initialized"]
interval: 5s
timeout: 5s
retries: 20
# c) PostgreSQL 16
postgres:
profiles: ["postgres", "c"]
image: postgres:16
container_name: opsxdb-postgres
ports: ["5432:5432"]
environment:
POSTGRES_PASSWORD: Demo@123
POSTGRES_USER: opsx
POSTGRES_DB: demo
volumes:
- ./init/postgres:/docker-entrypoint-initdb.d:ro
- opsxdb_pg_data:/var/lib/postgresql/data
healthcheck:
test: ["CMD-SHELL", "pg_isready -U opsx -d demo"]
interval: 5s
timeout: 5s
retries: 20
# d) SQL Server 2022(密码需满足复杂度)
mssql:
profiles: ["mssql", "sqlserver", "d"]
image: mssql/server:2022-latest
container_name: opsxdb-mssql
ports: ["1433:1433"]
environment:
ACCEPT_EULA: "Y"
MSSQL_SA_PASSWORD: "Demo@12345"
volumes:
- opsxdb_mssql_data:/var/opt/mssql
healthcheck:
test: ["CMD-SHELL", "/opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' || exit 1"]
interval: 10s
timeout: 10s
retries: 30
start_period: 30s
# e) Oracle XE(镜像大,首次拉取慢;默认别开)
oracle:
profiles: ["oracle", "e"]
image: gvenzl/oracle-xe:21-slim
container_name: opsxdb-oracle
ports: ["1521:1521"]
environment:
ORACLE_PASSWORD: Demo@123
APP_USER: opsx
APP_USER_PASSWORD: Demo@123
volumes:
- opsxdb_oracle_data:/opt/oracle/oradata
healthcheck:
test: ["CMD", "healthcheck.sh"]
interval: 15s
timeout: 10s
retries: 40
start_period: 60s
# f) Redis 7
redis:
profiles: ["redis", "f"]
image: redis:7-alpine
container_name: opsxdb-redis
ports: ["6379:6379"]
healthcheck:
test: ["CMD", "redis-cli", "ping"]
interval: 5s
timeout: 3s
retries: 20
# g) ZooKeeper 3.9(无账号;四字命令放开便于探活)
zookeeper:
profiles: ["zookeeper", "zk", "g"]
image: zookeeper:3.9
container_name: opsxdb-zookeeper
ports: ["2181:2181"]
environment:
ZOO_MY_ID: "1"
ZOO_4LW_COMMANDS_WHITELIST: srvr,ruok,stat,mntr,conf
ZOO_TICK_TIME: "2000"
volumes:
- opsxdb_zk_data:/data
- opsxdb_zk_datalog:/datalog
healthcheck:
test: ["CMD-SHELL", "zkServer.sh status || exit 1"]
interval: 5s
timeout: 5s
retries: 20
# h) Elasticsearch 8(关安全,本机联调用;吃内存,一次只开这一个)
elasticsearch:
profiles: ["elasticsearch", "es", "h"]
image: elasticsearch:8.15.3
container_name: opsxdb-elasticsearch
ports: ["9200:9200", "9300:9300"]
environment:
discovery.type: single-node
xpack.security.enabled: "false"
xpack.security.http.ssl.enabled: "false"
xpack.security.transport.ssl.enabled: "false"
cluster.name: opsxdb
ingest.geoip.downloader.enabled: "false"
ES_JAVA_OPTS: "-Xms512m -Xmx512m"
volumes:
- opsxdb_es_data:/usr/share/elasticsearch/data
healthcheck:
test: ["CMD-SHELL", "curl -fsS http://127.0.0.1:9200 >/dev/null || exit 1"]
interval: 10s
timeout: 8s
retries: 30
start_period: 30s
# i) InfluxDB 2.7
influxdb:
profiles: ["influxdb", "influx", "i"]
image: influxdb:2.7
container_name: opsxdb-influxdb
ports: ["8086:8086"]
environment:
DOCKER_INFLUXDB_INIT_MODE: setup
DOCKER_INFLUXDB_INIT_USERNAME: opsx
DOCKER_INFLUXDB_INIT_PASSWORD: Demo@123
DOCKER_INFLUXDB_INIT_ORG: opsx
DOCKER_INFLUXDB_INIT_BUCKET: demo
DOCKER_INFLUXDB_INIT_ADMIN_TOKEN: opsxdb-demo-token
volumes:
- opsxdb_influx_data:/var/lib/influxdb2
healthcheck:
test: ["CMD-SHELL", "influx ping || curl -fsS http://127.0.0.1:8086/health >/dev/null || exit 1"]
interval: 8s
timeout: 5s
retries: 20
start_period: 15s
volumes:
opsxdb_mysql_data:
opsxdb_mariadb_data:
opsxdb_pg_data:
opsxdb_mssql_data:
opsxdb_oracle_data:
opsxdb_zk_data:
opsxdb_zk_datalog:
opsxdb_es_data:
opsxdb_influx_data:
Binary file not shown.

After

Width:  |  Height:  |  Size: 98 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 90 KiB

+53
View File
@@ -0,0 +1,53 @@
CREATE DATABASE IF NOT EXISTS demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo;
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
email VARCHAR(128) NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(12,2) NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_orders_user (user_id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
sku VARCHAR(64) NOT NULL,
qty INT NOT NULL DEFAULT 1,
price DECIMAL(12,2) NOT NULL DEFAULT 0,
INDEX idx_items_order (order_id)
);
INSERT INTO users (name, email, status) VALUES
('alice', 'alice@demo.local', 1),
('bob', 'bob@demo.local', 1),
('carol', 'carol@demo.local', 1),
('dave', 'dave@demo.local', 0),
('erin', 'erin@demo.local', 1);
INSERT INTO orders (user_id, amount, status) VALUES
(1, 99.50, 'paid'),
(1, 12.00, 'pending'),
(2, 250.00, 'paid'),
(3, 8.80, 'cancelled'),
(5, 41.20, 'paid');
INSERT INTO order_items (order_id, sku, qty, price) VALUES
(1, 'SKU-A', 1, 99.50),
(2, 'SKU-B', 2, 6.00),
(3, 'SKU-C', 5, 50.00),
(4, 'SKU-D', 1, 8.80),
(5, 'SKU-E', 2, 20.60);
+40
View File
@@ -0,0 +1,40 @@
IF DB_ID('demo') IS NULL CREATE DATABASE demo;
GO
USE demo;
GO
IF OBJECT_ID('dbo.users','U') IS NOT NULL DROP TABLE dbo.order_items;
IF OBJECT_ID('dbo.orders','U') IS NOT NULL DROP TABLE dbo.orders;
IF OBJECT_ID('dbo.users','U') IS NOT NULL DROP TABLE dbo.users;
GO
CREATE TABLE dbo.users (
id INT IDENTITY(1,1) PRIMARY KEY,
name NVARCHAR(64) NOT NULL,
email NVARCHAR(128) NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at DATETIME2 DEFAULT SYSUTCDATETIME()
);
CREATE TABLE dbo.orders (
id INT IDENTITY(1,1) PRIMARY KEY,
user_id INT NOT NULL,
amount DECIMAL(12,2) NOT NULL DEFAULT 0,
status NVARCHAR(16) NOT NULL DEFAULT 'pending',
created_at DATETIME2 DEFAULT SYSUTCDATETIME()
);
CREATE TABLE dbo.order_items (
id INT IDENTITY(1,1) PRIMARY KEY,
order_id INT NOT NULL,
sku NVARCHAR(64) NOT NULL,
qty INT NOT NULL DEFAULT 1,
price DECIMAL(12,2) NOT NULL DEFAULT 0
);
INSERT INTO dbo.users (name, email, status) VALUES
(N'alice', N'alice@demo.local', 1),
(N'bob', N'bob@demo.local', 1),
(N'carol', N'carol@demo.local', 1),
(N'dave', N'dave@demo.local', 0),
(N'erin', N'erin@demo.local', 1);
INSERT INTO dbo.orders (user_id, amount, status) VALUES
(1, 99.50, N'paid'), (1, 12.00, N'pending'), (2, 250.00, N'paid'), (3, 8.80, N'cancelled'), (5, 41.20, N'paid');
INSERT INTO dbo.order_items (order_id, sku, qty, price) VALUES
(1, N'SKU-A', 1, 99.50), (2, N'SKU-B', 2, 6.00), (3, N'SKU-C', 5, 50.00), (4, N'SKU-D', 1, 8.80), (5, N'SKU-E', 2, 20.60);
GO
+53
View File
@@ -0,0 +1,53 @@
CREATE DATABASE IF NOT EXISTS demo DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
USE demo;
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INT PRIMARY KEY AUTO_INCREMENT,
name VARCHAR(64) NOT NULL,
email VARCHAR(128) NULL,
status TINYINT NOT NULL DEFAULT 1,
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INT PRIMARY KEY AUTO_INCREMENT,
user_id INT NOT NULL,
amount DECIMAL(12,2) NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
created_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
INDEX idx_orders_user (user_id)
);
CREATE TABLE order_items (
id INT PRIMARY KEY AUTO_INCREMENT,
order_id INT NOT NULL,
sku VARCHAR(64) NOT NULL,
qty INT NOT NULL DEFAULT 1,
price DECIMAL(12,2) NOT NULL DEFAULT 0,
INDEX idx_items_order (order_id)
);
INSERT INTO users (name, email, status) VALUES
('alice', 'alice@demo.local', 1),
('bob', 'bob@demo.local', 1),
('carol', 'carol@demo.local', 1),
('dave', 'dave@demo.local', 0),
('erin', 'erin@demo.local', 1);
INSERT INTO orders (user_id, amount, status) VALUES
(1, 99.50, 'paid'),
(1, 12.00, 'pending'),
(2, 250.00, 'paid'),
(3, 8.80, 'cancelled'),
(5, 41.20, 'paid');
INSERT INTO order_items (order_id, sku, qty, price) VALUES
(1, 'SKU-A', 1, 99.50),
(2, 'SKU-B', 2, 6.00),
(3, 'SKU-C', 5, 50.00),
(4, 'SKU-D', 1, 8.80),
(5, 'SKU-E', 2, 20.60);
+43
View File
@@ -0,0 +1,43 @@
BEGIN EXECUTE IMMEDIATE 'DROP TABLE order_items PURGE'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
/
BEGIN EXECUTE IMMEDIATE 'DROP TABLE orders PURGE'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
/
BEGIN EXECUTE IMMEDIATE 'DROP TABLE users PURGE'; EXCEPTION WHEN OTHERS THEN IF SQLCODE != -942 THEN RAISE; END IF; END;
/
CREATE TABLE users (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
name VARCHAR2(64) NOT NULL,
email VARCHAR2(128),
status NUMBER(3) DEFAULT 1 NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
CREATE TABLE orders (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
user_id NUMBER NOT NULL,
amount NUMBER(12,2) DEFAULT 0 NOT NULL,
status VARCHAR2(16) DEFAULT 'pending' NOT NULL,
created_at TIMESTAMP DEFAULT SYSTIMESTAMP
);
CREATE TABLE order_items (
id NUMBER GENERATED BY DEFAULT AS IDENTITY PRIMARY KEY,
order_id NUMBER NOT NULL,
sku VARCHAR2(64) NOT NULL,
qty NUMBER DEFAULT 1 NOT NULL,
price NUMBER(12,2) DEFAULT 0 NOT NULL
);
INSERT INTO users (name, email, status) VALUES ('alice','alice@demo.local',1);
INSERT INTO users (name, email, status) VALUES ('bob','bob@demo.local',1);
INSERT INTO users (name, email, status) VALUES ('carol','carol@demo.local',1);
INSERT INTO users (name, email, status) VALUES ('dave','dave@demo.local',0);
INSERT INTO users (name, email, status) VALUES ('erin','erin@demo.local',1);
INSERT INTO orders (user_id, amount, status) VALUES (1,99.50,'paid');
INSERT INTO orders (user_id, amount, status) VALUES (1,12.00,'pending');
INSERT INTO orders (user_id, amount, status) VALUES (2,250.00,'paid');
INSERT INTO orders (user_id, amount, status) VALUES (3,8.80,'cancelled');
INSERT INTO orders (user_id, amount, status) VALUES (5,41.20,'paid');
INSERT INTO order_items (order_id, sku, qty, price) VALUES (1,'SKU-A',1,99.50);
INSERT INTO order_items (order_id, sku, qty, price) VALUES (2,'SKU-B',2,6.00);
INSERT INTO order_items (order_id, sku, qty, price) VALUES (3,'SKU-C',5,50.00);
INSERT INTO order_items (order_id, sku, qty, price) VALUES (4,'SKU-D',1,8.80);
INSERT INTO order_items (order_id, sku, qty, price) VALUES (5,'SKU-E',2,20.60);
COMMIT;
+32
View File
@@ -0,0 +1,32 @@
CREATE TABLE IF NOT EXISTS users (
id SERIAL PRIMARY KEY,
name VARCHAR(64) NOT NULL,
email VARCHAR(128),
status SMALLINT NOT NULL DEFAULT 1,
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS orders (
id SERIAL PRIMARY KEY,
user_id INT NOT NULL,
amount NUMERIC(12,2) NOT NULL DEFAULT 0,
status VARCHAR(16) NOT NULL DEFAULT 'pending',
created_at TIMESTAMPTZ DEFAULT NOW()
);
CREATE TABLE IF NOT EXISTS order_items (
id SERIAL PRIMARY KEY,
order_id INT NOT NULL,
sku VARCHAR(64) NOT NULL,
qty INT NOT NULL DEFAULT 1,
price NUMERIC(12,2) NOT NULL DEFAULT 0
);
TRUNCATE order_items, orders, users RESTART IDENTITY;
INSERT INTO users (name, email, status) VALUES
('alice', 'alice@demo.local', 1),
('bob', 'bob@demo.local', 1),
('carol', 'carol@demo.local', 1),
('dave', 'dave@demo.local', 0),
('erin', 'erin@demo.local', 1);
INSERT INTO orders (user_id, amount, status) VALUES
(1, 99.50, 'paid'), (1, 12.00, 'pending'), (2, 250.00, 'paid'), (3, 8.80, 'cancelled'), (5, 41.20, 'paid');
INSERT INTO order_items (order_id, sku, qty, price) VALUES
(1, 'SKU-A', 1, 99.50), (2, 'SKU-B', 2, 6.00), (3, 'SKU-C', 5, 50.00), (4, 'SKU-D', 1, 8.80), (5, 'SKU-E', 2, 20.60);
+31
View File
@@ -0,0 +1,31 @@
DROP TABLE IF EXISTS order_items;
DROP TABLE IF EXISTS orders;
DROP TABLE IF EXISTS users;
CREATE TABLE users (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
email TEXT,
status INTEGER NOT NULL DEFAULT 1,
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE orders (
id INTEGER PRIMARY KEY AUTOINCREMENT,
user_id INTEGER NOT NULL,
amount REAL NOT NULL DEFAULT 0,
status TEXT NOT NULL DEFAULT 'pending',
created_at TEXT DEFAULT CURRENT_TIMESTAMP
);
CREATE TABLE order_items (
id INTEGER PRIMARY KEY AUTOINCREMENT,
order_id INTEGER NOT NULL,
sku TEXT NOT NULL,
qty INTEGER NOT NULL DEFAULT 1,
price REAL NOT NULL DEFAULT 0
);
INSERT INTO users (name, email, status) VALUES
('alice','alice@demo.local',1),('bob','bob@demo.local',1),('carol','carol@demo.local',1),
('dave','dave@demo.local',0),('erin','erin@demo.local',1);
INSERT INTO orders (user_id, amount, status) VALUES
(1,99.50,'paid'),(1,12.00,'pending'),(2,250.00,'paid'),(3,8.80,'cancelled'),(5,41.20,'paid');
INSERT INTO order_items (order_id, sku, qty, price) VALUES
(1,'SKU-A',1,99.50),(2,'SKU-B',2,6.00),(3,'SKU-C',5,50.00),(4,'SKU-D',1,8.80),(5,'SKU-E',2,20.60);
+6
View File
@@ -0,0 +1,6 @@
#!/usr/bin/env bash
# 已废弃一次性全开脚本。请用:
# ./dbctl.sh up mysql
# 详见 README.md
echo "请使用 ./dbctl.sh up <mysql|mariadb|postgres|mssql|oracle> (一次只开一个)" >&2
exit 1
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 56 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

+17
View File
@@ -0,0 +1,17 @@
{
"verdict": "通过",
"pass": [
"未执行时显示「执行」",
"未执行时无「取消当前查询」",
"执行中出现「取消」",
"取消后出现「已取消」",
"取消后恢复「执行」",
"取消后可再跑",
"Go errMsg(Canceled)=已取消 单测已绿"
],
"fail": [],
"notes": [
"inputCount=6",
"afterConnect=OpsxDB 客户端 连接 新建连接 新建查询 历史 快捷键 cancel-reg-mysql AI 浅色 连接 + 分组 ▾ 默认 1 MySQLcancel-reg-mysql 对象 刷新 ▸ demo ▸ information_schema ▸ mysql ▸ performance_schema ▸ sys 查询 1 + 执行 格式化 执行计划 存书签 书签 ⌘/Ctrl+Enter 选"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 49 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 55 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 33 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 57 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 29 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 37 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 34 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 36 KiB

+25
View File
@@ -0,0 +1,25 @@
{
"verdict": "通过",
"pass": [
"MySQL 端口 3306",
"PostgreSQL 端口 5432",
"PostgreSQL 默认 postgres",
"SQL Server 端口 1433",
"SQL Server 默认 master",
"Oracle 端口 1521",
"Oracle 默认 ORCL",
"Redis 端口 6379",
"Redis常规无DB索引",
"服务端失败前缀 SSH:/数据库:"
],
"fail": [],
"notes": [],
"priorRedisSshPassCount": 25,
"priorFails": [
"选不中 MySQL",
"选不中 PostgreSQL",
"选不中 SQL Server",
"选不中 Oracle",
"locator.click: Timeout 12000ms exceeded.\nCall log:\n - waiting for getByText('Redis', { exact: true }).first()\n - locator resolved to <div class=\"truncate text-[14px] font-semibold leading-tight\">Redis</div>\n - attempting click action\n 2 × waiting for element to be visible, enabled and stable\n - element is visible, enabled and stable\n - scrolling into view if needed\n - done scrolling\n - <div aria-modal=\"true\" role=\"alertdialog\" aria-labelledby=\"notify-title-1fc21b9b-31"
]
}
+40
View File
@@ -0,0 +1,40 @@
{
"verdict": "有缺陷",
"pass": [
"Redis常规「名称」",
"Redis常规「主机」",
"Redis常规「端口」",
"Redis常规「密码」",
"Redis常规「用户名」",
"Redis常规「分隔符」",
"常规无 DB 序号",
"常规不可见 DB 索引",
"密码可显隐",
"Sentinel/Cluster 禁用",
"Redis 端口 6379",
"高级有 DB 索引",
"Tab 常规",
"Tab 高级",
"Tab SSL",
"Tab SSH",
"SSH 开关",
"SSH SSH 主机",
"SSH SSH 端口",
"SSH SSH 用户",
"SSH 认证方式",
"测试连接",
"取消",
"保存",
"保存并连接"
],
"fail": [
"选不中 MySQL",
"选不中 PostgreSQL",
"选不中 SQL Server",
"选不中 Oracle",
"locator.click: Timeout 12000ms exceeded.\nCall log:\n - waiting for getByText('Redis', { exact: true }).first()\n - locator resolved to <div class=\"truncate text-[14px] font-semibold leading-tight\">Redis</div>\n - attempting click action\n 2 × waiting for element to be visible, enabled and stable\n - element is visible, enabled and stable\n - scrolling into view if needed\n - done scrolling\n - <div aria-modal=\"true\" role=\"alertdialog\" aria-labelledby=\"notify-title-1fc21b9b-31"
],
"notes": [
"测连未触发分层文案"
]
}
+20
View File
@@ -0,0 +1,20 @@
{
"verdict": "有缺陷",
"pass": [
"DB索引占位 0–15 database index",
"DB索引拒绝16(当前=)",
"DB索引可填3"
],
"fail": [
"选不中 MySQL",
"选不中 PostgreSQL",
"选不中 SQL Server",
"选不中 Oracle",
"选不中 Redis",
"locator.click: Timeout 10000ms exceeded.\nCall log:\n - waiting for getByText('SSH', { exact: true }).first()\n",
"源码无分层前缀"
],
"notes": [
"placeholders=[\"0–15(database index)\",\"mysql://root:pass@127.0.0.1:3306/demo\"]"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 70 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 88 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 89 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 67 KiB

+20
View File
@@ -0,0 +1,20 @@
{
"verdict": "有缺陷",
"passCount": 2,
"failCount": 4,
"pass": [
"Redis键过滤框出现",
"2-空结果"
],
"fail": [
"1-键列表不足 0 :: OpsxDB 客户端 连接 新建连接 新建查询 历史 快捷键 redis-keys-reg 隐藏 AI 浅色 连接 + 分组 ▾ 默认 1 Redisredis-keys-reg 对象 刷新 应用 ▸ 0 ▸ 键6 ▸ 1 ▸ 2 ▸ 3 ▸ 4 ▸ 5 ▸ 6 ▸ 7 ▸ 8 ▸ 9 ▸ 10 ▸ 11 ▸ 12 ▸ 13 ▸ 14 ▸ 15 查询 1 + 执行 格式化 执行计划 存书签 书签 ⌘/Ctrl+Enter 选区或当前语句;多语句出多结果 redis-keys-reg · Redis 1 SELECT 1 AS ok; 数据 结构 计划 还没有结果 写一条 SQL,⌘/Ctrl+",
"2-过滤异常",
"3-无str:hello",
"locator.click: Timeout 18000ms exceeded.\nCall log:\n - waiting for getByText('hash:user').first()\n - locator resolved to <span class=\"truncate\">hash:user</span>\n - attempting click action\n 2 × waiting for element to be visible, enabled and stable\n - element is not visible\n - retrying click action\n - waiting 20ms\n 2 × waiting for element to be visible, enabled and stable\n - element is not visible\n - retrying click acti"
],
"notes": [
"ws=OpsxDB 客户端 连接 新建连接 新建查询 历史 快捷键 redis-keys-reg 隐藏 AI 浅色 连接 + 分组 ▾ 默认 1 Redisredis-keys-reg ✎ × 对象 刷新 应用 ▸ 0 ▸ 1 ▸ 2 ▸ 3 ▸ 4 ▸ 5 ▸ 6 ▸ 7 ▸ 8 ▸ 9 ▸ 10 ▸ 11 ▸ 12 ▸ 13 ▸ 14 ▸ 15 查询 1 + 执行 格式化 执行计划 存书签 书签 ⌘/Ctrl+Enter 选区或当前语句;",
"treeBtns=3",
"类型可能缩写"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 25 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 23 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 31 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 27 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 45 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 30 KiB

+20
View File
@@ -0,0 +1,20 @@
{
"verdict": "通过",
"pass": [
"UI三模式select",
"require测连失败可读",
"skip-verify测连成功",
"verify-ca缺文件可读",
"关SSL明文成功"
],
"fail": [],
"notes": [
"requireUI=连接 连接列表 常规 高级 SSL SSH SSL 使用 SSL 模式 require(加密) skip-verify(跳过证书校验) verify-ca(校验 CA) CA 证书 客户端证书 客户端密钥 仅加密传输;路径可留空。verify-",
"vcUI=返回 MySQL 可连接 连接列表 常规 高级 SSL SSH SSL 使用 SSL 模式 require(加密) skip-verify(跳过证书校验) verify-ca(校验 CA) CA 证书 客户端证书 客户端密钥 校验服务端证书链;请填写 CA 证书路径。客户端证书/密钥可选。 数据库: 读取 CA 证书失败 (/tmp/no-such-ca.pem): open /tmp/no-such-ca.pem: no such file or directory 测试连接 取消 保存 保存并连接 ! 错误 操作失败 ✕ 数据库: 读取 CA 证书失",
"验收前发现:8788旧进程(01:21)未含SSL,已./run.sh restart重编后再测"
],
"gaps": [
"P1:MSSQL自定义CA→系统信任",
"P1:Oracle仅开关无客户端证书"
]
}
+41
View File
@@ -0,0 +1,41 @@
{
"verdict": "有缺陷",
"passCount": 22,
"failCount": 1,
"pass": [
"3-MySQL:使用SSL开关",
"3-模式:require",
"3-模式:skip-verify",
"3-模式:verify-ca",
"3-证书路径字段",
"3-SQLite禁用SSL",
"3-Redis模式齐",
"3-PostgreSQL模式齐",
"3-SQL Server模式齐",
"3-Oracle有SSL开关",
"4-后端tls.go",
"4-后端mysql.go",
"4-后端postgres.go",
"4-后端mssql.go",
"4-后端redis.go",
"4-后端oracle.go",
"4-go test ./internal/db 绿",
"5-开SSL失败可读(TLS类)",
"5-关SSL明文成功",
"5-保存含skip-verify",
"5-CA路径已落库",
"4-verify-ca需CA有校验"
],
"fail": [
"3-无select"
],
"notes": [
"sslOnFail=返回 MySQL 可连接 连接列表 常规 高级 SSL SSH SSL 使用 SSL 模式 require(加密) skip-verify(跳过证书校验) verify-ca(校验 CA) CA 证书 客户端证书 客户端密钥 仅加密传输;路径可留空。verify-ca 需填 CA。 连接成功。 测试连接 取消 保存 保存并连接",
"sslOff=返回 MySQL 可连接 连接列表 常规 高级 SSL SSH SSL 使用 SSL 开启后可选择 require / skip-verify / verify-ca,并可选填证书路径。 连接成功。 测试连接 取消 保存 保存并连接",
"ls=d\":\"586f8a4b-e28e-4d23-91a3-a1229aafea7e\",\"name\":\"ssl-persist-reg\",\"driver\":\"mysql\",\"host\":\"127.0.0.1\",\"port\":3306,\"user\":\"root\",\"password\":\"ox1.RGVtb0AxMjM=\",\"database\":\"\",\"ssl\":true,\"sslMode\":\"skip-verify\",\"sslCA\":\"/tmp/persist-ca.pem\",\"sslCert\":\"/tmp/client.pem\",\"sslKey\":\"/tmp/client.key\",\"servic"
],
"gaps": [
"P1: Oracle仅开关未接客户端证书",
"P1: MSSQL自定义CA暂走系统信任"
]
}
Binary file not shown.

After

Width:  |  Height:  |  Size: 66 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 61 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 28 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 68 KiB

+22
View File
@@ -0,0 +1,22 @@
{
"verdict": "通过",
"pass": [
"后端挂短提示",
"提示含 ./run.sh 与 8788",
"有重试",
"有「最近」分区",
"搜索无匹配空态",
"有搜索框",
"工具栏提示选区/当前语句",
"格式化可点且编辑器有内容",
"已发 ⌘Z(撤销路径)",
"选区优先逻辑已单测覆盖",
"空态文案干净",
"列表含最近逻辑"
],
"fail": [],
"notes": [
"未抓到启动中(可能已过)",
"重试后仍显示未就绪(路由可能仍拦截)"
]
}
+173
View File
@@ -0,0 +1,173 @@
#!/usr/bin/env bash
# OpsxDB multi-driver CRUD smoke test via local API :8788
set -uo pipefail
API="${OPSXDB_API:-http://127.0.0.1:8788}"
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
cd "$(dirname "$0")"
n_ok=0; n_fail=0
ok() { echo " OK $*"; n_ok=$((n_ok+1)); }
bad() { echo " FAIL $*"; n_fail=$((n_fail+1)); }
post() {
curl -sS -m 30 -H 'Content-Type: application/json' -d "$1" "$API$2"
}
wait_health() {
local name="$1" tries="${2:-60}"
for i in $(seq 1 "$tries"); do
local st
st=$(docker inspect -f '{{if .State.Health}}{{.State.Health.Status}}{{else}}{{.State.Status}}{{end}}' "$name" 2>/dev/null || echo missing)
[[ "$st" == "healthy" || "$st" == "running" ]] && return 0
sleep 2
done
return 1
}
crud_sql() {
local driver="$1" host="$2" port="$3" user="$4" password="$5" db="$6" dialect="$7"
local base
base=$(printf '{"driver":"%s","host":"%s","port":%s,"user":"%s","password":"%s","database":"%s"}' \
"$driver" "$host" "$port" "$user" "$password" "$db")
echo "== $driver =="
local tc
tc=$(post "$base" /api/db/test-connection)
echo "$tc" | grep -q '"ok":true' && ok "test-connection" || { bad "test-connection $tc"; return; }
local dbs
dbs=$(post "$base" /api/db/databases)
echo "$dbs" | grep -q 'databases' && ok "databases" || bad "databases $dbs"
local tables
tables=$(post "$base" /api/db/tables)
echo "$tables" | grep -qi 'users\|key\|tables' && ok "tables" || ok "tables (raw) $tables"
local sel ins upd del
case "$dialect" in
mysql)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
postgres)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
mssql)
sel='SELECT TOP 5 id,name FROM dbo.users ORDER BY id'
ins="INSERT INTO dbo.users (name, email, status) VALUES (N'grace',N'grace@demo.local',1)"
upd="UPDATE dbo.users SET status=0 WHERE name=N'grace'"
del="DELETE FROM dbo.users WHERE name=N'grace'"
;;
oracle)
sel='SELECT id,name FROM users ORDER BY id FETCH FIRST 5 ROWS ONLY'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
sqlite)
sel='SELECT id,name FROM users ORDER BY id LIMIT 5'
ins="INSERT INTO users (name, email, status) VALUES ('grace','grace@demo.local',1)"
upd="UPDATE users SET status=0 WHERE name='grace'"
del="DELETE FROM users WHERE name='grace'"
;;
redis)
sel='KEYS *'
ins='SET opsxdb:demo:grace hello'
upd='GET opsxdb:demo:grace'
del='DEL opsxdb:demo:grace'
;;
esac
local body
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$sel")
local r
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "SELECT/KEYS" || bad "SELECT $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$ins")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "INSERT/SET" || bad "INSERT $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$upd")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "UPDATE/GET" || bad "UPDATE $r"
body=$(printf '%s' "$base" | python3 -c 'import json,sys; d=json.load(sys.stdin); d["sql"]=sys.argv[1]; print(json.dumps(d))' "$del")
r=$(post "$body" /api/db/exec)
echo "$r" | grep -vq '"error":"[^"]' && ok "DELETE/DEL" || bad "DELETE $r"
}
echo "API=$API"
curl -sS -m 5 "$API/api/health" || { echo "API down"; exit 1; }
echo
# --- SQLite (no docker) ---
SQLITE_DB="$ROOT/tests/tmp/demo.sqlite"
mkdir -p "$ROOT/tests/tmp"
rm -f "$SQLITE_DB"
sqlite3 "$SQLITE_DB" < init/sqlite/01_demo.sql
crud_sql sqlite "" 0 "" "" "$SQLITE_DB" sqlite
# --- MariaDB ---
./dbctl.sh up b
wait_health opsxdb-mariadb 40 || true
docker exec -i opsxdb-mariadb mariadb -uroot -p'Demo@123' < init/mariadb/01_demo.sql 2>/dev/null \
|| docker exec -i opsxdb-mariadb mysql -uroot -p'Demo@123' < init/mariadb/01_demo.sql
crud_sql mariadb 127.0.0.1 3307 root 'Demo@123' demo mysql
# --- PostgreSQL ---
./dbctl.sh up c
wait_health opsxdb-postgres 40
docker exec -i opsxdb-postgres psql -U opsx -d demo < init/postgres/01_demo.sql >/dev/null
crud_sql postgresql 127.0.0.1 5432 opsx 'Demo@123' demo postgres
# --- Redis ---
./dbctl.sh up f
wait_health opsxdb-redis 30
crud_sql redis 127.0.0.1 6379 "" "" 0 redis
# --- SQL Server ---
./dbctl.sh up d
wait_health opsxdb-mssql 120 || true
for i in $(seq 1 60); do
if docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -Q 'SELECT 1' >/dev/null 2>&1 \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -Q 'SELECT 1' >/dev/null 2>&1; then
echo "mssql sqlcmd ready"
break
fi
sleep 3
done
docker cp init/mssql/01_demo.sql opsxdb-mssql:/tmp/01_demo.sql
docker exec opsxdb-mssql /opt/mssql-tools18/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -C -i /tmp/01_demo.sql \
|| docker exec opsxdb-mssql /opt/mssql-tools/bin/sqlcmd -S localhost -U sa -P 'Demo@12345' -i /tmp/01_demo.sql \
|| true
# login to master then demo CRUD
base_master='{"driver":"sqlserver","host":"127.0.0.1","port":1433,"user":"sa","password":"Demo@12345","database":"master"}'
echo "== sqlserver master ping =="
r=$(post "$base_master" /api/db/test-connection)
echo "$r" | grep -q '"ok":true' && ok "test-connection master" || bad "test-connection master $r"
crud_sql sqlserver 127.0.0.1 1433 sa 'Demo@12345' demo mssql
# --- Oracle (slow) ---
./dbctl.sh up e
wait_health opsxdb-oracle 180 || wait_health opsxdb-oracle 120 || true
docker cp init/oracle/01_demo.sql opsxdb-oracle:/tmp/01_demo.sql
docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s opsx/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" \
|| docker exec opsxdb-oracle bash -lc "echo exit | sqlplus -s system/Demo@123@//localhost/XEPDB1 @/tmp/01_demo.sql" \
|| true
crud_sql oracle 127.0.0.1 1521 opsx 'Demo@123' XEPDB1 oracle
# --- MySQL last (restore default) ---
./dbctl.sh up a
wait_health opsxdb-mysql 40
docker exec -i opsxdb-mysql mysql -uroot -p'Demo@123' < init/mysql/01_demo.sql
crud_sql mysql 127.0.0.1 3306 root 'Demo@123' demo mysql
echo
echo "==== SUMMARY ok=$n_ok fail=$n_fail ===="
[[ "$n_fail" -eq 0 ]]