7eb5e984c2
- auth: Login 简化为管理员始终通过 .env 验证,GetProfile 修正 admin DB 查询 - devtools: .sh/.bat 同步重写为完整 CLI (start/stop/status/logs/build/db:*) - docs: 新增 devtools.md,重写 Deploy.md (三种方式+Windows说明),更新 README/gateway-api - voice-service: DashScope 实时流式 STT 支持 - gateway: Phase 6 多模型配置 + 多端客户端管理 + WebSocket 增强 Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
468 lines
13 KiB
Bash
468 lines
13 KiB
Bash
#!/bin/bash
|
|
# ========================================
|
|
# Cyrene DevTools 启动脚本
|
|
# 管理开发环境: 数据库 / 服务编译 / DevTools 控制台
|
|
# ========================================
|
|
set -e
|
|
|
|
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
|
DEVTOOLS_DIR="$SCRIPT_DIR/devtools"
|
|
ROOT="$SCRIPT_DIR"
|
|
PORT="${DEVTOOLS_PORT:-9090}"
|
|
LOG_DIR="$DEVTOOLS_DIR/logs"
|
|
LOG_FILE="$LOG_DIR/sh.log"
|
|
|
|
# 颜色
|
|
RED='\033[0;31m'
|
|
GREEN='\033[0;32m'
|
|
YELLOW='\033[1;33m'
|
|
CYAN='\033[0;36m'
|
|
BOLD='\033[1m'
|
|
NC='\033[0m'
|
|
|
|
# ========== 平台检测 ==========
|
|
IS_WIN=false
|
|
case "$(uname -s)" in
|
|
MINGW*|MSYS*|CYGWIN*) IS_WIN=true ;;
|
|
esac
|
|
|
|
# ========== 帮助 ==========
|
|
show_help() {
|
|
echo -e "${CYAN}Cyrene DevTools${NC} — 开发环境管理工具"
|
|
echo ""
|
|
echo -e "${BOLD}用法:${NC} ./devtools.sh [命令] [选项]"
|
|
echo ""
|
|
echo -e "${BOLD}命令:${NC}"
|
|
echo " (无参数) 启动 DevTools 控制台 (默认)"
|
|
echo " start 启动 DevTools 控制台"
|
|
echo " start --fresh 强制重启全部后端服务后启动"
|
|
echo " start --build 编译全部服务后启动"
|
|
echo " stop 停止 DevTools 控制台"
|
|
echo " status 查看所有服务状态"
|
|
echo " logs [服务ID] 查看服务日志 (默认显示最近 20 行)"
|
|
echo " build [服务ID] 编译服务 (不指定则编译全部)"
|
|
echo " db:start 启动数据库容器 (Docker Compose)"
|
|
echo " db:stop 停止数据库容器"
|
|
echo " db:status 检查数据库连接状态"
|
|
echo " help 显示此帮助"
|
|
echo ""
|
|
echo -e "${BOLD}选项:${NC}"
|
|
echo " --port, -p <端口> 指定 DevTools 端口 (默认: 9090)"
|
|
echo " --fresh 启动前强制重启全部后端服务"
|
|
echo " --build 启动前编译全部服务"
|
|
echo ""
|
|
echo -e "${BOLD}示例:${NC}"
|
|
echo " ./devtools.sh # 快速启动"
|
|
echo " ./devtools.sh start --build # 编译后启动"
|
|
echo " ./devtools.sh start --fresh # 全新重启"
|
|
echo " ./devtools.sh logs gateway # 查看 Gateway 日志"
|
|
echo " ./devtools.sh build ai-core # 仅编译 AI-Core"
|
|
echo " ./devtools.sh db:status # 检查数据库"
|
|
echo ""
|
|
echo -e "${BOLD}Web 控制台:${NC} http://localhost:$PORT"
|
|
}
|
|
|
|
# ========== 依赖检查 ==========
|
|
check_deps() {
|
|
local missing=0
|
|
|
|
# Node.js
|
|
if ! command -v node &>/dev/null; then
|
|
if [ -x /usr/local/node/bin/node ]; then
|
|
export PATH="/usr/local/node/bin:$PATH"
|
|
else
|
|
echo -e "${RED}✗ Node.js 未安装${NC}"
|
|
missing=1
|
|
fi
|
|
fi
|
|
|
|
# Docker (用于数据库)
|
|
if ! command -v docker &>/dev/null; then
|
|
echo -e "${YELLOW}⚠ Docker 未安装 (数据库容器功能不可用)${NC}"
|
|
fi
|
|
|
|
# Go (用于编译后端服务)
|
|
if ! command -v go &>/dev/null; then
|
|
if $IS_WIN && [ -f "/c/Program Files/Go/bin/go.exe" ]; then
|
|
export PATH="$PATH:/c/Program Files/Go/bin"
|
|
else
|
|
echo -e "${YELLOW}⚠ Go 未安装 (服务编译功能不可用)${NC}"
|
|
fi
|
|
fi
|
|
|
|
return $missing
|
|
}
|
|
|
|
# ========== 加载 .env ==========
|
|
load_env() {
|
|
local env_file="$ROOT/backend/.env"
|
|
if [ -f "$env_file" ]; then
|
|
echo -e "${GREEN}✓ 加载环境变量: backend/.env${NC}"
|
|
set -a
|
|
source "$env_file"
|
|
set +a
|
|
else
|
|
echo -e "${YELLOW}⚠ 未找到 backend/.env,使用默认值${NC}"
|
|
fi
|
|
}
|
|
|
|
# ========== 端口工具 (跨平台) ==========
|
|
port_in_use() {
|
|
local port=$1
|
|
if $IS_WIN; then
|
|
netstat -ano 2>/dev/null | grep -q ":$port " | grep -q "LISTENING"
|
|
else
|
|
ss -tlnp 2>/dev/null | grep -q ":$port " || netstat -tlnp 2>/dev/null | grep -q ":$port "
|
|
fi
|
|
}
|
|
|
|
kill_port() {
|
|
local port=$1
|
|
if $IS_WIN; then
|
|
local pid=$(netstat -ano 2>/dev/null | grep ":$port " | grep "LISTENING" | awk '{print $NF}' | head -1)
|
|
if [ -n "$pid" ] && [ "$pid" != "0" ]; then
|
|
powershell -Command "Stop-Process -Id $pid -Force" 2>/dev/null || true
|
|
fi
|
|
else
|
|
fuser -k "$port/tcp" 2>/dev/null || true
|
|
fi
|
|
sleep 1
|
|
}
|
|
|
|
# ========== 健康检查 ==========
|
|
health_check() {
|
|
local url=$1
|
|
local max_wait=${2:-30}
|
|
local waited=0
|
|
while [ $waited -lt $max_wait ]; do
|
|
if curl -s -o /dev/null -w "%{http_code}" "$url" 2>/dev/null | grep -q "200"; then
|
|
return 0
|
|
fi
|
|
sleep 1
|
|
waited=$((waited + 1))
|
|
done
|
|
return 1
|
|
}
|
|
|
|
# ========== 数据库管理 ==========
|
|
DB_COMPOSE_FILE="$ROOT/docker-compose.dev.db.yml"
|
|
|
|
db_status() {
|
|
if port_in_use 5432; then
|
|
echo -e "${GREEN}✓ PostgreSQL 在线 (端口 5432)${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${RED}✗ PostgreSQL 离线${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
db_start() {
|
|
echo -e "${CYAN}启动数据库容器...${NC}"
|
|
if [ ! -f "$DB_COMPOSE_FILE" ]; then
|
|
echo -e "${RED}✗ 未找到 $DB_COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
docker compose -f "$DB_COMPOSE_FILE" up -d
|
|
echo -e "${YELLOW}等待数据库就绪...${NC}"
|
|
if health_check "http://localhost:5432" 30; then
|
|
echo -e "${GREEN}✓ 数据库已就绪${NC}"
|
|
else
|
|
echo -e "${YELLOW}⚠ 数据库可能仍在启动中,请稍后检查${NC}"
|
|
fi
|
|
}
|
|
|
|
db_stop() {
|
|
echo -e "${CYAN}停止数据库容器...${NC}"
|
|
if [ ! -f "$DB_COMPOSE_FILE" ]; then
|
|
echo -e "${RED}✗ 未找到 $DB_COMPOSE_FILE${NC}"
|
|
return 1
|
|
fi
|
|
docker compose -f "$DB_COMPOSE_FILE" down
|
|
echo -e "${GREEN}✓ 数据库已停止${NC}"
|
|
}
|
|
|
|
# ========== 服务编译 ==========
|
|
SERVICES=(
|
|
"memory-service:backend/memory-service"
|
|
"tool-engine:backend/tool-engine"
|
|
"iot-debug-service:backend/iot-debug-service"
|
|
"voice-service:backend/voice-service"
|
|
"ai-core:backend/ai-core"
|
|
"plugin-manager:backend/plugin-manager"
|
|
"platform-bridge:backend/platform-bridge"
|
|
"gateway:backend/gateway"
|
|
)
|
|
|
|
build_service() {
|
|
local id=$1
|
|
local dir=$2
|
|
local label=${3:-$id}
|
|
|
|
echo -e "${CYAN}[编译] $label...${NC}"
|
|
local binary="main"
|
|
$IS_WIN && binary="main.exe"
|
|
|
|
cd "$ROOT/$dir"
|
|
if GOWORK=off go build -o "$binary" ./cmd/main.go 2>&1; then
|
|
echo -e "${GREEN} ✓ $label 编译完成${NC}"
|
|
return 0
|
|
else
|
|
echo -e "${RED} ✗ $label 编译失败${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
build_all() {
|
|
echo -e "${BOLD}编译全部后端服务...${NC}"
|
|
local failed=0
|
|
for entry in "${SERVICES[@]}"; do
|
|
IFS=':' read -r id dir <<< "$entry"
|
|
# 检查目录是否存在
|
|
if [ -d "$ROOT/$dir" ]; then
|
|
build_service "$id" "$dir" "$id" || failed=$((failed + 1))
|
|
fi
|
|
done
|
|
|
|
if [ $failed -eq 0 ]; then
|
|
echo -e "${GREEN}✓ 全部编译完成${NC}"
|
|
else
|
|
echo -e "${YELLOW}⚠ $failed 个服务编译失败${NC}"
|
|
fi
|
|
cd "$ROOT"
|
|
return $failed
|
|
}
|
|
|
|
# ========== 查看日志 ==========
|
|
show_logs() {
|
|
local service_id=$1
|
|
local lines=${2:-20}
|
|
local log_path="$LOG_DIR/${service_id}.log"
|
|
|
|
if [ ! -f "$log_path" ]; then
|
|
echo -e "${YELLOW}日志文件不存在: $log_path${NC}"
|
|
return 1
|
|
fi
|
|
|
|
echo -e "${BOLD}=== $service_id 日志 (最近 $lines 行) ===${NC}"
|
|
tail -n "$lines" "$log_path"
|
|
}
|
|
|
|
# ========== 启动 DevTools ==========
|
|
start_devtools() {
|
|
local do_build=false
|
|
local do_fresh=false
|
|
|
|
# 解析参数
|
|
for arg in "$@"; do
|
|
case $arg in
|
|
--build) do_build=true ;;
|
|
--fresh) do_fresh=true ;;
|
|
--port=*) PORT="${arg#*=}" ;;
|
|
-p) shift; PORT="$1" ;;
|
|
esac
|
|
done
|
|
|
|
echo ""
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${CYAN} Cyrene DevTools${NC}"
|
|
echo -e "${CYAN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
|
|
# 依赖检查
|
|
check_deps || exit 1
|
|
|
|
echo -e "${YELLOW}Node.js:${NC} $(node --version)"
|
|
command -v go &>/dev/null && echo -e "${YELLOW}Go:${NC} $(go version | cut -d' ' -f3)"
|
|
command -v docker &>/dev/null && echo -e "${YELLOW}Docker:${NC} $(docker --version | cut -d' ' -f3 | tr -d ',')"
|
|
|
|
# 加载环境变量
|
|
load_env
|
|
|
|
# 编译 (如果指定)
|
|
if $do_build; then
|
|
build_all
|
|
fi
|
|
|
|
# 全新重启 (如果指定)
|
|
if $do_fresh; then
|
|
echo -e "${YELLOW}强制重启全部后端服务...${NC}"
|
|
curl -s -X POST "http://localhost:$PORT/api/services/start-all-fresh" 2>/dev/null || true
|
|
fi
|
|
|
|
# 检查并释放端口
|
|
if port_in_use "$PORT"; then
|
|
# 检查是否是已有 DevTools 实例
|
|
if curl -s -o /dev/null "http://localhost:$PORT/api/health" 2>/dev/null; then
|
|
echo -e "${GREEN}✓ DevTools 已在运行: http://localhost:$PORT${NC}"
|
|
echo -e "${CYAN} API: http://localhost:$PORT/api/health${NC}"
|
|
return 0
|
|
fi
|
|
echo -e "${YELLOW}⚠ 端口 $PORT 被占用,正在释放...${NC}"
|
|
kill_port "$PORT"
|
|
fi
|
|
|
|
# 切换到 devtools 目录
|
|
cd "$DEVTOOLS_DIR"
|
|
|
|
# 安装依赖
|
|
if [ ! -d "node_modules" ] || [ ! -f "node_modules/.package-lock.json" ]; then
|
|
echo -e "${YELLOW}安装依赖...${NC}"
|
|
npm install --silent
|
|
fi
|
|
|
|
# 确保日志目录存在
|
|
mkdir -p "$LOG_DIR"
|
|
|
|
echo ""
|
|
echo -e "${GREEN}启动 DevTools 服务器 (端口: $PORT)...${NC}"
|
|
echo -e "${CYAN} Web 控制台: http://localhost:$PORT${NC}"
|
|
echo -e "${CYAN} API: http://localhost:$PORT/api/health${NC}"
|
|
echo -e "${CYAN} WebSocket: ws://localhost:$PORT/ws${NC}"
|
|
echo ""
|
|
echo -e "${YELLOW}正在后台启动...${NC}"
|
|
|
|
# 后台启动 DevTools
|
|
nohup node src/index.js > "$LOG_FILE" 2>&1 &
|
|
local pid=$!
|
|
cd "$ROOT"
|
|
|
|
# 等待健康检查
|
|
echo -e "${YELLOW}等待服务就绪...${NC}"
|
|
if health_check "http://localhost:$PORT/api/health" 30; then
|
|
echo ""
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo -e "${GREEN} DevTools 已启动!${NC}"
|
|
echo -e "${GREEN} PID: ${pid}${NC}"
|
|
echo -e "${GREEN} 控制台: http://localhost:$PORT${NC}"
|
|
echo -e "${GREEN} 日志: ${LOG_FILE}${NC}"
|
|
echo -e "${GREEN}━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━${NC}"
|
|
echo ""
|
|
|
|
# 检查数据库状态
|
|
db_status
|
|
|
|
return 0
|
|
fi
|
|
|
|
# 超时
|
|
if kill -0 "$pid" 2>/dev/null; then
|
|
echo ""
|
|
echo -e "${YELLOW}⚠ 服务可能仍在启动中 (已等待 30 秒)${NC}"
|
|
echo -e "${CYAN} 请稍后检查 http://localhost:$PORT/api/health${NC}"
|
|
else
|
|
echo ""
|
|
echo -e "${RED}✗ 服务启动失败,请检查日志: ${LOG_FILE}${NC}"
|
|
return 1
|
|
fi
|
|
}
|
|
|
|
# ========== 停止 DevTools ==========
|
|
stop_devtools() {
|
|
if port_in_use "$PORT"; then
|
|
echo -e "${CYAN}停止 DevTools...${NC}"
|
|
if $IS_WIN; then
|
|
kill_port "$PORT"
|
|
else
|
|
fuser -k "$PORT/tcp" 2>/dev/null || true
|
|
fi
|
|
echo -e "${GREEN}✓ DevTools 已停止${NC}"
|
|
else
|
|
echo -e "${YELLOW}DevTools 未在运行${NC}"
|
|
fi
|
|
}
|
|
|
|
# ========== 查看状态 ==========
|
|
show_status() {
|
|
if curl -s -o /dev/null "http://localhost:$PORT/api/health" 2>/dev/null; then
|
|
echo -e "${GREEN}✓ DevTools 在线${NC}"
|
|
|
|
# 获取服务状态
|
|
local status_json=$(curl -s "http://localhost:$PORT/api/services" 2>/dev/null)
|
|
if [ -n "$status_json" ]; then
|
|
echo ""
|
|
echo -e "${BOLD}服务状态:${NC}"
|
|
local tmpfile="$(mktemp)"
|
|
echo "$status_json" > "$tmpfile"
|
|
node -e "
|
|
const fs = require('fs');
|
|
const data = JSON.parse(fs.readFileSync(process.argv[1], 'utf-8'));
|
|
for (const [id, svc] of Object.entries(data)) {
|
|
const icon = svc.status === 'running' ? '✓' : svc.status === 'stopped' ? '✗' : '○';
|
|
const color = svc.status === 'running' ? '\x1b[32m' : '\x1b[31m';
|
|
const pid = svc.pid ? ' (PID: ' + svc.pid + ')' : '';
|
|
const uptime = svc.uptime ? ' | uptime: ' + Math.round(svc.uptime / 1000) + 's' : '';
|
|
console.log(' ' + color + icon + '\x1b[0m ' + svc.name.padEnd(16) + ' [' + svc.status + ']' + pid + uptime);
|
|
}
|
|
" "$tmpfile"
|
|
rm -f "$tmpfile"
|
|
fi
|
|
|
|
# 数据库状态
|
|
echo ""
|
|
db_status
|
|
else
|
|
echo -e "${RED}✗ DevTools 离线${NC}"
|
|
fi
|
|
}
|
|
|
|
# ========== 主入口 ==========
|
|
CMD="${1:-start}"
|
|
shift 2>/dev/null || true
|
|
|
|
case "$CMD" in
|
|
help|--help|-h)
|
|
show_help
|
|
;;
|
|
start|"")
|
|
start_devtools "$@"
|
|
;;
|
|
stop)
|
|
stop_devtools
|
|
;;
|
|
status)
|
|
show_status
|
|
;;
|
|
logs)
|
|
if [ -z "$1" ]; then
|
|
echo -e "${YELLOW}用法: ./devtools.sh logs <服务ID>${NC}"
|
|
echo "可用服务: gateway, ai-core, memory-service, tool-engine, voice-service, iot-debug-service, plugin-manager, platform-bridge, frontend"
|
|
exit 1
|
|
fi
|
|
show_logs "$1" "${2:-20}"
|
|
;;
|
|
build)
|
|
check_deps || exit 1
|
|
load_env
|
|
if [ -n "$1" ]; then
|
|
# 查找服务目录
|
|
for entry in "${SERVICES[@]}"; do
|
|
IFS=':' read -r id dir <<< "$entry"
|
|
if [ "$id" = "$1" ]; then
|
|
build_service "$id" "$dir" "$id"
|
|
exit $?
|
|
fi
|
|
done
|
|
echo -e "${RED}未知服务: $1${NC}"
|
|
echo "可用服务: ${SERVICES[*]}"
|
|
exit 1
|
|
else
|
|
build_all
|
|
fi
|
|
;;
|
|
db:start)
|
|
db_start
|
|
;;
|
|
db:stop)
|
|
db_stop
|
|
;;
|
|
db:status)
|
|
db_status
|
|
;;
|
|
*)
|
|
echo -e "${RED}未知命令: $CMD${NC}"
|
|
echo ""
|
|
show_help
|
|
exit 1
|
|
;;
|
|
esac
|