Files
starRiverProperty/docs/superpowers/plans/2026-05-01-v2-test-env-setup.md
T
hpd840321 7b2bd307f1 Initial commit: reorganized source tree
- 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.
2026-05-09 09:56:45 +08:00

1307 lines
40 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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: 创建目录结构**
```bash
mkdir -p 源码/scripts/test-env/config/service-templates
```
- [ ] **Step 2: 编写 env.sh**
编辑 `源码/scripts/test-env/config/env.sh`,内容如下:
```bash
#!/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
bash -c 'source 源码/scripts/test-env/config/env.sh && echo "MYSQL=$MYSQL_HOST:$MYSQL_PORT"'
# 预期: MYSQL=192.168.3.12:3307
```
- [ ] **Step 4: Commit**
```bash
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**
```yaml
# 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 语法**
```bash
cd 源码/scripts/test-env
source config/env.sh
docker compose -f docker-compose.infra.yml config --quiet
# 预期: 无输出 (语法正确)
```
- [ ] **Step 3: 启动并验证所有容器**
```bash
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**
```bash
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: 编写数据库恢复脚本**
```bash
#!/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
bash 源码/scripts/test-env/prepare-db.sh
# 预期: MySQL connection OK, 然后恢复各个库
```
- [ ] **Step 3: Commit**
```bash
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`:
```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`:
```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`:
```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`:
```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`:
```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 (解压 + 配置注入)**
```bash
#!/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
bash 源码/scripts/test-env/prepare-services.sh
# 预期: 解压 6 个 tar.gz, 生成 5 个配置文件
```
- [ ] **Step 7: Commit**
```bash
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 编译脚本**
```bash
#!/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
bash 源码/scripts/test-env/build-elevator-v2.sh
# 预期: BUILD SUCCESS, JAR synced
```
- [ ] **Step 3: Commit**
```bash
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**
```bash
#!/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**
```bash
#!/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: 测试启动/停止流程**
```bash
# 先确保 Docker infra 已启动
# 然后:
bash 源码/scripts/test-env/start-all.sh
sleep 30
bash 源码/scripts/test-env/stop-all.sh
```
- [ ] **Step 4: Commit**
```bash
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: 编写探活脚本**
```bash
#!/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
bash 源码/scripts/test-env/health-check.sh
```
- [ ] **Step 3: Commit**
```bash
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: 编写功能验证脚本**
```bash
#!/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
bash 源码/scripts/test-env/verify-functional.sh
```
- [ ] **Step 3: Commit**
```bash
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: 编写主入口脚本**
```bash
#!/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
bash 源码/scripts/test-env/setup.sh
# 预期: 所有 6 个 Phase 完成, 显示 Setup Complete!
```
- [ ] **Step 3: Commit**
```bash
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
# 停止所有之前的进程
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
bash 源码/scripts/test-env/setup.sh 2>&1 | tee /tmp/v2test-setup.log
```
- [ ] **Step 3: 验证日志**
```bash
# 检查关键日志标记
grep -c "ERROR" /tmp/v2test-setup.log # 预期: 0
grep "Setup Complete" /tmp/v2test-setup.log # 预期: 找到
grep "Duration:" /tmp/v2test-setup.log # 预期: 找到耗时
```
- [ ] **Step 4: 验证关键端口**
```bash
bash 源码/scripts/test-env/health-check.sh
# 预期: 全部 PASSED, FAILED=0
```
- [ ] **Step 5: 验证功能**
```bash
bash 源码/scripts/test-env/verify-functional.sh
# 预期: 全部通过, 0 failures
```
- [ ] **Step 6: 清理**
```bash
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)