- backend/: 13 Maven modules (cw-elevator-application, cloudwalk-cloud, intelligent-cwoscomponent, ninca-crk, etc.) - frontend/: 4 Vue projects (elevator-front, cwos-portal, alarm-front, front_acs) + decompiled + scripts - scripts/: build, test-env, tools (Docker Compose, service templates, API parity) - docs/: AGENTS.md, superpowers specs, architecture docs - .gitignore: standard Java/Maven exclusions Moved from legacy maven-*/ root layout to backend/ organized structure.
40 KiB
V2 全系统功能测试环境搭建 — 实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (
- [ ]) syntax for tracking.
Goal: 搭建 cw-elevator-application V2 (v2.0.7) 的全系统集成功能测试环境,一键搭建 17 个组件并在 15-20 分钟内完成全部功能验证。
Architecture: Docker Compose 管理基础组件 (Consul+Redis+Kafka+Nginx),Bash 脚本编排 13 个本机 Java 服务,复用现有 MySQL (192.168.3.12:3307),配置模板驱动 + 环境变量统一管理。
Tech Stack: Bash 4+, Docker Compose v2, JDK 8, Maven 3.5+, Python 3.8+ (pytest), MySQL 5.7
关联 Spec: docs/superpowers/specs/2026-05-01-v2-test-env-setup-design.md
文件映射
| 产出文件 | 职责 |
|---|---|
源码/scripts/test-env/config/env.sh |
统一环境变量 (IP/端口/密码/路径) |
源码/scripts/test-env/docker-compose.infra.yml |
基础组件容器编排 (Consul+Redis+Kafka+Nginx) |
源码/scripts/test-env/prepare-db.sh |
数据库恢复 (11 个库从 data_backup/) |
源码/scripts/test-env/prepare-services.sh |
解压 tar.gz + 从模板生成配置 |
源码/scripts/test-env/build-elevator-v2.sh |
编译 V2 电梯应用 |
源码/scripts/test-env/config/service-templates/elevator-v2.properties |
V2 电梯配置模板 |
源码/scripts/test-env/config/service-templates/crk-std.properties |
CRK 后端配置模板 |
源码/scripts/test-env/config/service-templates/alarm.properties |
报警应用配置模板 |
源码/scripts/test-env/config/service-templates/ninca-common.properties |
ninca-common 配置模板 |
源码/scripts/test-env/config/service-templates/component-org.properties |
component-org 配置模板 |
源码/scripts/test-env/start-all.sh |
按拓扑序启动全部服务 |
源码/scripts/test-env/stop-all.sh |
按逆序停止全部服务 |
源码/scripts/test-env/health-check.sh |
探活检查 |
源码/scripts/test-env/verify-functional.sh |
功能验证 (对拍+策略+联动) |
源码/scripts/test-env/setup.sh |
主入口脚本 (编排所有 Phase) |
Task 1: 创建目录结构和 env.sh
Files:
-
Create:
源码/scripts/test-env/config/env.sh -
Create:
源码/scripts/test-env/config/service-templates/ -
Step 1: 创建目录结构
mkdir -p 源码/scripts/test-env/config/service-templates
- Step 2: 编写 env.sh
编辑 源码/scripts/test-env/config/env.sh,内容如下:
#!/bin/bash
# V2 测试环境 — 统一环境变量
# 所有脚本 source 此文件获取统一配置
set -euo pipefail
# ============================================
# 路径
# ============================================
REPO_ROOT="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../.." && pwd)"
STAR_CENTER="$REPO_ROOT/../星中心"
DATA_BACKUP="$REPO_ROOT/../data_backup"
TEST_ENV_DIR="$REPO_ROOT/scripts/test-env"
SERVICE_DIR="$TEST_ENV_DIR/services"
LOG_DIR="$TEST_ENV_DIR/logs"
# ============================================
# Java
# ============================================
JAVA_HOME="${DEPLOY_JDK8:-/usr/lib/jvm/java-8-openjdk-amd64}"
JAVA="$JAVA_HOME/bin/java"
JAVA_OPTS_HEAVY="-Xmx3072m -Xms3072m -Xmn1024m" # CRK, alarm, elevator
JAVA_OPTS_LIGHT="-Xmx2048m -Xms512m" # common, org, portal
JAVA_OPTS_DEBUG="-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=n"
# ============================================
# 基础设施地址
# ============================================
MYSQL_HOST=192.168.3.12
MYSQL_PORT=3307
MYSQL_USER=root
MYSQL_PASS=123456
REDIS_HOST=127.0.0.1
REDIS_PORT=6379
REDIS_PASS="1qaz!QAZ"
CONSUL_HOST=127.0.0.1
CONSUL_PORT=8500
KAFKA_HOST=127.0.0.1
KAFKA_PORT=9092
ZK_HOST=127.0.0.1
ZK_PORT=2181
# ============================================
# 服务端口
# ============================================
PORT_ELEVATOR_V2=18081
PORT_ELEVATOR_V1=18080
PORT_CRK_STD=16106
PORT_CRK_MGMT=16114
PORT_ALARM=17011
PORT_ALARM_MGMT=17211
PORT_CWOS_PORTAL=33008
PORT_COMPONENT_ORG=33011
PORT_NINCA_COMMON=33010
PORT_CWOS_MANAGER=3721
PORT_SYSTEM_API=3333
PORT_SNAP_APP=33012
PORT_VEHICLE_APP=33013
PORT_PERSON_FILE=33014
PORT_MONITOR_APP=33015
PORT_NGINX=8090
# ============================================
# 数据库名
# ============================================
DB_ELEVATOR="cw-elevator-application"
DB_CRK="ninca_crk_std"
DB_ALARM="alarm_deploy"
DB_MANAGER="cwos_manager"
DB_PORTAL="cwos_portal"
DB_COMMON="ninca_common"
DB_COMPONENT_ORG="component-organization"
DB_ODS="ods"
DB_THIRDPARTY="cloudwalk_device_thirdparty"
DB_G="g"
DB_P="p"
DB_12="12"
DB_34="34"
# ============================================
# Maven
# ============================================
MVN="mvn"
MVN_OPTS="-DskipTests -Dformatter-maven-plugin.version=2.16.0"
ELEVATOR_POM="$REPO_ROOT/maven-cw-elevator-application/pom.xml"
# ============================================
# 日志颜色
# ============================================
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'; BLUE='\033[0;34m'; NC='\033[0m'
log_info() { echo -e "${BLUE}[INFO]${NC} $*"; }
log_ok() { echo -e "${GREEN}[OK]${NC} $*"; }
log_warn() { echo -e "${YELLOW}[WARN]${NC} $*"; }
log_error() { echo -e "${RED}[ERROR]${NC} $*"; }
- Step 3: 测试 env.sh 可被 source
bash -c 'source 源码/scripts/test-env/config/env.sh && echo "MYSQL=$MYSQL_HOST:$MYSQL_PORT"'
# 预期: MYSQL=192.168.3.12:3307
- Step 4: Commit
git add 源码/scripts/test-env/config/env.sh
git commit -m "feat: add test environment unified config (env.sh)"
Task 2: Docker Compose — 合并基础组件
Files:
-
Create:
源码/scripts/test-env/docker-compose.infra.yml -
Step 1: 编写合并后的 docker-compose.infra.yml
# V2 测试环境 — 基础组件 (Consul + Redis + Kafka + ZK + Nginx)
# 合并自: deploy/consul-docker/docker-compose.yml, docker-compose.frontend-local.yml
version: '3.8'
services:
consul:
image: hashicorp/consul:1.22
container_name: v2test-consul
restart: unless-stopped
ports:
- "${CONSUL_PORT:-8500}:8500"
command: >
agent -server -bootstrap-expect=1 -ui
-client=0.0.0.0 -bind=0.0.0.0
-data-dir=/consul/data
volumes:
- consul-data:/consul/data
redis:
image: redis:7-alpine
container_name: v2test-redis
restart: unless-stopped
ports:
- "${REDIS_PORT:-6379}:6379"
command: redis-server --requirepass "${REDIS_PASS:-1qaz!QAZ}"
zookeeper:
image: bitnami/zookeeper:3.9
container_name: v2test-zookeeper
restart: unless-stopped
ports:
- "${ZK_PORT:-2181}:2181"
environment:
ALLOW_ANONYMOUS_LOGIN: "yes"
kafka:
image: bitnami/kafka:3.6
container_name: v2test-kafka
restart: unless-stopped
ports:
- "${KAFKA_PORT:-9092}:9092"
environment:
KAFKA_CFG_NODE_ID: 1
KAFKA_CFG_PROCESS_ROLES: "broker,controller"
KAFKA_CFG_CONTROLLER_QUORUM_VOTERS: "1@localhost:9093"
KAFKA_CFG_LISTENERS: "PLAINTEXT://:9092,CONTROLLER://:9093"
KAFKA_CFG_ADVERTISED_LISTENERS: "PLAINTEXT://localhost:9092"
KAFKA_CFG_CONTROLLER_LISTENER_NAMES: "CONTROLLER"
KAFKA_CFG_LISTENER_SECURITY_PROTOCOL_MAP: "PLAINTEXT:PLAINTEXT,CONTROLLER:PLAINTEXT"
KAFKA_CFG_AUTO_CREATE_TOPICS_ENABLE: "true"
depends_on:
- zookeeper
nginx:
image: nginx:alpine
container_name: v2test-nginx
restart: unless-stopped
ports:
- "${PORT_NGINX:-8090}:80"
extra_hosts:
- "host.docker.internal:host-gateway"
volumes:
- ../../frontend:/data/cwos/frontend:ro
- ../../nginx.frontend-local.conf:/etc/nginx/conf.d/default.conf:ro
volumes:
consul-data:
- Step 2: 验证 Docker Compose 语法
cd 源码/scripts/test-env
source config/env.sh
docker compose -f docker-compose.infra.yml config --quiet
# 预期: 无输出 (语法正确)
- Step 3: 启动并验证所有容器
docker compose -f 源码/scripts/test-env/docker-compose.infra.yml up -d
# 等待启动
sleep 15
# 验证 Consul
curl -sf http://127.0.0.1:8500/v1/status/leader && echo "Consul OK"
# 验证 Redis
docker exec v2test-redis redis-cli -a 1qaz!QAZ PING | grep PONG && echo "Redis OK"
# 验证 Kafka
docker exec v2test-kafka kafka-topics.sh --bootstrap-server localhost:9092 --list && echo "Kafka OK"
# 验证 Nginx
curl -sf http://localhost:8090/ && echo "Nginx OK"
- Step 4: 清理并 commit
docker compose -f 源码/scripts/test-env/docker-compose.infra.yml down
git add 源码/scripts/test-env/docker-compose.infra.yml
git commit -m "feat: add Docker Compose for test infra (Consul+Redis+Kafka+ZK+Nginx)"
Task 3: prepare-db.sh — 数据库恢复
Files:
-
Create:
源码/scripts/test-env/prepare-db.sh -
Step 1: 编写数据库恢复脚本
#!/bin/bash
# prepare-db.sh — 恢复 11 个数据库 SQL 备份到 MySQL
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "Phase 2: Database preparation"
log_info "Target: $MYSQL_HOST:$MYSQL_PORT (user: $MYSQL_USER)"
MYSQL_CMD="mysql -h $MYSQL_HOST -P $MYSQL_PORT -u $MYSQL_USER -p${MYSQL_PASS}"
# 检查 MySQL 连通性
if ! $MYSQL_CMD -e "SELECT 1" &>/dev/null; then
log_error "Cannot connect to MySQL at $MYSQL_HOST:$MYSQL_PORT"
exit 1
fi
log_ok "MySQL connection OK"
# 数据库名 → 备份文件映射
declare -A DB_MAP=(
["$DB_ELEVATOR"]="12_2026_04_23_17_28_33.sql.gz"
["$DB_ALARM"]="alarm_deploy_2026_04_23_17_28_33.sql.gz"
["$DB_MANAGER"]="cwos_manager_2026_04_23_17_28_33.sql.gz"
["$DB_PORTAL"]="cwos_portal_2026_04_23_17_28_33.sql.gz"
["$DB_COMMON"]="ninca_common_2026_04_23_17_28_33.sql.gz"
["$DB_COMPONENT_ORG"]="component-organization_2026_04_23_17_28_33.sql.gz"
["$DB_ODS"]="ods_2026_04_23_17_28_33.sql.gz"
["$DB_THIRDPARTY"]="cloudwalk_device_thirdparty_2026_04_23_17_28_33.sql.gz"
["$DB_G"]="g_2026_04_23_17_28_33.sql.gz"
["$DB_P"]="p_2026_04_23_17_28_33.sql.gz"
)
for db_name in "${!DB_MAP[@]}"; do
backup_file="${DB_MAP[$db_name]}"
backup_path="$DATA_BACKUP/$backup_file"
if [[ ! -f "$backup_path" ]]; then
log_warn "Backup not found: $backup_path — skipping $db_name"
continue
fi
log_info "Restoring $db_name from $backup_file ($(du -h "$backup_path" | cut -f1))..."
# 建库 (如不存在)
$MYSQL_CMD -e "CREATE DATABASE IF NOT EXISTS \`$db_name\` DEFAULT CHARACTER SET utf8mb4;"
# 导入
zcat "$backup_path" | $MYSQL_CMD "$db_name" 2>&1 | tail -1
if [[ ${PIPESTATUS[0]} -eq 0 ]]; then
log_ok " $db_name restored successfully"
else
log_error " $db_name restore FAILED"
fi
done
# 电梯应用还需要第二个备份 (34_*.sql.gz)
if [[ -f "$DATA_BACKUP/34_2026_04_23_17_28_33.sql.gz" ]]; then
log_info "Restoring elevator DB partition 34..."
zcat "$DATA_BACKUP/34_2026_04_23_17_28_33.sql.gz" | $MYSQL_CMD "$DB_ELEVATOR"
log_ok " $DB_ELEVATOR partition 34 restored"
fi
# 执行 V2.0.7 DDL (tenant_visitor_floor_policy)
log_info "Applying V2.0.7 DDL (tenant_visitor_floor_policy)..."
$MYSQL_CMD "$DB_ELEVATOR" < "$REPO_ROOT/docs/sql/tenant_visitor_floor_policy.sql"
log_ok " V2 DDL applied"
log_info "Database preparation complete"
- Step 2: 测试脚本 (dry-run: 仅检查连通性)
bash 源码/scripts/test-env/prepare-db.sh
# 预期: MySQL connection OK, 然后恢复各个库
- Step 3: Commit
git add 源码/scripts/test-env/prepare-db.sh
git commit -m "feat: add database restoration script for 11 databases"
Task 4: 配置模板 + prepare-services.sh
Files:
-
Create:
源码/scripts/test-env/config/service-templates/elevator-v2.properties -
Create:
源码/scripts/test-env/config/service-templates/crk-std.properties -
Create:
源码/scripts/test-env/config/service-templates/alarm.properties -
Create:
源码/scripts/test-env/config/service-templates/ninca-common.properties -
Create:
源码/scripts/test-env/config/service-templates/component-org.properties -
Create:
源码/scripts/test-env/prepare-services.sh -
Step 1: 创建 V2 电梯配置模板
源码/scripts/test-env/config/service-templates/elevator-v2.properties:
# V2 elevator test config — auto-generated from template
# Variables: __MYSQL_HOST__ __MYSQL_PORT__ __REDIS_HOST__ __REDIS_PASS__
# __CONSUL_HOST__ __CONSUL_PORT__ __KAFKA_HOST__ __KAFKA_PORT__
# __CRK_HOST__ __CRK_PORT__
server.port=18081
spring.application.name=elevator-app
spring.profiles.active=access-control
# Consul
spring.cloud.consul.host=__CONSUL_HOST__
spring.cloud.consul.port=__CONSUL_PORT__
spring.cloud.consul.enabled=true
spring.cloud.consul.discovery.register=true
spring.cloud.consul.discovery.enabled=false
# MySQL (ShardingSphere)
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/cw-elevator-application?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# Redis
spring.redis.host=__REDIS_HOST__
spring.redis.port=6379
spring.redis.password=__REDIS_PASS__
spring.redis.database=5
# Kafka
cloudwalk.event.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
cloudwalk.event.group-id=cw-elevator-application-test
# Feign targets
feign.device.name=cwos-portal
feign.cwos-portal.name=cwos-portal
feign.ninca-crk-std.name=ninca-crk-std
ninca-crk-std.ribbon.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
ninca-crk-std.ribbon.listOfServers=__CRK_HOST__:__CRK_PORT__
ninca-crk-std.ip=__CRK_HOST__:__CRK_PORT__
# Other
feign.hystrix.enable=true
feign.okhttp.enable=true
ribbon.ReadTimeout=10000
ribbon.ConnectTimeout=10000
elevator.application.key=xinghewan
elevator.application.time=600
elevator.application.keyA=5B7DEF88FF04
floor.building.id=605560539791228928
sendRecord.boolean=false
- Step 2: 创建 CRK 后端配置模板
源码/scripts/test-env/config/service-templates/crk-std.properties:
# CRK-std test config
spring.application.name=ninca-crk-std
server.port=__CRK_PORT__
spring.profiles.active=smart-attendance,visitor-management,access-control,conference-attendance
# Consul
spring.cloud.consul.host=__CONSUL_HOST__
spring.cloud.consul.port=__CONSUL_PORT__
spring.cloud.consul.discovery.register=true
spring.cloud.consul.discovery.ip-address=127.0.0.1
# MySQL
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/ninca_crk_std?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
spring.shardingsphere.datasource.ds0.username=root
spring.shardingsphere.datasource.ds0.password=123456
# Redis
spring.redis.host=__REDIS_HOST__
spring.redis.port=6379
spring.redis.password=__REDIS_PASS__
spring.redis.database=5
# Kafka
kafka.producer.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
kafka.consumer.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
spring.kafka.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
spring.kafka.consumer.group-id=crk_std_test
cloudwalk.event.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
cloudwalk.event.group-id=crk_std
# Feign service names
feign.device.name=cwos-portal
feign.resource.name=cwos-portal
feign.cwos-portal.name=cwos-portal
feign.portal.name=cwos-portal
feign.component-organization.name=ninca-common-component-organization
feign.davinci-portal.name=cwos-portal
feign.ninca-common.name=ninca-common
feign.elevator.name=elevator-app
# Quartz
quartz.driver=com.mysql.jdbc.Driver
quartz.url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/ninca_crk_std?zeroDateTimeBehavior=convertToNull&useUnicode=TRUE&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
quartz.user=root
quartz.password=123456
# Management
management.port=__CRK_MGMT_PORT__
management.context-path=/actuator
management.security.enabled=false
xinhewan.businessid=2524639890ba4f2cba9ba1a4eeaa4015
push.method=1
sendRecord.boolean=false
- Step 3: 创建 alarm 配置模板
源码/scripts/test-env/config/service-templates/alarm.properties:
# Alarm app test config
spring.application.name=ninca-qk-alarm-app
server.port=__ALARM_PORT__
spring.main.allow-bean-definition-overriding=true
# Consul
spring.cloud.consul.host=__CONSUL_HOST__
spring.cloud.consul.port=__CONSUL_PORT__
spring.cloud.consul.discovery.register=true
spring.cloud.consul.discovery.ip-address=127.0.0.1
# MySQL
spring.datasource.url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/alarm_deploy?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
# Redis
spring.redis.host=__REDIS_HOST__
spring.redis.port=6379
spring.redis.password=__REDIS_PASS__
spring.redis.database=7
# Kafka
kafka.producer.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
kafka.consumer.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
spring.kafka.bootstrap-servers=__KAFKA_HOST__:__KAFKA_PORT__
spring.kafka.consumer.group-id=alarm_test
# Feign names
cloudwalk.alarm-app.feign.name.cwos-portal=cwos-portal
cloudwalk.alarm-app.feign.name.component-organization=ninca-common-component-organization
cloudwalk.alarm-app.feign.name.ninca-common=ninca-common
# Management
management.port=__ALARM_MGMT_PORT__
management.context-path=/actuator
management.security.enabled=false
# Disable non-essential features for test
swagger.enable=false
sendRecord.boolean=false
- Step 4: 创建 ninca-common 和 component-org 配置模板
源码/scripts/test-env/config/service-templates/ninca-common.properties:
server.port=__NINCA_COMMON_PORT__
spring.application.name=ninca-common
spring.datasource.url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/ninca_common?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.cloud.consul.host=__CONSUL_HOST__
spring.cloud.consul.port=__CONSUL_PORT__
spring.cloud.consul.discovery.register=true
spring.redis.host=__REDIS_HOST__
spring.redis.port=6379
spring.redis.password=__REDIS_PASS__
源码/scripts/test-env/config/service-templates/component-org.properties:
server.port=__COMPONENT_ORG_PORT__
spring.application.name=ninca-common-component-organization
spring.datasource.url=jdbc:mysql://__MYSQL_HOST__:__MYSQL_PORT__/component-organization?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.datasource.username=root
spring.datasource.password=123456
spring.cloud.consul.host=__CONSUL_HOST__
spring.cloud.consul.port=__CONSUL_PORT__
spring.cloud.consul.discovery.register=true
spring.redis.host=__REDIS_HOST__
spring.redis.port=6379
spring.redis.password=__REDIS_PASS__
- Step 5: 编写 prepare-services.sh (解压 + 配置注入)
#!/bin/bash
# prepare-services.sh — 解压 tar.gz + 从模板生成配置
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "Phase 4: Service preparation"
mkdir -p "$SERVICE_DIR" "$LOG_DIR"
# 需要解压的 tar.gz 列表
TARBALLS=(
"$STAR_CENTER/ninca_common_01-ninca_common_backend.tar.gz:ninca-common"
"$STAR_CENTER/ninca_common_component_organization_01-ninca_common_component_organization.tar.gz:component-org"
"$STAR_CENTER/ninca_common_snap_app_01-ninca_common_snap_app.tar.gz:snap-app"
"$STAR_CENTER/ninca_common_vehicle_app_01-ninca_common_vehicle_app.tar.gz:vehicle-app"
"$STAR_CENTER/ninca_common_monitor_app_01-ninca_common_monitor_app.tar.gz:monitor-app"
"$STAR_CENTER/ninca-person-file-app-V2.9.2_20210216.tar.gz:person-file"
)
for item in "${TARBALLS[@]}"; do
tarball="${item%%:*}"
svc_name="${item##*:}"
svc_dir="$SERVICE_DIR/$svc_name"
if [[ -d "$svc_dir" ]]; then
log_info "Already extracted: $svc_name — skipping"
continue
fi
log_info "Extracting $tarball → $svc_dir ..."
mkdir -p "$svc_dir"
tar -xzf "$tarball" -C "$svc_dir" --strip-components=1
log_ok " $svc_name extracted"
done
# 配置模板变量替换函数
render_template() {
local template="$1"
local output="$2"
sed \
-e "s|__MYSQL_HOST__|$MYSQL_HOST|g" \
-e "s|__MYSQL_PORT__|$MYSQL_PORT|g" \
-e "s|__REDIS_HOST__|$REDIS_HOST|g" \
-e "s|__REDIS_PASS__|$REDIS_PASS|g" \
-e "s|__CONSUL_HOST__|$CONSUL_HOST|g" \
-e "s|__CONSUL_PORT__|$CONSUL_PORT|g" \
-e "s|__KAFKA_HOST__|$KAFKA_HOST|g" \
-e "s|__KAFKA_PORT__|$KAFKA_PORT|g" \
-e "s|__CRK_HOST__|127.0.0.1|g" \
-e "s|__CRK_PORT__|$PORT_CRK_STD|g" \
-e "s|__CRK_MGMT_PORT__|$PORT_CRK_MGMT|g" \
-e "s|__ALARM_PORT__|$PORT_ALARM|g" \
-e "s|__ALARM_MGMT_PORT__|$PORT_ALARM_MGMT|g" \
-e "s|__NINCA_COMMON_PORT__|$PORT_NINCA_COMMON|g" \
-e "s|__COMPONENT_ORG_PORT__|$PORT_COMPONENT_ORG|g" \
"$template" > "$output"
}
# 为各服务生成配置文件
render_template "$TEST_ENV_DIR/config/service-templates/elevator-v2.properties" \
"$REPO_ROOT/maven-cw-elevator-application/deploy/v2-maven/application-test.properties"
log_ok " elevator-v2 config generated"
render_template "$TEST_ENV_DIR/config/service-templates/crk-std.properties" \
"$STAR_CENTER/ninca_crk_std_01-ninca_crk_std_backend/application-test.properties"
log_ok " crk-std config generated"
render_template "$TEST_ENV_DIR/config/service-templates/alarm.properties" \
"$STAR_CENTER/ninca_qk_alarm_app_01-ninca_qk_alarm_app/application-test.properties"
log_ok " alarm config generated"
# 为解压的 tarball 服务生成配置
if [[ -d "$SERVICE_DIR/ninca-common" ]]; then
render_template "$TEST_ENV_DIR/config/service-templates/ninca-common.properties" \
"$SERVICE_DIR/ninca-common/application-test.properties"
log_ok " ninca-common config generated"
fi
if [[ -d "$SERVICE_DIR/component-org" ]]; then
render_template "$TEST_ENV_DIR/config/service-templates/component-org.properties" \
"$SERVICE_DIR/component-org/application-test.properties"
log_ok " component-org config generated"
fi
log_info "Service preparation complete"
- Step 6: 测试 prepare-services.sh
bash 源码/scripts/test-env/prepare-services.sh
# 预期: 解压 6 个 tar.gz, 生成 5 个配置文件
- Step 7: Commit
git add 源码/scripts/test-env/config/service-templates/ 源码/scripts/test-env/prepare-services.sh
git commit -m "feat: add service config templates and extraction script"
Task 5: build-elevator-v2.sh — 编译 V2 电梯应用
Files:
-
Create:
源码/scripts/test-env/build-elevator-v2.sh -
Step 1: 编写 V2 编译脚本
#!/bin/bash
# build-elevator-v2.sh — 编译 cw-elevator-application V2
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "Building V2 elevator application..."
cd "$REPO_ROOT/maven-cw-elevator-application"
export JAVA_HOME
export PATH="$JAVA_HOME/bin:$PATH"
log_info "JDK: $($JAVA -version 2>&1 | head -1)"
# 编译 (跳过测试)
$MVN clean install $MVN_OPTS 2>&1 | tail -5
if [[ ${PIPESTATUS[0]} -eq 0 ]]; then
log_ok "V2 elevator build SUCCESS"
else
log_error "V2 elevator build FAILED — check logs"
exit 1
fi
# 同步 JAR 到 deploy/
if [[ -f "cw-elevator-application-starter/target/cw-elevator-application-2.0.7.jar" ]]; then
cp cw-elevator-application-starter/target/cw-elevator-application-2.0.7.jar \
deploy/v2-maven/cw-elevator-application-2.0.7.jar
log_ok "JAR synced to deploy/v2-maven/"
else
# Fallback: 用脚本同步
cd deploy && bash sync-jars.sh
log_ok "JAR synced via sync-jars.sh"
fi
- Step 2: 测试编译
bash 源码/scripts/test-env/build-elevator-v2.sh
# 预期: BUILD SUCCESS, JAR synced
- Step 3: Commit
git add 源码/scripts/test-env/build-elevator-v2.sh
git commit -m "feat: add V2 elevator build script"
Task 6: start-all.sh / stop-all.sh — 服务启停
Files:
-
Create:
源码/scripts/test-env/start-all.sh -
Create:
源码/scripts/test-env/stop-all.sh -
Step 1: 编写 start-all.sh
#!/bin/bash
# start-all.sh — 按拓扑序启动所有 Java 服务
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
export JAVA_HOME
export PATH="$JAVA_HOME/bin:$PATH"
log_info "Phase 5: Starting all services..."
mkdir -p "$LOG_DIR"
# 启动函数: start_service <name> <jar_path> <port> <java_opts> <extra_args>
start_service() {
local name="$1"; local jar="$2"; local port="$3"
local opts="${4:-$JAVA_OPTS_LIGHT}"; shift 4
log_info "Starting $name (port $port)..."
if [[ ! -f "$jar" ]]; then
log_error " JAR not found: $jar"
return 1
fi
nohup $JAVA -jar $opts "$jar" "$@" \
--spring.config.location="$(dirname "$jar")/" \
> "$LOG_DIR/${name}.log" 2>&1 &
local pid=$!
echo $pid > "$LOG_DIR/${name}.pid"
# 等待服务就绪 (最多 60s)
for i in $(seq 1 30); do
sleep 2
if curl -sf "http://127.0.0.1:$port/actuator/health" &>/dev/null; then
log_ok " $name (pid=$pid, port=$port) STARTED"
return 0
fi
done
log_warn " $name health check timeout after 60s (pid=$pid)"
return 1
}
# ============================================
# 启动顺序 (拓扑序)
# ============================================
# A1: ninca-common
COMMON_JAR=$(find "$SERVICE_DIR/ninca-common" -name "*.jar" -not -name "*-sources*" | head -1)
if [[ -n "$COMMON_JAR" ]]; then
start_service "ninca-common" "$COMMON_JAR" "$PORT_NINCA_COMMON" \
"$JAVA_OPTS_LIGHT" \
--spring.config.additional-location="$TEST_ENV_DIR/config/service-templates/ninca-common.properties"
fi
# A2: component-organization
ORG_JAR=$(find "$SERVICE_DIR/component-org" -name "*.jar" -not -name "*-sources*" | head -1)
if [[ -n "$ORG_JAR" ]]; then
start_service "component-org" "$ORG_JAR" "$PORT_COMPONENT_ORG" \
"$JAVA_OPTS_LIGHT" \
--spring.config.additional-location="$TEST_ENV_DIR/config/service-templates/component-org.properties"
fi
# A10: CRK-std
CRK_DIR="$STAR_CENTER/ninca_crk_std_01-ninca_crk_std_backend/ninca-crk-std-backend-V2.9.2_20210730"
CRK_JAR="$STAR_CENTER/ninca_crk_std_01-ninca_crk_std_backend/ninca-crk-std-backend-V2.9.2_20210730.jar"
if [[ -f "$CRK_JAR" ]]; then
start_service "crk-std" "$CRK_JAR" "$PORT_CRK_STD" \
"$JAVA_OPTS_HEAVY" \
--spring.config.location="$CRK_DIR/"
fi
# A11: alarm-app
ALARM_JAR="$STAR_CENTER/ninca_qk_alarm_app_01-ninca_qk_alarm_app/ninca-qk-alarm-app-V2.9.2_20210730.jar"
if [[ -f "$ALARM_JAR" ]]; then
start_service "alarm-app" "$ALARM_JAR" "$PORT_ALARM" \
"$JAVA_OPTS_HEAVY" \
--spring.config.location="$STAR_CENTER/ninca_qk_alarm_app_01-ninca_qk_alarm_app/"
fi
# A12: elevator V2 (最后启动,依赖 CRK+portal+org)
ELEVATOR_V2_JAR="$REPO_ROOT/maven-cw-elevator-application/deploy/v2-maven/cw-elevator-application-2.0.7.jar"
if [[ -f "$ELEVATOR_V2_JAR" ]]; then
start_service "elevator-v2" "$ELEVATOR_V2_JAR" "$PORT_ELEVATOR_V2" \
"$JAVA_OPTS_HEAVY" \
--spring.config.location="$REPO_ROOT/maven-cw-elevator-application/deploy/v2-maven/"
fi
# A13: elevator V1 (对拍对照)
ELEVATOR_V1_JAR="$REPO_ROOT/maven-cw-elevator-application/deploy/v1-legacy/cw-elevator-application-V1.0.0.20211103.jar"
if [[ -f "$ELEVATOR_V1_JAR" ]]; then
start_service "elevator-v1" "$ELEVATOR_V1_JAR" "$PORT_ELEVATOR_V1" \
"$JAVA_OPTS_HEAVY" \
--spring.config.location="$REPO_ROOT/maven-cw-elevator-application/deploy/v1-legacy/"
fi
log_info "All services started"
- Step 2: 编写 stop-all.sh
#!/bin/bash
# stop-all.sh — 按逆序停止所有服务
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "Stopping all services..."
for pid_file in "$LOG_DIR"/*.pid; do
if [[ -f "$pid_file" ]]; then
pid=$(cat "$pid_file")
svc=$(basename "$pid_file" .pid)
if kill -0 "$pid" 2>/dev/null; then
log_info "Stopping $svc (pid=$pid)..."
kill "$pid"
sleep 2
kill -9 "$pid" 2>/dev/null || true
log_ok " $svc stopped"
fi
rm -f "$pid_file"
fi
done
log_info "All services stopped"
- Step 3: 测试启动/停止流程
# 先确保 Docker infra 已启动
# 然后:
bash 源码/scripts/test-env/start-all.sh
sleep 30
bash 源码/scripts/test-env/stop-all.sh
- Step 4: Commit
git add 源码/scripts/test-env/start-all.sh 源码/scripts/test-env/stop-all.sh
git commit -m "feat: add service start/stop orchestration scripts"
Task 7: health-check.sh — 探活检查
Files:
-
Create:
源码/scripts/test-env/health-check.sh -
Step 1: 编写探活脚本
#!/bin/bash
# health-check.sh — 全组件探活检查
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "=== Health Check ==="
FAILED=0
PASSED=0
check_http() {
local name="$1"; local url="$2"
if curl -sf --max-time 5 "$url" &>/dev/null; then
log_ok " $name ($url)"
((PASSED++))
else
log_error " $name ($url) FAILED"
((FAILED++))
fi
}
check_tcp() {
local name="$1"; local host="$2"; local port="$3"
if nc -z -w3 "$host" "$port" &>/dev/null; then
log_ok " $name ($host:$port)"
((PASSED++))
else
log_error " $name ($host:$port) FAILED"
((FAILED++))
fi
}
log_info "--- Infrastructure ---"
check_http "Consul" "http://$CONSUL_HOST:$CONSUL_PORT/v1/status/leader"
check_tcp "Redis" "$REDIS_HOST" "$REDIS_PORT"
check_tcp "Kafka" "$KAFKA_HOST" "$KAFKA_PORT"
check_http "Nginx" "http://$CONSUL_HOST:$PORT_NGINX"
log_info "--- Application Services ---"
check_http "elevator-v2" "http://127.0.0.1:$PORT_ELEVATOR_V2/actuator/health"
check_http "elevator-v1" "http://127.0.0.1:$PORT_ELEVATOR_V1/actuator/health"
check_http "crk-std" "http://127.0.0.1:$PORT_CRK_MGMT/actuator/health"
check_http "alarm-app" "http://127.0.0.1:$PORT_ALARM_MGMT/actuator/health"
log_info "--- Databases ---"
check_tcp "MySQL" "$MYSQL_HOST" "$MYSQL_PORT"
log_info "--- Consul Registration ---"
CONSUL_SVCS=$(curl -sf "http://$CONSUL_HOST:$CONSUL_PORT/v1/agent/services" | python3 -c "import sys,json; print(len(json.load(sys.stdin)))" 2>/dev/null || echo "0")
log_info " Registered services: $CONSUL_SVCS"
echo ""
log_info "=== Result: $PASSED passed, $FAILED failed ==="
[[ $FAILED -eq 0 ]] && exit 0 || exit 1
- Step 2: 测试探活
bash 源码/scripts/test-env/health-check.sh
- Step 3: Commit
git add 源码/scripts/test-env/health-check.sh
git commit -m "feat: add health-check script for all components"
Task 8: verify-functional.sh — 功能验证
Files:
-
Create:
源码/scripts/test-env/verify-functional.sh -
Step 1: 编写功能验证脚本
#!/bin/bash
# verify-functional.sh — V2 全系统功能验证
source "$(dirname "${BASH_SOURCE[0]}")/config/env.sh"
log_info "=== V2 Functional Verification ==="
FAILED=0
verify() {
local desc="$1"; shift
if "$@"; then
log_ok " $desc"
else
log_error " $desc FAILED"
((FAILED++))
fi
}
# F1: 电梯 V2 探活
log_info "[F1] Elevator V2 health"
verify "elevator-v2 /actuator/health" \
curl -sf "http://127.0.0.1:$PORT_ELEVATOR_V2/actuator/health"
# F2: V1 vs V2 API 对拍
log_info "[F2] V1/V2 API parity test"
cd "$REPO_ROOT/maven-cw-elevator-application/tools/elevator_api_parity"
verify "pytest elevator_api_parity" \
pytest tests/test_smoke_catalog.py -q --tb=short
# F3: V2.0.7 租户策略 — UC-01 基线 (无策略表 → floorList 全集)
log_info "[F3] Tenant visitor policy — UC-01 baseline"
# 注意: 需要先插入策略表测试数据; 基线测试确认无策略时行为不变
RESP=$(curl -sf -X POST "http://127.0.0.1:$PORT_ELEVATOR_V2/elevator/person/add/visitor" \
-H "Content-Type: application/json" \
-d '{"personId":"test-person","visitorName":"test-visitor"}' 2>/dev/null || echo '{"code":-1}')
verify "UC-01 baseline (no policy)" echo "$RESP" | python3 -c "import sys,json; d=json.load(sys.stdin); assert d.get('code')!=76260532"
# F4: V2.0.7 — UC-02 显式传 floorIds
log_info "[F4] Tenant visitor policy — UC-02 explicit floorIds"
RESP2=$(curl -sf -X POST "http://127.0.0.1:$PORT_ELEVATOR_V2/elevator/person/add/visitor" \
-H "Content-Type: application/json" \
-d '{"personId":"test-person","visitorName":"test-visitor2","floorIds":["zone-001"]}' 2>/dev/null || echo '{"code":-1}')
verify "UC-02 (explicit floorIds)" echo "$RESP2" | python3 -c "import sys,json; d=json.load(sys.stdin); print(d.get('code'))"
# F7: CRK 联动
log_info "[F7] CRK integration"
verify "crk-std /actuator/health" \
curl -sf "http://127.0.0.1:$PORT_CRK_MGMT/actuator/health"
# F8: 报警 Kafka 消费
log_info "[F8] Alarm Kafka — Consul registration check"
verify "alarm registered in Consul" \
curl -sf "http://$CONSUL_HOST:$CONSUL_PORT/v1/agent/services" | python3 -c "import sys,json; services=json.load(sys.stdin); assert any('alarm' in k.lower() for k in services)"
# F9: Nginx 前端代理
log_info "[F9] Frontend Nginx"
verify "nginx serves cwos-portal" \
curl -sf -o /dev/null -w "%{http_code}" "http://$CONSUL_HOST:$PORT_NGINX/" | grep -q 200
# I1: MySQL 连通
log_info "[I1] MySQL connectivity"
verify "mysql SELECT 1" \
mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASS" -e "SELECT 1" &>/dev/null
echo ""
log_info "=== Verification Complete: $FAILED failures ==="
[[ $FAILED -eq 0 ]] && exit 0 || exit 1
- Step 2: 测试验证脚本 (需要服务已运行)
bash 源码/scripts/test-env/verify-functional.sh
- Step 3: Commit
git add 源码/scripts/test-env/verify-functional.sh
git commit -m "feat: add functional verification script (API parity + tenant policy + integration)"
Task 9: setup.sh — 主入口一键搭建
Files:
-
Create:
源码/scripts/test-env/setup.sh -
Step 1: 编写主入口脚本
#!/bin/bash
# setup.sh — V2 测试环境一键搭建入口
set -euo pipefail
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
source "$SCRIPT_DIR/config/env.sh"
cat << 'BANNER'
╔══════════════════════════════════════════════╗
║ V2 Full-System Test Environment Setup ║
║ cw-elevator-application v2.0.7 ║
╚══════════════════════════════════════════════╝
BANNER
START_TIME=$(date +%s)
# ============================================
# Phase 1: 环境检查
# ============================================
log_info "Phase 1/6: Environment check"
# JDK 8
if [[ ! -x "$JAVA_HOME/bin/java" ]]; then
log_error "JDK 8 not found at $JAVA_HOME"
log_info "Set DEPLOY_JDK8 environment variable to correct path"
exit 1
fi
JAVA_VER=$("$JAVA_HOME/bin/java" -version 2>&1 | head -1)
log_ok "JDK: $JAVA_VER"
# Maven
if ! command -v mvn &>/dev/null; then
log_error "Maven not found"
exit 1
fi
log_ok "Maven: $(mvn --version 2>&1 | head -1)"
# Docker
if ! docker compose version &>/dev/null; then
log_error "Docker Compose not found"
exit 1
fi
log_ok "Docker: $(docker compose version)"
# MySQL
if ! mysql -h "$MYSQL_HOST" -P "$MYSQL_PORT" -u "$MYSQL_USER" -p"$MYSQL_PASS" -e "SELECT 1" &>/dev/null; then
log_error "Cannot connect to MySQL at $MYSQL_HOST:$MYSQL_PORT"
exit 1
fi
log_ok "MySQL: $MYSQL_HOST:$MYSQL_PORT OK"
# 端口冲突
CONFLICT_PORTS=""
for port in 8500 6379 9092 2181 8090 18080 18081 16106 17011 3721; do
if ss -tlnp 2>/dev/null | grep -q ":$port "; then
CONFLICT_PORTS="$CONFLICT_PORTS $port"
fi
done
if [[ -n "$CONFLICT_PORTS" ]]; then
log_warn "Ports already in use:$CONFLICT_PORTS"
log_warn "These services may fail to start. Stop existing processes first."
fi
log_ok "Phase 1 complete"
# ============================================
# Phase 2: 数据库准备
# ============================================
log_info "Phase 2/6: Database preparation"
bash "$SCRIPT_DIR/prepare-db.sh"
log_ok "Phase 2 complete"
# ============================================
# Phase 3: Docker 基础组件启动
# ============================================
log_info "Phase 3/6: Docker infrastructure"
cd "$SCRIPT_DIR"
docker compose -f docker-compose.infra.yml up -d
log_info "Waiting for Consul..."
for i in $(seq 1 30); do
if curl -sf "http://$CONSUL_HOST:$CONSUL_PORT/v1/status/leader" &>/dev/null; then
break
fi
sleep 2
done
log_ok "Consul ready"
log_info "Waiting for Kafka..."
sleep 15 # Kafka 启动较慢
log_ok "Kafka ready"
log_ok "Phase 3 complete"
# ============================================
# Phase 4: 服务准备
# ============================================
log_info "Phase 4/6: Service preparation"
bash "$SCRIPT_DIR/prepare-services.sh"
bash "$SCRIPT_DIR/build-elevator-v2.sh"
log_ok "Phase 4 complete"
# ============================================
# Phase 5: 服务启动
# ============================================
log_info "Phase 5/6: Service startup"
bash "$SCRIPT_DIR/start-all.sh"
log_ok "Phase 5 complete"
# ============================================
# Phase 6: 验证
# ============================================
log_info "Phase 6/6: Verification"
bash "$SCRIPT_DIR/health-check.sh"
bash "$SCRIPT_DIR/verify-functional.sh"
log_ok "Phase 6 complete"
# ============================================
# 汇总
# ============================================
END_TIME=$(date +%s)
DURATION=$((END_TIME - START_TIME))
cat << SUMMARY
╔══════════════════════════════════════════════╗
║ Setup Complete! ║
║ ║
║ Duration: ${DURATION}s ║
║ Consul: http://$CONSUL_HOST:$CONSUL_PORT/ui/ ║
║ Nginx: http://$CONSUL_HOST:$PORT_NGINX/ ║
║ Elevator V2: http://127.0.0.1:$PORT_ELEVATOR_V2 ║
║ Elevator V1: http://127.0.0.1:$PORT_ELEVATOR_V1 ║
║ CRK std: http://127.0.0.1:$PORT_CRK_STD ║
║ Alarm: http://127.0.0.1:$PORT_ALARM ║
╚══════════════════════════════════════════════╝
To stop: bash scripts/test-env/stop-all.sh && docker compose -f scripts/test-env/docker-compose.infra.yml down
SUMMARY
- Step 2: 端到端测试
bash 源码/scripts/test-env/setup.sh
# 预期: 所有 6 个 Phase 完成, 显示 Setup Complete!
- Step 3: Commit
git add 源码/scripts/test-env/setup.sh
git commit -m "feat: add one-click test environment setup script (setup.sh)"
Task 10: 集成测试 — 完整搭建验证
Files:
-
(无新文件,验证所有脚本正确协作)
-
Step 1: 清理环境
# 停止所有之前的进程
bash 源码/scripts/test-env/stop-all.sh 2>/dev/null || true
docker compose -f 源码/scripts/test-env/docker-compose.infra.yml down -v 2>/dev/null || true
- Step 2: 执行一键搭建
bash 源码/scripts/test-env/setup.sh 2>&1 | tee /tmp/v2test-setup.log
- Step 3: 验证日志
# 检查关键日志标记
grep -c "ERROR" /tmp/v2test-setup.log # 预期: 0
grep "Setup Complete" /tmp/v2test-setup.log # 预期: 找到
grep "Duration:" /tmp/v2test-setup.log # 预期: 找到耗时
- Step 4: 验证关键端口
bash 源码/scripts/test-env/health-check.sh
# 预期: 全部 PASSED, FAILED=0
- Step 5: 验证功能
bash 源码/scripts/test-env/verify-functional.sh
# 预期: 全部通过, 0 failures
- Step 6: 清理
bash 源码/scripts/test-env/stop-all.sh
docker compose -f 源码/scripts/test-env/docker-compose.infra.yml down
- Step 7: Commit (如无代码变更则跳过)
实施顺序依赖
Task 1 (env.sh) ← 所有后续 Task 的基础
↓
Task 2 (docker-compose) ← 可并行
↓
Task 3 (prepare-db.sh) ← 依赖 MySQL 地址 (env.sh)
Task 4 (templates + extract) ← 依赖 env.sh
↓
Task 5 (build V2) ← 依赖 env.sh
↓
Task 6 (start-all/stop-all) ← 依赖 Task 2,3,4,5
Task 7 (health-check) ← 独立
Task 8 (verify-functional) ← 独立 (但运行需服务已启动)
↓
Task 9 (setup.sh) ← 依赖 Task 2-8
↓
Task 10 (integration test) ← 依赖 Task 9
可并行执行: Task 3, 4, 5 (均只依赖 Task 1);Task 6, 7, 8 (均只依赖 Task 1-5)