- Add full-deploy-and-compare implementation plan - Add application-test.properties (generated test config template) - Ignore scripts/test-env/logs/, services/, *.jar.bak Former-commit-id: d8139e3dc14d0b00f5b9d7fd74b9f45b6db0c84f
12 KiB
V2 完整环境部署 + V1/V2 对比测试 — 实施计划
For agentic workers: REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development or superpowers:executing-plans. Steps use checkbox (
- [ ]) syntax.
Goal: 搭建 V2 电梯功能测试所需的最小服务集(infra + 3 Java 服务),运行 V1/V2 API 对拍及策略差异验证。
Architecture: Docker 提供 Consul/Redis/Nginx,复用 MySQL 192.168.3.12:3307,按 ninca-common → component-org → elevator V2 → elevator V1 顺序启动,最终执行 pytest 对拍 + curl 策略测试。
Tech Stack: Bash, Docker Compose v2, JDK 8, Maven 3.9, Python 3.10 + pytest, MySQL 5.7
关联 Spec: docs/superpowers/specs/2026-05-01-v2-test-env-setup-design.md
前置状态
MySQL: 192.168.3.12:3307 root/123456 ✅
Docker: Compose v2.40.3 可用 ✅
JDK 8: /usr/lib/jvm/java-8-openjdk-amd64 ✅
部署包: 13 个 tar.gz, 7.2 GB ✅
DB 数据: 11 个库已恢复, 策略表已创建 ✅
Task 1: 启动基础设施 (Docker Compose)
Files:
-
Use:
scripts/test-env/docker-compose.infra.yml -
Use:
scripts/test-env/config/env.sh -
Step 1: 清理旧容器并启动
source scripts/test-env/config/env.sh
cd scripts/test-env
docker rm -f v2test-consul v2test-redis v2test-nginx 2>/dev/null || true
docker compose -f docker-compose.infra.yml down --remove-orphans 2>/dev/null || true
docker compose -f docker-compose.infra.yml up -d
sleep 5
curl -sf http://127.0.0.1:9517/v1/status/leader && echo "Consul UP" || echo "FAIL"
redis-cli -p 6380 -a '1qaz!QAZ' PING && echo "Redis UP" || echo "FAIL"
- Step 2: 验证 MySQL
mysql -h 192.168.3.12 -P 3307 -u root -p123456 -e "SELECT 1" && echo "MySQL UP"
Task 2: 启动 ninca-common (port 33010)
Files:
- Create:
scripts/test-env/config/ninca-common-override.properties - Use:
services/ninca-common/ninca_common_01-ninca_common_backend/ninca-common-backend-V2.9.2_20210730.jar
关键: ninca-common 用 ShardingSphere,需覆盖 spring.shardingsphere.datasource.ds0.jdbc-url
- Step 1: 创建 ShardingSphere 覆盖配置
source scripts/test-env/config/env.sh
cat > $TEST_ENV_DIR/config/ninca-common-override.properties << EOF
spring.shardingsphere.datasource.names=ds0
spring.shardingsphere.datasource.ds0.type=com.zaxxer.hikari.HikariDataSource
spring.shardingsphere.datasource.ds0.driver-class-name=com.mysql.jdbc.Driver
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://$MYSQL_HOST:$MYSQL_PORT/ninca_common?useSSL=false&characterEncoding=utf-8&serverTimezone=Asia/Shanghai
spring.shardingsphere.datasource.ds0.username=$MYSQL_USER
spring.shardingsphere.datasource.ds0.password=$MYSQL_PASS
spring.shardingsphere.sharding.default-data-source-name=ds0
EOF
- Step 2: 启动
NC_JAR="$SERVICE_DIR/ninca-common/ninca_common_01-ninca_common_backend/ninca-common-backend-V2.9.2_20210730.jar"
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx1024m "$NC_JAR" \
--server.port=33010 \
--spring.config.additional-location="$TEST_ENV_DIR/config/ninca-common-override.properties" \
--spring.cloud.consul.host=$CONSUL_HOST --spring.cloud.consul.port=$CONSUL_PORT \
--spring.redis.host=$REDIS_HOST --spring.redis.port=$REDIS_PORT --spring.redis.password=$REDIS_PASS \
&> /tmp/ninca-common-plan.log &
echo "PID=$!"
sleep 30
curl -sf http://127.0.0.1:33010/health && echo " ✅" || echo " ❌"
- Step 3: 如启动失败 → Python stub
# 若 ninca-common 无法启动,创建 stub 模拟 person 属性查询
# 组件 org 的 person/detail 内部调 ninca-common,可用空响应 stub 绕过
Task 3: 启动 component-organization (port 33011)
Files:
-
Use:
services/component-org/.../ninca-common-component-organization-V2.9.2_20210730.jar -
Step 1: 确保 Quartz 表存在
mysql -h 192.168.3.12 -P 3307 -u root -p123456 -e "
CREATE TABLE IF NOT EXISTS \`component-organization\`.QRTZ_LOCKS (
SCHED_NAME VARCHAR(120) NOT NULL, LOCK_NAME VARCHAR(40) NOT NULL,
PRIMARY KEY (SCHED_NAME, LOCK_NAME)
) ENGINE=InnoDB;" 2>/dev/null
- Step 2: 启动 (带 ninca-common Ribbon 路由)
COMP_JAR="$SERVICE_DIR/component-org/ninca_common_component_organization_01-ninca_common_component_organization/ninca-common-component-organization-V2.9.2_20210730.jar"
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -Dlogging.config=/tmp/logback-comp-org.xml -jar -Xmx1024m "$COMP_JAR" \
--server.port=33011 \
--spring.datasource.url="jdbc:mysql://$MYSQL_HOST:$MYSQL_PORT/component-organization?useSSL=false&characterEncoding=utf-8" \
--spring.datasource.username=$MYSQL_USER --spring.datasource.password=$MYSQL_PASS \
--spring.cloud.consul.host=$CONSUL_HOST --spring.cloud.consul.port=$CONSUL_PORT \
--spring.redis.host=$REDIS_HOST --spring.redis.port=$REDIS_PORT --spring.redis.password=$REDIS_PASS \
--ninca-common.ribbon.listOfServers=127.0.0.1:33010 \
&> /tmp/comp-org-plan.log &
echo "PID=$!"
sleep 30
curl -sf http://127.0.0.1:17116/actuator/health | python3 -c "import sys,json;print(json.load(sys.stdin)['status'])" && echo " ✅"
- Step 3: 验证 person/detail
curl -sf -X POST http://127.0.0.1:33011/component/person/detail \
-H "Content-Type: application/json" \
-d '{"id":"1072908835884208128"}' | python3 -c "import sys,json;d=json.load(sys.stdin);print(f'code={d[\"code\"]}')"
# 预期: code=00000000 (非 53014011)
- Step 4: 如果 still 53014011
放弃启动 ninca-common,改为 Python stub 方案:
- 创建
scripts/test-env/stub-person-service.py - 监听 33010,响应
/health和任意 person 查询 - 重启 comp-org(它只需 ninca-common 可达,不关心响应内容)
Task 4: 启动 elevator V2 (port 18081)
Files:
-
Use:
maven-cw-elevator-application/deploy/v2-maven/cw-elevator-application-2.0.9.jar -
Step 1: 重启 V2 带全量 Ribbon 路由
pkill -f "elevator.*18081" 2>/dev/null; sleep 2
source scripts/test-env/config/env.sh
DEPLOY_DIR="$REPO_ROOT/maven-cw-elevator-application/deploy/v2-maven"
V2_JAR=$(ls -t "$DEPLOY_DIR"/cw-elevator-application-*.jar | head -1)
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx2048m "$V2_JAR" \
--server.port=18081 --spring.redis.port=6380 --spring.redis.password='1qaz!QAZ' \
--spring.cloud.consul.host=127.0.0.1 --spring.cloud.consul.port=9517 \
--ninca-common-component-organization.ribbon.listOfServers=127.0.0.1:33011 \
--spring.config.location="$DEPLOY_DIR/" \
&> /tmp/v2-plan.log &
echo "PID=$!"
sleep 35
curl -sf http://127.0.0.1:18081/health && echo " ✅"
Task 5: 启动 elevator V1 (port 18080)
Files:
-
Use:
maven-cw-elevator-application/deploy/v1-legacy/cw-elevator-application-V1.0.0.20211103.jar -
Step 1: 启动 V1
pkill -f "elevator.*18080" 2>/dev/null; sleep 2
source scripts/test-env/config/env.sh
V1_DIR="$REPO_ROOT/maven-cw-elevator-application/deploy/v1-legacy"
V1_JAR=$(ls -t "$V1_DIR"/cw-elevator-application-*.jar | head -1)
nohup /usr/lib/jvm/java-8-openjdk-amd64/bin/java -jar -Xmx2048m "$V1_JAR" \
--server.port=18080 --spring.redis.port=6380 --spring.redis.password='1qaz!QAZ' \
--spring.cloud.consul.host=127.0.0.1 --spring.cloud.consul.port=9517 \
--ninca-common-component-organization.ribbon.listOfServers=127.0.0.1:33011 \
--spring.config.location="$V1_DIR/" \
&> /tmp/v1-plan.log &
echo "PID=$!"
sleep 35
curl -sf http://127.0.0.1:18080/health && echo " ✅"
Task 6: 对拍测试
Files:
-
Use:
maven-cw-elevator-application/tools/elevator_api_parity/ -
Step 1: 运行全量对拍
cd maven-cw-elevator-application/tools/elevator_api_parity
ELEVATOR_BASE_OLD=http://127.0.0.1:18080 ELEVATOR_BASE_NEW=http://127.0.0.1:18081 \
python3 -m pytest tests/ -v --tb=short -p no:allure_pytest
# 预期: 8/8 passed
Task 7: 策略差异验证
Files:
-
DB:
tenant_visitor_floor_policy(广发基金 org_id=488b8ad049bb43408a6fbcc50bcb89ac) -
人员:
1072908835884208128(秦夏) -
Step 1: V1 add/visitor (无策略 → floorList 全集)
curl -s -X POST http://127.0.0.1:18080/elevator/person/add/visitor \
-H "Content-Type: application/json" \
-d '{"personId":"1072908835884208128","businessId":"2524639890ba4f2cba9ba1a4eeaa4015","visitorName":"test","phone":"13800000000"}'
# 预期: code != 76260532, 使用 floorList 全集
- Step 2: V2 add/visitor (策略 → allow_zone_ids 替换)
curl -s -X POST http://127.0.0.1:18081/elevator/person/add/visitor \
-H "Content-Type: application/json" \
-d '{"personId":"1072908835884208128","businessId":"2524639890ba4f2cba9ba1a4eeaa4015","visitorName":"test","phone":"13800000000"}'
# 预期: 策略生效 → 返回 allow_zone_ids 交集 或 floorList 全集 (V1 无策略)
- Step 3: 差异判定
# V1_CODE != V2_CODE → STRATEGY DIVERGENCE CONFIRMED
# V1_CODE == V2_CODE → policy not triggered (check comp-org person/detail)
Task 8: 失败回退 — Python stub 方案
如果 Task 2-3 的 Java 服务无法启动:
Files:
-
Create:
scripts/test-env/stub-person-service.py -
Step 1: 创建 stub (模拟 ninca-common + comp-org)
#!/usr/bin/env python3
"""Stub: 模拟 component-organization person/detail,返回广发基金秦夏数据"""
from http.server import HTTPServer, BaseHTTPRequestHandler
import json
class PersonStub(BaseHTTPRequestHandler):
def do_POST(self):
body_len = int(self.headers.get('Content-Length', 0))
body = json.loads(self.rfile.read(body_len)) if body_len > 0 else {}
person_id = body.get('id', body.get('personId', 'unknown'))
if self.path in ('/health', '/actuator/health'):
self._json(200, {"status": "UP"})
elif '/person/detail' in self.path or '/component/person/detail' in self.path:
self._json(200, {
"code": "00000000", "success": True, "message": "success",
"data": {
"id": person_id, "name": "秦夏", "businessId": "2524639890ba4f2cba9ba1a4eeaa4015",
"phone": "13666667067",
"organizationIds": ["488b8ad049bb43408a6fbcc50bcb89ac"],
"floorList": ["605560541473144832", "605560541657694208"],
"labelIds": [], "labelNames": []
}
})
elif '/component/person/page' in self.path:
self._json(200, {"code": "00000000", "success": True, "data": {"datas": [], "total": 0}})
else:
self._json(200, {"code": "00000000", "success": True, "data": None})
def do_GET(self):
self._json(200, {"status": "UP"})
def _json(self, status, data):
self.send_response(status)
self.send_header('Content-Type', 'application/json;charset=utf-8')
self.end_headers()
self.wfile.write(json.dumps(data, ensure_ascii=False).encode())
HTTPServer(('127.0.0.1', 33010), PersonStub).serve_forever()
- Step 2: 启动 stub 并重新配置电梯
python3 scripts/test-env/stub-person-service.py &
echo "Stub PID=$!"
# 重新启动 elevator V2,Feign 路由到 stub:33010
实施依赖
Task 1 (infra) ──→ Task 2 (ninca-common)
↓ ↘ (fail → Task 8 stub)
Task 3 (comp-org)
↓
Task 4 (elevator V2) + Task 5 (elevator V1)
↓
Task 6 (对拍) + Task 7 (策略差异)
Task 4/5 可并行;Task 6/7 可在 4/5 完成后立即执行。