#!/usr/bin/env bash # 一次性试用脚本:本机编译前后端,Docker 仅跑一个一次性 MySQL,数据全部隔离。 # # 特性: # - 前端本机 vite build -> frontend/dist(由后端直接托管) # - 后端本机 go build -> .dev-run/ 下的二进制,本机直接运行 # - MySQL 用独立容器 wmms-dev-mysql + 匿名卷,只绑本机,不影响其他环境 # - 自动探测空闲端口:后端默认从 8080 起,MySQL 从 13306 起 # - 每次都是全新数据库(执行 scripts/init.sql) # - 按 Ctrl+C 停止并删除二进制目录与 MySQL 容器/数据卷 # # 可选环境变量: # APP_PORT 期望的网页端口;被占用时自动顺延(默认 8080) # DB_PORT 期望映射到本机的 MySQL 端口;被占用时顺延(默认 13306) # KEEP_DATA=1 退出时保留 .dev-run/ 和 MySQL 容器(默认全部删除) # NPM_REGISTRY 重装依赖时使用的 npm 镜像源(默认 https://registry.npmmirror.com) set -euo pipefail cd "$(dirname "$0")" RUNDIR="$PWD/.dev-run" DB_CONTAINER="wmms-dev-mysql" DB_PASSWORD="wmms1234" DB_NAME="wmms" # 默认走国内镜像,避免官方源 ECONNRESET;可用 NPM_REGISTRY 覆盖。 NPM_REGISTRY="${NPM_REGISTRY:-https://registry.npmmirror.com}" find_free_port() { local start="$1" p="$1" for ((i = 0; i < 100; i++)); do if ! ss -ltn 2>/dev/null | awk '{print $4}' | grep -Eq "[:.]$p$"; then echo "$p" return fi p=$((p + 1)) done echo "$start" } cleaned=0 PID="" cleanup() { [ "$cleaned" = "1" ] && return cleaned=1 echo echo "==> 停止服务" [ -n "$PID" ] && kill "$PID" 2>/dev/null || true if [ "${KEEP_DATA:-0}" = "1" ]; then echo "==> KEEP_DATA=1,保留 $RUNDIR 与容器 $DB_CONTAINER" else docker rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true rm -rf "$RUNDIR" echo "==> 已删除 MySQL 容器/数据卷与 $RUNDIR" fi } trap cleanup EXIT INT TERM APP_PORT="$(find_free_port "${APP_PORT:-8080}")" DB_PORT="$(find_free_port "${DB_PORT:-13306}")" echo "==> 1/4 构建前端(vite build)" ( cd frontend # 随仓库带来的 node_modules 可能是在 Windows 上安装的:缺 Linux 平台的 # rollup 原生包,且 .bin 丢失可执行位。这种情况下用 npm ci 按 lock 重装。 need_install=0 if [ ! -d node_modules ]; then need_install=1 elif [ ! -d node_modules/@rollup/rollup-linux-x64-gnu ] && [ ! -d node_modules/@rollup/rollup-linux-x64-musl ]; then echo " 检测到 node_modules 非 Linux 平台安装,重新安装依赖..." need_install=1 fi if [ "$need_install" = "1" ]; then npm ci --registry="$NPM_REGISTRY" else chmod +x node_modules/.bin/* 2>/dev/null || true fi npm run build ) echo "==> 2/4 构建后端(go build)" mkdir -p "$RUNDIR" BIN="$RUNDIR/server" go build -o "$BIN" ./cmd/server # 若已有同名残留容器(上次异常退出),先清掉。 docker rm -f "$DB_CONTAINER" >/dev/null 2>&1 || true echo "==> 3/4 启动一次性 MySQL(容器 $DB_CONTAINER,本机端口 $DB_PORT)" docker run -d --name "$DB_CONTAINER" \ -e MYSQL_ROOT_PASSWORD="$DB_PASSWORD" \ -e MYSQL_DATABASE="$DB_NAME" \ -p "127.0.0.1:$DB_PORT:3306" \ -v "$PWD/scripts/init.sql:/docker-entrypoint-initdb.d/001-init.sql:ro" \ mysql:8.0 \ --default-authentication-plugin=mysql_native_password \ --character-set-server=utf8mb4 \ --collation-server=utf8mb4_unicode_ci >/dev/null echo " 等待 MySQL 就绪..." for ((i = 0; i < 90; i++)); do # 必须走 TCP(127.0.0.1:3306),不能用 localhost 的 socket:初始化阶段的 # 临时服务器只监听 socket,socket ping 会提前成功,但 3306 尚未就绪, # 后端连入时正逢重启,报 unexpected EOF / bad connection。 if docker exec "$DB_CONTAINER" mysqladmin ping -h 127.0.0.1 -P 3306 -uroot -p"$DB_PASSWORD" --silent >/dev/null 2>&1; then break fi if ! docker ps --format '{{.Names}}' | grep -qx "$DB_CONTAINER"; then echo "MySQL 容器意外退出,日志:" docker logs "$DB_CONTAINER" 2>&1 | tail -40 || true exit 1 fi sleep 1 done echo "==> 4/4 启动后端(端口 $APP_PORT)" echo " 访问地址: http://localhost:$APP_PORT" DSN="root:$DB_PASSWORD@tcp(127.0.0.1:$DB_PORT)/$DB_NAME?charset=utf8mb4&parseTime=true" APP_PORT="$APP_PORT" DB_DSN="$DSN" "$BIN" & PID=$! READY=0 for ((i = 0; i < 60; i++)); do if curl -fsS "http://localhost:$APP_PORT/api/health" >/dev/null 2>&1; then READY=1 break fi if ! kill -0 "$PID" 2>/dev/null; then echo "后端进程意外退出,请查看上方日志" exit 1 fi sleep 0.5 done if [ "$READY" != "1" ]; then echo "警告:未检测到服务就绪,后端日志:" wait "$PID" || true exit 1 fi echo echo "============================================================" echo " 试用环境已启动(数据一次性,关闭即清除,不影响其他环境)" echo " 访问地址: http://localhost:$APP_PORT" echo " 账号:admin / admin123 user / user123" echo " 按 Ctrl+C 停止并删除 MySQL 容器与临时文件" echo "============================================================" echo wait "$PID"