mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-10 00:40:30 +08:00
43c7da2300
- 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
328 lines
12 KiB
Markdown
328 lines
12 KiB
Markdown
# 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 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 完成后立即执行。
|