Files
starRiverProperty/docs/superpowers/plans/2026-05-02-full-deploy-and-compare.md
T
反编译工作区 43c7da2300 docs: add v2 deployment plan; gitignore: exclude generated test-env files
- 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
2026-05-03 16:20:08 +08:00

328 lines
12 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 完整环境部署 + 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`
---
## 前置状态
```bash
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: 清理旧容器并启动**
```bash
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**
```bash
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 覆盖配置**
```bash
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: 启动**
```bash
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**
```bash
# 若 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 表存在**
```bash
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 路由)**
```bash
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**
```bash
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 路由**
```bash
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**
```bash
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: 运行全量对拍**
```bash
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 全集)**
```bash
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 替换)**
```bash
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: 差异判定**
```bash
# 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)**
```python
#!/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 并重新配置电梯**
```bash
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 完成后立即执行。