Files
starRiverProperty/docs/superpowers/plans/2026-05-02-full-deploy-and-compare.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

12 KiB
Raw Blame History

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 V2Feign 路由到 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 完成后立即执行。