mirror of
https://github.com/hpd840321/starRiverProperty.git
synced 2026-06-09 08:20:31 +08:00
feat(elevator): 对齐 V1 lib 的 Davinci/扫描/事件与部署配置
- davinci-manager-storage:FilePart 路径与基址按 V1 JAR(/portal/file、/part/*、GET /download) - 启动类:扫描 cn.cloudwalk.serial 与 cn.cloudwalk.cwos.client.resource,补 UUIDSerial 与 ApplicationService - deploy:v1/v2 application 中 cloudwalk.serial.enabled、Kafka 指向 192.168.3.12:9092;deploy/.gitignore 忽略日志 - cloudwalk-common-serial:补充 META-INF/spring.factories(Boot 自动配置) - 电梯:Session 配置、Davinci Bean、Feign 包、MQTT/Visitor/Zone Feign;部署脚本与 API parity 工具更新 - 文档与根脚本若干;未纳入大体积 jar/zip 与 v1 CFR 对比目录 Made-with: Cursor Former-commit-id: b76d142d13ebb5c0898de2d9d11bc583876829c2
This commit is contained in:
@@ -43,10 +43,11 @@ management.health.db.enabled=false
|
||||
cloudwalk.datafield.enable=true
|
||||
cloudwalk.datafield.securityKey=d4b2aabc97394a12a27fc3cca6cd9ba1
|
||||
cloudwalk.datafield.encrypt=AES
|
||||
# redis\u914D\u7F6E
|
||||
spring.redis.host=10.128.161.95
|
||||
# redis\u914D\u7F6E\uFF08\u672C\u673A Docker\uFF1Aybs-redis 6379->6379\uFF0C\u82E5\u7528 craftlabs-redis \u6539\u4E3A 6380\uFF09
|
||||
spring.redis.host=127.0.0.1
|
||||
spring.redis.port=6379
|
||||
spring.redis.password=1qaz!QAZ
|
||||
# \u65E0\u5BC6\u7801\u65F6\u4E0D\u914D\u7F6E password \uFF08\u82E5\u5BB9\u5668\u5F00\u4E86\u5BC6\u7801\u8BF7\u53D6\u6D88\u6CE8\u91CA\u5E76\u586B\u5199\uFF09
|
||||
# spring.redis.password=
|
||||
spring.redis.database=5
|
||||
spring.redis.timeout=0
|
||||
spring.redis.pool.max-active=10
|
||||
@@ -57,9 +58,9 @@ spring.redis.pool.min-idle=0
|
||||
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://10.128.161.95:3306/cw-elevator-application?zeroDateTimeBehavior=convertToNull&useUnicode=true&characterEncoding=utf-8&useSSL=false&allowMultiQueries=true
|
||||
spring.shardingsphere.datasource.ds0.jdbc-url=jdbc:mysql://192.168.3.12:3307/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=1qaz!QAZ
|
||||
spring.shardingsphere.datasource.ds0.password=123456
|
||||
spring.shardingsphere.datasource.ds0.connection-timeout=60000
|
||||
spring.shardingsphere.datasource.ds0.maximum-pool-size=20
|
||||
spring.shardingsphere.datasource.ds0.minimum-idle=5
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
> **范围**:`/media/zebra/9e8fa357-7db6-4d70-88ed-d5de5a059a663/星河湾星中星/反编译` 下 `maven-*` 目录内全部 `pom.xml`(**主聚合工程 5 个 + 补充反应堆 7 个**,合计 **44** 个 `pom.xml` 文件)。
|
||||
> **说明**:子模块未单独声明 `<version>` 时,与**反应堆(reactor)父 POM** 的 `<version>` 一致。
|
||||
> **生成方式**:走查各 `pom.xml` 的 `parent`、`groupId`、`artifactId`、`version`、`properties` 关键项。
|
||||
> **版本演进(主版本升级)**:相对历史反编译/私服线,本工作区工程坐标已整体抬升主版本号以便区分 —— 电梯 **2.0-SNAPSHOT**、intelligent **3.0.0-xinghewan**、cloudwalk-cloud **4.0.0-Brussels-SRX**、ninca-crk **2.0.0**、ninca-qk-alarm **1.0.0-SNAPSHOT**;`cloudwalk.internal.version` 与 intelligent 依赖属性已同步。
|
||||
> **版本演进(主版本升级)**:相对历史反编译/私服线,本工作区多工程坐标抬升主版本号以便区分。**电梯运行口径**:依赖 **`intelligent-cwoscomponent` 2.9.2-xinghewan**(与 `cw_lib` 一致,**不使用** 3.0.0);**`maven-intelligent-cwoscomponent` 源码反应堆** 仍为 **3.0.0-xinghewan**(与其它工程/历史 3.x 线兼容)。另:cloudwalk-cloud **4.0.0-Brussels-SRX**、ninca-crk **2.0.0**、ninca-qk-alarm **1.0.0-SNAPSHOT**。
|
||||
|
||||
---
|
||||
|
||||
@@ -45,7 +45,7 @@
|
||||
| `cw-elevator-application-service/` | `cw-elevator-application-service` | **2.0-SNAPSHOT**(继承) | 同上 |
|
||||
| `cw-elevator-application-web/` | `cw-elevator-application-web` | **2.0-SNAPSHOT**(继承) | 同上 |
|
||||
|
||||
**反应堆内常用属性(节选)**:`cloudwalk.internal.version` **4.0.0-Brussels-SRX**;`intelligent.cwoscomponent.version` **3.0.0-xinghewan**;`fastjson.version` **1.2.83**;`guava.version` **28.2-jre**;`poi.version` **4.1.2**;`java.version` **1.8**。
|
||||
**反应堆内常用属性(节选)**:`cloudwalk.internal.version` / `cloudwalk.legacy.public.version` **3.7.2-Brussels-SRX**;`intelligent.cwoscomponent.version` **2.9.2-xinghewan**(与 **`cw_lib`** / 上线口径一致);`fastjson.version` **1.2.73**;`guava.version` **28.2-jre**;`poi.version` **4.1.2**;`java.version` **1.8**。
|
||||
|
||||
---
|
||||
|
||||
@@ -187,8 +187,8 @@
|
||||
|
||||
| 概念 | 常见取值 | 出现在 |
|
||||
|------|----------|--------|
|
||||
| 云从内部线版本 | **4.0.0-Brussels-SRX** | 电梯、intelligent、cloudwalk-cloud、ninca-crk、ninca-qk-alarm 的 `cloudwalk.internal.version` 等 |
|
||||
| intelligent 组件线 | **3.0.0-xinghewan** | intelligent 反应堆;电梯 `intelligent.cwoscomponent.version`;ninca-crk `intelligent.cwoscomponent.rest.version` |
|
||||
| 云从内部线版本 | **3.7.2**(电梯常用) / **4.0.0**(部分工程) | 电梯、ninca-qk-alarm 等 `cloudwalk.internal.version` 多为 **3.7.2**;**cloudwalk-cloud / ninca-crk** 等仍可能 **4.0.0** |
|
||||
| intelligent 组件线 | **2.9.2-xinghewan**(电梯) / **3.0.0-xinghewan**(反应堆源码、ninca-crk) | **电梯** `intelligent.cwoscomponent.version` **必须为 2.9.2**(与 `cw_lib` 一致);**`maven-intelligent-cwoscomponent`** 反应堆仍为 **3.0.0**;**ninca-crk** `intelligent.cwoscomponent.rest.version` 多为 **3.0.0** |
|
||||
| 设备 SDK 协议实体 | **2.2.0** | intelligent `cloudwalk.device.sdk.version`;**`maven-cloudwalk-device-sdk`** 反应堆版本 |
|
||||
| 设备管理 common/interface | **2.0.2** | **`maven-cloudwalk-device-manager`**;依赖 **`cloudwalk-common-result` 3.7.2**(**`maven-cloudwalk-legacy-public`**) |
|
||||
| AKS / 设备认证 interface | **1.0.0-SNAPSHOT** | **`maven-cwos-common-aks`**(**`cwos-common-aks-interface`**)、**`maven-cwos-device-authentication`**(**`cwos-device-authentication-interface`**);与 **`cw_lib`** 同名 JAR 对齐;**device-authentication** 另依赖 **`cloudwalk-common-service`**(**`maven-cloudwalk-cloud`**)、**device-manager-interface**、**protocol-entity** |
|
||||
@@ -272,6 +272,7 @@ maven-ninca-qk-alarm/ninca-qk-alarm-app-starter/pom.xml
|
||||
| 2026-04-24 | 补充反应堆 **`maven-cloudwalk-device-manager`**(**`cloudwalk-device-manager` 2.0.2**、**common**、**interface**;**`反1`** zip);`pom` 总数 **37 → 40**;须在 **`maven-cloudwalk-legacy-public`** 之后 install;见 [本地编译说明.md](../build/本地编译说明.md) §3 |
|
||||
| 2026-04-24 | 补充反应堆 **`maven-cwos-common-aks`**、**`maven-cwos-device-authentication`**(**`反1`**:`cwos-common-aks-interface`、`cwos-device-authentication-interface`);`pom` 总数 **40 → 44**;**device-authentication** 须在 **`maven-cloudwalk-cloud`** 与 **`maven-cwos-common-aks`** 之后 install;见 [本地编译说明.md](../build/本地编译说明.md) §3 |
|
||||
| 2026-04-24 | **`intelligent-cwoscomponent-rest`**:`FeignClient` 由 **`org.springframework.cloud.netflix.feign`** 改为 **`org.springframework.cloud.openfeign`**,与 **Greenwich + `spring-cloud-starter-openfeign`** 一致 |
|
||||
| 2026-04-27 | **电梯** `intelligent.cwoscomponent.version` 定为 **2.9.2-xinghewan**(与 **`cw_lib`**、上线口径一致,**禁止 3.0.0**);`build_nexus_only.sh` 改为从 **`cw_lib` + 父 POM 桩** install-file,不再默认编译 **`maven-intelligent-cwoscomponent` 3.0.0** |
|
||||
|
||||
---
|
||||
|
||||
|
||||
Vendored
+3
-3
@@ -28,8 +28,8 @@ java -version # 应显示 1.8.x
|
||||
6. **`maven-cloudwalk-cloud`**:`mvn -DskipTests clean install` — 提供 **`cloudwalk-common-event`**、**`cloudwalk-common-service`** 等(**`maven-cwos-device-authentication`** 依赖 **`cloudwalk-common-service`**,故 **aks / device-authentication 须排在本步之后**)。
|
||||
7. **`maven-cwos-common-aks`**:`mvn -DskipTests clean install` — 自 **`反1/cwos-common-aks-interface-1.0.0-SNAPSHOT.jar.src.zip`** 提供 **`cn.cloudwalk.cloud:cwos-common-aks:1.0.0-SNAPSHOT`** 与 **`cwos-common-aks-interface`**(依赖第 2 步 **`cloudwalk-common-result`**)。
|
||||
8. **`maven-cwos-device-authentication`**:`mvn -DskipTests clean install` — 自 **`反1/cwos-device-authentication-interface-1.0.0-SNAPSHOT.jar.src.zip`** 提供 **`cn.cloudwalk.cloud:cwos-device-authentication:1.0.0-SNAPSHOT`** 与 **`cwos-device-authentication-interface`**(依赖第 1、3、6、7 步及 **`cwos-common-aks-interface`**)。
|
||||
9. **`maven-intelligent-cwoscomponent`**:`mvn -DskipTests clean install`(依赖第 1 步的 **protocol-entity** 与 cloudwalk 模块)
|
||||
10. **`maven-cw-elevator-application`**:`mvn -DskipTests clean install`(`service` 若仍失败,多为其它私服构件或业务源码问题,见下文)
|
||||
9. **`maven-intelligent-cwoscomponent`**:若需维护 **3.0.0-xinghewan** 源码线:`mvn -DskipTests clean install`(依赖第 1 步的 **protocol-entity** 与 cloudwalk 模块)。**仅编电梯**且上线口径为 **`intelligent-cwoscomponent` 2.9.2(`cw_lib`)** 时,**不要**依赖本步;请使用 `maven-cw-elevator-application/scripts/build_nexus_only.sh`(从 **`cw_lib`** 安装 2.9.2 JAR+POM + 父 POM 桩)。
|
||||
10. **`maven-cw-elevator-application`**:`mvn -DskipTests clean install`(或用 **`build_nexus_only.sh`** 隔离本地库 + Nexus;`service` 若仍失败,多为其它私服构件或业务源码问题,见下文)
|
||||
11. 其余:`maven-ninca-crk`、`maven-ninca-qk-alarm` 按需单独编译。
|
||||
|
||||
单工程示例:
|
||||
@@ -156,7 +156,7 @@ mvn -pl cw-elevator-application-service -am -DskipTests clean compile
|
||||
| `cwos-component-resource:pom:1.0.0-SNAPSHOT` absent | 接口包 **`cwos-component-resource-interface`** 所引用的 **父 POM** 未在私服。 |
|
||||
| `cwos-portal:pom:1.0.0-SNAPSHOT` absent | 同上,**`cwos-portal-interface`** 的父工程未发布。 |
|
||||
| `cloudwalk-intelligent-davinci-manager:pom:1.1.7-SNAPSHOT` absent | **`davinci-manager-storage`** 的父 POM 未在私服(或曾失败被缓存)。 |
|
||||
| `The POM for ... intelligent-cwoscomponent-rest:jar:3.0.0-xinghewan is missing` | 因 **6.1** 失败,**interface/rest 未 install** 到本地仓库,电梯解析 `intelligent-cwoscomponent-rest` 时只能报「无 POM/无依赖信息」。 |
|
||||
| `The POM for ... intelligent-cwoscomponent-rest:jar:2.9.2-xinghewan is missing` | **interface/rest** 未写入本地仓库(未从 `cw_lib`/`build_nexus_only.sh` 安装,或 **6.1** 仍失败),电梯解析 `intelligent-cwoscomponent-rest` 时只能报「无 POM/无依赖信息」。
|
||||
|
||||
**结论**:`service` 的失败 **不是** 电梯业务源码语法问题,而是 **Maven 依赖图不完整**(私服缺件 + 上一步 intelligent 未成功)。
|
||||
|
||||
|
||||
+7
-3
@@ -4,6 +4,7 @@ import cn.cloudwalk.intelligent.davinci.common.result.DavinciResult;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartFinishDTO;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartInitDTO;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartInitResultDTO;
|
||||
import feign.Response;
|
||||
import org.springframework.web.bind.annotation.RequestBody;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
@@ -13,14 +14,17 @@ import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface FilePartFeign {
|
||||
|
||||
@RequestMapping(value = { "/init" }, method = { RequestMethod.POST })
|
||||
@RequestMapping(value = { "/part/init" }, method = { RequestMethod.POST })
|
||||
DavinciResult<PartInitResultDTO> init(@RequestBody PartInitDTO paramPartInitDTO);
|
||||
|
||||
@RequestMapping(value = { "/append" }, method = { RequestMethod.POST }, consumes = { "multipart/form-data" })
|
||||
@RequestMapping(value = { "/part/append" }, method = { RequestMethod.POST }, consumes = { "multipart/form-data" })
|
||||
DavinciResult<PartInitResultDTO> append(@RequestParam("filePath") String paramString1,
|
||||
@RequestParam("partNumber") Integer paramInteger, @RequestParam("uploadId") String paramString2,
|
||||
@RequestPart("file") MultipartFile paramMultipartFile);
|
||||
|
||||
@RequestMapping(value = { "/finish" }, method = { RequestMethod.POST })
|
||||
@RequestMapping(value = { "/part/finish" }, method = { RequestMethod.POST })
|
||||
DavinciResult<String> finish(@RequestBody PartFinishDTO paramPartFinishDTO);
|
||||
|
||||
@RequestMapping(value = { "/download" }, method = { RequestMethod.GET })
|
||||
Response bigFileDownload(@RequestParam("path") String path);
|
||||
}
|
||||
|
||||
+3
@@ -4,6 +4,7 @@ import cn.cloudwalk.intelligent.davinci.common.exception.DavinciServiceException
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartFinishDTO;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartInitDTO;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.part.dto.PartInitResultDTO;
|
||||
import java.io.InputStream;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
public interface FilePartManager {
|
||||
@@ -14,4 +15,6 @@ public interface FilePartManager {
|
||||
MultipartFile paramMultipartFile) throws DavinciServiceException;
|
||||
|
||||
String finish(PartFinishDTO paramPartFinishDTO) throws DavinciServiceException;
|
||||
|
||||
InputStream bigFileDownload(String paramString) throws DavinciServiceException;
|
||||
}
|
||||
|
||||
+18
-11
@@ -9,28 +9,22 @@ import cn.cloudwalk.intelligent.davinci.storage.feign.FilePartFeign;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.manager.FilePartManager;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Component
|
||||
@Import({ FeignClientsConfiguration.class })
|
||||
public class FilePartManagerImpl implements FilePartManager {
|
||||
|
||||
private FilePartFeign filePartFeign;
|
||||
private FilePartFeign filePartRestFeign;
|
||||
|
||||
@Autowired
|
||||
public FilePartManagerImpl(@Value("${feign.davinci-portal.name:davinci-portal}") String serviceName,
|
||||
Decoder decoder, Encoder encoder, Client client) {
|
||||
String url = "http://" + serviceName + "/portal/file/part";
|
||||
public FilePartManagerImpl(String serviceName, Decoder decoder, Encoder encoder, Client client) {
|
||||
String url = "http://" + serviceName + "/portal/file";
|
||||
|
||||
this.filePartFeign = Feign.builder().client(client).decode404().encoder(new SpringFormEncoder())
|
||||
.decoder(decoder).contract(new SpringMvcContract()).target(FilePartFeign.class, url);
|
||||
@@ -66,4 +60,17 @@ public class FilePartManagerImpl implements FilePartManager {
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream bigFileDownload(String path) throws DavinciServiceException {
|
||||
try {
|
||||
Response response = this.filePartFeign.bigFileDownload(path);
|
||||
if (response.body() == null) {
|
||||
return null;
|
||||
}
|
||||
return response.body().asInputStream();
|
||||
} catch (IOException e) {
|
||||
throw new DavinciServiceException("", "调用Davinci-portal服务,获取大文件流接口异常");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
-3
@@ -25,15 +25,12 @@ import java.util.Locale;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.openfeign.FeignClientsConfiguration;
|
||||
import org.springframework.cloud.openfeign.support.SpringMvcContract;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
@Component
|
||||
@Import({ FeignClientsConfiguration.class })
|
||||
public class FileStorageManagerImpl implements FileStorageManager {
|
||||
|
||||
private FileManagerFeign fileManagerFeign;
|
||||
|
||||
+5
-2
@@ -6,17 +6,20 @@ import cn.cloudwalk.serial.code.GeneralSerialCode;
|
||||
import cn.cloudwalk.serial.code.MacGeneralSerial;
|
||||
import cn.cloudwalk.serial.code.RedisGeneralCode;
|
||||
import cn.cloudwalk.serial.redis.CloudwalkRedisService;
|
||||
import cn.cloudwalk.serial.strategy.ServerIdStrategyBeanConfig;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.data.redis.core.RedisTemplate;
|
||||
|
||||
@Configuration
|
||||
@EnableConfigurationProperties({ CloudwalkSerialProperties.class })
|
||||
@ConditionalOnProperty(prefix = "cloudwalk.serial", value = { "enabled" }, havingValue = "true", matchIfMissing = true)
|
||||
@Import({ServerIdStrategyBeanConfig.class, CloudwalkSnowflakeConfiguration.class})
|
||||
@EnableConfigurationProperties({CloudwalkSerialProperties.class})
|
||||
@ConditionalOnProperty(prefix = "cloudwalk.serial", value = {"enabled"}, havingValue = "true", matchIfMissing = true)
|
||||
public class CloudwalkSerialAutoConfiguration {
|
||||
@Autowired(required = false)
|
||||
private RedisTemplate<String, String> redisTemplate;
|
||||
|
||||
+3
@@ -0,0 +1,3 @@
|
||||
# Spring Boot 2.1:注册序列号与 Snowflake 相关自动配置(此前缺失导致 AbstractGeneralCode 等 Bean 未创建)
|
||||
org.springframework.boot.autoconfigure.EnableAutoConfiguration=\
|
||||
cn.cloudwalk.serial.autoconfig.serial.CloudwalkSerialAutoConfiguration
|
||||
@@ -1,2 +1,5 @@
|
||||
# 发布目录中的可执行 JAR 体积大,默认不纳入 Git;说明与清单可单独跟踪。
|
||||
releases/**/*.jar
|
||||
|
||||
# scripts/build_nexus_only.sh 使用的隔离 Maven 本地仓库(仅 Nexus 依赖缓存)
|
||||
.m2-elevator-nexus-only/
|
||||
|
||||
@@ -58,6 +58,15 @@
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cloudwalk-common-web</artifactId>
|
||||
</dependency>
|
||||
<!-- cloudwalk-common-web 在部分私服 POM 上传递依赖不完整;隔离仓库构建时显式补齐编译 classpath -->
|
||||
<dependency>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-autoconfigure</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.zxing</groupId>
|
||||
<artifactId>core</artifactId>
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
<?xml version="1.0" encoding="UTF-8" ?>
|
||||
<!DOCTYPE configuration
|
||||
PUBLIC "-//mybatis.org//DTD Config 3.0//EN"
|
||||
"http://mybatis.org/dtd/mybatis-3-config.dtd">
|
||||
<configuration>
|
||||
|
||||
<!--<settings>
|
||||
<setting name="logImpl" value="STDOUT_LOGGING"/>
|
||||
</settings>-->
|
||||
|
||||
<plugins>
|
||||
<plugin interceptor="com.github.pagehelper.PageInterceptor"/>
|
||||
</plugins>
|
||||
|
||||
</configuration>
|
||||
@@ -56,6 +56,10 @@
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cloudwalk-common-event</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cwos-sdk-event</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.elevator</groupId>
|
||||
<artifactId>cw-elevator-application-data</artifactId>
|
||||
@@ -73,6 +77,14 @@
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-starter-openfeign</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
package cn.cloudwalk.elevator;
|
||||
|
||||
import cn.cloudwalk.cloud.context.CloudwalkSessionContextHolder;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
|
||||
/** 未扫描 {@code cn.cloudwalk.web} 时,等价于 LocaleConfiguration 中的 SessionHolder Bean。 */
|
||||
@Configuration
|
||||
public class CloudwalkSessionHolderConfiguration {
|
||||
|
||||
@Bean
|
||||
public CloudwalkSessionContextHolder cloudwalkSessionContextHolder() {
|
||||
return new CloudwalkSessionContextHolder();
|
||||
}
|
||||
}
|
||||
+1
-1
@@ -4,7 +4,7 @@ import cn.cloudwalk.cloud.exception.ServiceException;
|
||||
import cn.cloudwalk.cloud.result.CloudwalkResult;
|
||||
import cn.cloudwalk.elevator.mqtt.fallback.MqttFeignClientFallback;
|
||||
import cn.cloudwalk.elevator.mqtt.param.MqttSendMessageParam;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
|
||||
+1
-1
@@ -5,7 +5,7 @@ import cn.cloudwalk.cloud.result.CloudwalkResult;
|
||||
import cn.cloudwalk.elevator.visitor.fallback.VisitorFeignClientFallback;
|
||||
import cn.cloudwalk.elevator.visitor.param.VisitorRecordQueryParam;
|
||||
import cn.cloudwalk.elevator.visitor.result.VisitorQueryResult;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
|
||||
+1
-1
@@ -9,7 +9,7 @@ import cn.cloudwalk.elevator.zone.param.ZoneQueryParam;
|
||||
import cn.cloudwalk.elevator.zone.result.ZoneResult;
|
||||
import cn.cloudwalk.elevator.zone.result.ZoneTreeResult;
|
||||
import java.util.List;
|
||||
import org.springframework.cloud.openfeign.FeignClient;
|
||||
import org.springframework.cloud.netflix.feign.FeignClient;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.RequestMethod;
|
||||
|
||||
|
||||
+21
-4
@@ -6,7 +6,7 @@ import org.mybatis.spring.annotation.MapperScan;
|
||||
import org.springframework.boot.SpringApplication;
|
||||
import org.springframework.boot.autoconfigure.SpringBootApplication;
|
||||
import org.springframework.cache.annotation.EnableCaching;
|
||||
import org.springframework.cloud.openfeign.EnableFeignClients;
|
||||
import org.springframework.cloud.netflix.feign.EnableFeignClients;
|
||||
import org.springframework.context.annotation.EnableAspectJAutoProxy;
|
||||
import org.springframework.scheduling.annotation.EnableAsync;
|
||||
|
||||
@@ -14,9 +14,26 @@ import org.springframework.scheduling.annotation.EnableAsync;
|
||||
@EnableAsync
|
||||
@EnableCaching
|
||||
@EnableAspectJAutoProxy(exposeProxy = true)
|
||||
@EnableFeignClients(basePackages = "cn.cloudwalk.elevator")
|
||||
@MapperScan("cn.cloudwalk.elevator")
|
||||
@SpringBootApplication(exclude = {PageHelperAutoConfiguration.class})
|
||||
@EnableFeignClients(basePackages = {
|
||||
"cn.cloudwalk.elevator",
|
||||
"cn.cloudwalk.rest.cwoscomponent",
|
||||
"cn.cloudwalk.cwos.client.resource"
|
||||
})
|
||||
@MapperScan({
|
||||
"cn.cloudwalk.elevator.record.mapper",
|
||||
"cn.cloudwalk.elevator.device.mapper",
|
||||
"cn.cloudwalk.elevator.passrule.mapper",
|
||||
"cn.cloudwalk.elevator.person.mapper",
|
||||
"cn.cloudwalk.elevator.codeElevatorArea.mapper"
|
||||
})
|
||||
@SpringBootApplication(
|
||||
exclude = {PageHelperAutoConfiguration.class},
|
||||
scanBasePackages = {
|
||||
"cn.cloudwalk.elevator",
|
||||
"cn.cloudwalk.rest.cwoscomponent",
|
||||
"cn.cloudwalk.serial",
|
||||
"cn.cloudwalk.cwos.client.resource"
|
||||
})
|
||||
public class ElevatorApplication {
|
||||
|
||||
public static void main(String[] args) {
|
||||
|
||||
+37
@@ -0,0 +1,37 @@
|
||||
package cn.cloudwalk.elevator.config;
|
||||
|
||||
import cn.cloudwalk.elevator.integration.davinci.OpenFeignFileStorageManager;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.manager.FilePartManager;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.manager.FileStorageManager;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.manager.impl.FilePartManagerImpl;
|
||||
import feign.Client;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.cloud.netflix.feign.FeignClientsConfiguration;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.context.annotation.Import;
|
||||
|
||||
@Configuration
|
||||
@Import(FeignClientsConfiguration.class)
|
||||
public class DavinciStorageBeansConfiguration {
|
||||
|
||||
@Bean
|
||||
public FileStorageManager fileStorageManager(
|
||||
@Value("${feign.davinci-portal.name:davinci-portal}") String serviceName,
|
||||
Decoder decoder,
|
||||
Encoder encoder,
|
||||
Client client) {
|
||||
return new OpenFeignFileStorageManager(serviceName, decoder, encoder, client);
|
||||
}
|
||||
|
||||
@Bean
|
||||
public FilePartManager filePartManager(
|
||||
@Value("${feign.davinci-portal.name:davinci-portal}") String serviceName,
|
||||
Decoder decoder,
|
||||
Encoder encoder,
|
||||
Client client) {
|
||||
return new FilePartManagerImpl(serviceName, decoder, encoder, client);
|
||||
}
|
||||
}
|
||||
+228
@@ -0,0 +1,228 @@
|
||||
package cn.cloudwalk.elevator.integration.davinci;
|
||||
|
||||
import cn.cloudwalk.intelligent.davinci.common.exception.DavinciServiceException;
|
||||
import cn.cloudwalk.intelligent.davinci.common.result.DavinciResult;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.bean.file.dto.FileRemoveDTO;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.feign.FileManagerFeign;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.feign.OuterCallFeignClient;
|
||||
import cn.cloudwalk.intelligent.davinci.storage.manager.FileStorageManager;
|
||||
import feign.Client;
|
||||
import feign.Feign;
|
||||
import feign.Response;
|
||||
import feign.codec.Decoder;
|
||||
import feign.codec.Encoder;
|
||||
import feign.form.spring.SpringFormEncoder;
|
||||
import feign.okhttp.OkHttpClient;
|
||||
import java.io.FilterInputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URI;
|
||||
import java.net.URISyntaxException;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import org.apache.commons.io.IOUtils;
|
||||
import org.springframework.cloud.netflix.feign.support.SpringMvcContract;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.multipart.MultipartFile;
|
||||
|
||||
/**
|
||||
* 与 davinci-manager-storage 中逻辑一致,但固定使用 {@link SpringMvcContract}(OpenFeign)。
|
||||
* 避免依赖 Nexus 仍带 Netflix 引用的旧 {@code FileStorageManagerImpl} 字节码导致 NoClassDefFoundError。
|
||||
*/
|
||||
public class OpenFeignFileStorageManager implements FileStorageManager {
|
||||
|
||||
private final FileManagerFeign fileManagerFeign;
|
||||
private final FileManagerFeign fileManagerRestFeign;
|
||||
|
||||
public OpenFeignFileStorageManager(String serviceName, Decoder decoder, Encoder encoder, Client client) {
|
||||
String url = "http://" + serviceName + "/portal/fileManager";
|
||||
|
||||
this.fileManagerFeign = Feign.builder().client(client).decode404().encoder(new SpringFormEncoder())
|
||||
.decoder(decoder).contract(new SpringMvcContract()).target(FileManagerFeign.class, url);
|
||||
|
||||
this.fileManagerRestFeign = Feign.builder().client(client).decode404().encoder(encoder).decoder(decoder)
|
||||
.contract(new SpringMvcContract()).target(FileManagerFeign.class, url);
|
||||
}
|
||||
|
||||
static void assertSafeHttpUrl(String urlString) throws DavinciServiceException {
|
||||
if (StringUtils.isEmpty(urlString)) {
|
||||
throw new DavinciServiceException("INVALID_URL", "URL 为空");
|
||||
}
|
||||
URI uri;
|
||||
try {
|
||||
uri = new URI(urlString);
|
||||
} catch (URISyntaxException e) {
|
||||
throw new DavinciServiceException("INVALID_URL", "URL 非法");
|
||||
}
|
||||
String scheme = uri.getScheme();
|
||||
if (scheme == null || (!"http".equalsIgnoreCase(scheme) && !"https".equalsIgnoreCase(scheme))) {
|
||||
throw new DavinciServiceException("INVALID_URL", "仅允许 http 或 https 协议");
|
||||
}
|
||||
String host = uri.getHost();
|
||||
if (StringUtils.isEmpty(host)) {
|
||||
throw new DavinciServiceException("INVALID_URL", "缺少主机名");
|
||||
}
|
||||
String lowerHost = host.toLowerCase(Locale.ROOT);
|
||||
if ("localhost".equals(lowerHost) || lowerHost.endsWith(".local")) {
|
||||
throw new DavinciServiceException("INVALID_URL", "禁止访问该主机");
|
||||
}
|
||||
if ("metadata.google.internal".equalsIgnoreCase(host)) {
|
||||
throw new DavinciServiceException("INVALID_URL", "禁止访问该主机");
|
||||
}
|
||||
try {
|
||||
InetAddress[] all = InetAddress.getAllByName(host);
|
||||
for (InetAddress addr : all) {
|
||||
if (addr.isAnyLocalAddress() || addr.isLoopbackAddress() || addr.isLinkLocalAddress()
|
||||
|| addr.isSiteLocalAddress() || addr.isMulticastAddress()) {
|
||||
throw new DavinciServiceException("INVALID_URL", "禁止访问内网或保留地址");
|
||||
}
|
||||
}
|
||||
} catch (UnknownHostException e) {
|
||||
throw new DavinciServiceException("INVALID_URL", "无法解析主机");
|
||||
}
|
||||
}
|
||||
|
||||
private static void requireDavinciResult(DavinciResult<?> result, String op) throws DavinciServiceException {
|
||||
if (result == null) {
|
||||
throw new DavinciServiceException("NULL_RESULT", "Davinci-portal 返回空结果: " + op);
|
||||
}
|
||||
}
|
||||
|
||||
private static InputStream attachResponseClose(InputStream bodyStream, Response response) {
|
||||
return new FilterInputStream(bodyStream) {
|
||||
@Override
|
||||
public void close() throws IOException {
|
||||
try {
|
||||
super.close();
|
||||
} finally {
|
||||
response.close();
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fileUpload(MultipartFile file) throws DavinciServiceException {
|
||||
DavinciResult<String> result = this.fileManagerFeign.fileUpload(file);
|
||||
requireDavinciResult(result, "fileUpload");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String fileUpload(String moduleCategory, MultipartFile file) throws DavinciServiceException {
|
||||
DavinciResult<String> result = this.fileManagerFeign.fileUpload(moduleCategory, file);
|
||||
requireDavinciResult(result, "fileUpload(module)");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String bigFileUpload(MultipartFile file) throws DavinciServiceException {
|
||||
DavinciResult<String> result = this.fileManagerFeign.bigFileUpload(file);
|
||||
requireDavinciResult(result, "bigFileUpload");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public String bigFileUpload(String moduleCategory, MultipartFile file) throws DavinciServiceException {
|
||||
DavinciResult<String> result = this.fileManagerFeign.bigFileUpload(moduleCategory, file);
|
||||
requireDavinciResult(result, "bigFileUpload(module)");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public byte[] fileDownload(String path) throws DavinciServiceException {
|
||||
try (Response response = this.fileManagerFeign.fileDownload(path)) {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
if (response.body() == null) {
|
||||
return null;
|
||||
}
|
||||
try (InputStream inputStream = response.body().asInputStream()) {
|
||||
return IOUtils.toByteArray(inputStream);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
throw new DavinciServiceException("FILE_DOWNLOAD_IO", "调用Davinci-portal服务,获取文件流接口异常");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream fileDownloadStream(String path) throws DavinciServiceException {
|
||||
Response response = this.fileManagerFeign.fileDownload(path);
|
||||
try {
|
||||
if (response == null) {
|
||||
return null;
|
||||
}
|
||||
if (response.body() == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
return attachResponseClose(response.body().asInputStream(), response);
|
||||
} catch (IOException e) {
|
||||
if (response != null) {
|
||||
response.close();
|
||||
}
|
||||
throw new DavinciServiceException("FILE_DOWNLOAD_IO", "调用Davinci-portal服务,获取文件流接口异常");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getFileBase64(String path) throws DavinciServiceException {
|
||||
if (StringUtils.isEmpty(path)) {
|
||||
return "";
|
||||
}
|
||||
DavinciResult<String> result = this.fileManagerFeign.getFileData(path);
|
||||
requireDavinciResult(result, "getFileData");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<String> remove(FileRemoveDTO dto) throws DavinciServiceException {
|
||||
DavinciResult<List<String>> result = this.fileManagerRestFeign.remove(dto);
|
||||
requireDavinciResult(result, "remove");
|
||||
if (result.isSuccess()) {
|
||||
return result.getData();
|
||||
}
|
||||
throw new DavinciServiceException(result.getCode(), result.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public InputStream fileDownLoadWithAbsoluteUrl(String url) throws DavinciServiceException {
|
||||
assertSafeHttpUrl(url);
|
||||
OuterCallFeignClient feignClient = Feign.builder().client(new OkHttpClient()).target(OuterCallFeignClient.class,
|
||||
url);
|
||||
Response response;
|
||||
try {
|
||||
response = feignClient.downLoad();
|
||||
} catch (RuntimeException e) {
|
||||
throw new DavinciServiceException("OUTER_DOWNLOAD", "拉取远程文件失败");
|
||||
}
|
||||
try {
|
||||
if (response.body() == null) {
|
||||
response.close();
|
||||
return null;
|
||||
}
|
||||
return attachResponseClose(response.body().asInputStream(), response);
|
||||
} catch (IOException e) {
|
||||
response.close();
|
||||
throw new DavinciServiceException("OUTER_DOWNLOAD", "读取远程文件流失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<configuration debug="false" scan="true">
|
||||
<property name="CONTEXT_NAME" value="api.1.0.0"/>
|
||||
<contextName>${CONTEXT_NAME}</contextName>
|
||||
|
||||
<springProperty scope="context" name="fileName" source="logging.file" defaultValue="default"/>
|
||||
|
||||
<!--myibatis log configure-->
|
||||
<logger name="com.apache.ibatis" level="DEBUG"/>
|
||||
<logger name="java.sql.Connection" level="DEBUG"/>
|
||||
<logger name="java.sql.Statement" level="DEBUG"/>
|
||||
<logger name="java.sql.PreparedStatement" level="DEBUG"/>
|
||||
|
||||
<!-- 控制台输出 -->
|
||||
<appender name="S" class="ch.qos.logback.core.ConsoleAppender">
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{50}:%line - %msg%n</pattern>
|
||||
<!-- 设置字符集 -->
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
</appender>
|
||||
|
||||
<!-- 按照每天生成日志文件 -->
|
||||
<appender name="R" class="ch.qos.logback.core.rolling.RollingFileAppender">
|
||||
<File>${LOG_PATH}/${fileName}.log</File>
|
||||
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
|
||||
<!--日志文件输出的文件名-->
|
||||
<FileNamePattern>${LOG_PATH}/${fileName}.%d{yyyy-MM-dd}.%i.log</FileNamePattern>
|
||||
<!--日志文件保留天数-->
|
||||
<MaxHistory>7</MaxHistory>
|
||||
<!--日志文件大小 -->
|
||||
<timeBasedFileNamingAndTriggeringPolicy class="ch.qos.logback.core.rolling.SizeAndTimeBasedFNATP">
|
||||
<maxFileSize>50MB</maxFileSize>
|
||||
</timeBasedFileNamingAndTriggeringPolicy>
|
||||
</rollingPolicy>
|
||||
|
||||
<encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
|
||||
<!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
|
||||
<pattern>[%d{yyyy-MM-dd HH:mm:ss.SSS}] [%thread] %-5level %logger{50}:%line - %msg%n</pattern>
|
||||
<!-- 设置字符集 -->
|
||||
<charset>UTF-8</charset>
|
||||
</encoder>
|
||||
|
||||
<!--日志文件最大的大小-->
|
||||
<!-- <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy"> -->
|
||||
<!-- <MaxFileSize>10MB</MaxFileSize> -->
|
||||
<!-- </triggeringPolicy> -->
|
||||
</appender>
|
||||
|
||||
<!-- 日志输出级别 -->
|
||||
<root level="INFO">
|
||||
<appender-ref ref="S"/>
|
||||
<appender-ref ref="R"/>
|
||||
</root>
|
||||
</configuration>
|
||||
@@ -20,7 +20,7 @@
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
|
||||
@@ -0,0 +1,5 @@
|
||||
# 体积大,由 sync-jars.sh 生成;属性文件可版本管理
|
||||
*.jar
|
||||
|
||||
# 运行/调试产生的日志不纳入版本库
|
||||
**/logs/*.log
|
||||
@@ -0,0 +1,92 @@
|
||||
# 电梯应用双版本同路径部署
|
||||
|
||||
每个发行目录内 **JAR**、**`application.properties`**、**`bootstrap.properties`**(Consul)与 **`redis-override.properties`** 位于**同一目录**。`run.sh` 使用:
|
||||
|
||||
```bash
|
||||
--spring.config.location=file:./application.properties,file:./redis-override.properties
|
||||
```
|
||||
|
||||
(说明见下文:Boot 1.5 下 jar 内 `classpath:/application.properties` 往往最后生效,仅靠外置文件盖不住密码。)
|
||||
|
||||
## 目录
|
||||
|
||||
| 目录 | JAR | 端口(见配置首行) |
|
||||
|------|-----|-------------------|
|
||||
| `v1-legacy/` | `cw-elevator-application-V1.0.0.20211103.jar` | **18080** |
|
||||
| `v2-maven/` | `cw-elevator-application-2.0.0.jar` | **18081** |
|
||||
|
||||
属性文件由历史包 `cw-elevator-application-V1.0.0.20211103/application.properties` 复制而来,仅在各自文件头部增加了 **注释** 与 **`server.port`**,便于双实例并行(对拍 / API 套件)。
|
||||
|
||||
## 一次性同步 JAR
|
||||
|
||||
在 **`deploy/`** 下执行(需已存在 V1 原始 JAR;V2 优先用 `releases/v2.0.0/`,否则用 `cw-elevator-application-starter/target/`):
|
||||
|
||||
```bash
|
||||
./sync-jars.sh
|
||||
```
|
||||
|
||||
## 启动(两个终端)
|
||||
|
||||
```bash
|
||||
cd v1-legacy && ./run.sh
|
||||
```
|
||||
|
||||
```bash
|
||||
cd v2-maven && ./run.sh
|
||||
```
|
||||
|
||||
探活示例:`curl -s http://127.0.0.1:18080/actuator/health` 与 `18081`。
|
||||
|
||||
## Consul(`192.168.3.12` Docker)
|
||||
|
||||
本仓库在 **`v1-legacy/bootstrap.properties`**、**`v2-maven/bootstrap.properties`** 中写入:
|
||||
|
||||
- `spring.cloud.consul.host=192.168.3.12`
|
||||
- `spring.cloud.consul.port=8500`
|
||||
|
||||
用于覆盖 fat-jar 内 **`10.128.161.95:8500`**,与 **`deploy/consul-docker`**(`hashicorp/consul:1.22`)对齐。在同一目录执行 `./run.sh` 时,Spring Cloud 会加载上述 **`bootstrap.properties`**。
|
||||
|
||||
验证 Consul:`curl -s http://192.168.3.12:8500/v1/status/leader`。浏览器打开 **`http://192.168.3.12:8500`** 可看 UI。
|
||||
|
||||
**说明**:jar 内 **Dubbo / ZooKeeper** 仍可能指向旧 IP(如 `10.128.161.95:2181`);若启动报 ZK 连接失败,需在 **`application.properties`** 中另行改 Dubbo 注册中心(本次仅处理 Consul)。
|
||||
|
||||
### Feign `ninca-crk-std`(Ribbon / Consul)
|
||||
|
||||
若日志出现 **`Load balancer does not have available server for client: ninca-crk-std`**,表示 **Consul 里没有名为 `ninca-crk-std` 的注册实例**,而 Feign 默认用 **Consul 服务发现**拉实例列表。
|
||||
|
||||
**做法**:① 在目标环境启动 **`ninca-crk-std`(访客标准服务)** 并注册到同一 Consul;或 ② 在 **`application.properties`** 中使用 **Ribbon 静态列表**(已增加 `ninca-crk-std.ribbon.NIWSServerListClassName` 与 `listOfServers`,默认与 `ninca-crk-std.ip` 一致),把 **`listOfServers`** 改成你实际可访问的 **`主机:端口`**。
|
||||
|
||||
## Redis 与 `SPRING_APPLICATION_JSON`
|
||||
|
||||
fat JAR 的 `classpath:/application.properties` 会带内网旧 **host** 与 **password**;在 Spring Boot 1.5 下,同目录的 `application.properties` / `redis-override.properties` 往往**压不过** jar 里同文件(见上节说明)。
|
||||
|
||||
**当前做法**:`deploy/merge-redis-json.sh` 读取各目录下的 **`redis-override.properties`**,生成一行 **`SPRING_APPLICATION_JSON`**(包含 `spring.redis.host` / `port` / `password`),由 `run.sh` **`export`** 后再启动 JVM,优先级高于打包配置。
|
||||
|
||||
- 默认已指向 **`redis-override.properties`** 中的地址与口令(若你的环境不一致,直接改该文件)。
|
||||
- **临时覆盖口令**:`SPRING_REDIS_PASSWORD='别的密码' ./run.sh`(若设为**空字符串**表示使用无密码 Redis)。
|
||||
- 需要 **python3**。
|
||||
- **勿把生产口令提交到公开仓库**;团队协作时口令宜走密钥管理,`redis-override.properties` 仅作本机示例。
|
||||
|
||||
## 修改配置
|
||||
|
||||
直接编辑对应目录下的 **`application.properties`**(与 JAR 同路径),重启进程生效。
|
||||
|
||||
## JDK 版本(避免 `InaccessibleObjectException` / CGLIB)
|
||||
|
||||
历史 JAR 面向 **JDK 8**。本仓库在 **`deploy/common-java.sh`** 顶部用变量 **`DEPLOY_JDK8`** 写死默认路径(当前为 **`/usr/lib/jvm/java-8-openjdk-amd64`**)。换机器请只改这一处,或通过环境变量临时覆盖:
|
||||
|
||||
`DEPLOY_JDK8=/你的/jdk8 ./run.sh`
|
||||
|
||||
**默认**:`run.sh` 使用 **`DEPLOY_JDK8`**,**不跟随** Conda 的 `JAVA_HOME`。
|
||||
|
||||
- 坚持用当前 Shell 里的 Java(如 Conda JDK17):
|
||||
`ELEVATOR_USE_ENV_JAVA=1 ./run.sh`
|
||||
非 JDK8 时会自动追加 `--add-opens=...`。
|
||||
- 额外 JVM:
|
||||
`ELEVATOR_JAVA_OPTS="-Xmx512m" ./run.sh`
|
||||
|
||||
## Shell 脚本换行(若出现 `bash\r`)
|
||||
|
||||
在 Windows 或某些编辑器下保存成 **CRLF** 会导致 `#!/usr/bin/env bash\r`。仓库根已有 **`.editorconfig`** 约束 `*.sh` 使用 **LF**;若再出现可执行:
|
||||
|
||||
`find . -name '*.sh' -type f -exec sed -i 's/\r$//' {} +`(在 `反编译` 根目录)
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# shellcheck shell=bash
|
||||
# 由 v1-legacy/run.sh、v2-maven/run.sh source:JAVA_HOME;非 JDK8 时追加 --add-opens。
|
||||
#
|
||||
# === 本机 JDK 8 安装根目录(含 bin/java);换机器只需改下行默认路径或通过环境变量覆盖 ===
|
||||
: "${DEPLOY_JDK8:=/usr/lib/jvm/java-8-openjdk-amd64}"
|
||||
|
||||
_pick_java_home() {
|
||||
if [[ "${ELEVATOR_USE_ENV_JAVA:-0}" == "1" ]] && [[ -n "${JAVA_HOME:-}" && -x "${JAVA_HOME}/bin/java" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ -x "${DEPLOY_JDK8}/bin/java" ]]; then
|
||||
export JAVA_HOME="${DEPLOY_JDK8}"
|
||||
return 0
|
||||
fi
|
||||
for d in /usr/lib/jvm/java-8-openjdk-amd64 /usr/lib/jvm/java-1.8.0-openjdk; do
|
||||
if [[ -x "$d/bin/java" ]]; then
|
||||
export JAVA_HOME="$d"
|
||||
return 0
|
||||
fi
|
||||
done
|
||||
if [[ -n "${JAVA_HOME:-}" && -x "${JAVA_HOME}/bin/java" ]]; then
|
||||
return 0
|
||||
fi
|
||||
export JAVA_HOME="${JAVA_HOME:-${DEPLOY_JDK8}}"
|
||||
}
|
||||
|
||||
_jdk8_open_flags() {
|
||||
local java="$1"
|
||||
if "$java" -version 2>&1 | grep -qE 'version "1\.8\.'; then
|
||||
echo ""
|
||||
return
|
||||
fi
|
||||
echo "--add-opens=java.base/java.lang=ALL-UNNAMED"
|
||||
echo "--add-opens=java.base/java.lang.reflect=ALL-UNNAMED"
|
||||
echo "--add-opens=java.base/java.util=ALL-UNNAMED"
|
||||
echo "--add-opens=java.base/java.io=ALL-UNNAMED"
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
# Consul 单机 Server(Docker),镜像 hashicorp/consul:1.22
|
||||
# 在存放本文件的目录执行: docker compose up -d
|
||||
# UI / HTTP API: http://<宿主机IP>:8500 (例: 192.168.3.12:8500)
|
||||
# 应用侧: spring.cloud.consul.host=<宿主机IP> spring.cloud.consul.port=8500
|
||||
services:
|
||||
consul:
|
||||
image: hashicorp/consul:1.22
|
||||
container_name: consul
|
||||
restart: unless-stopped
|
||||
ports:
|
||||
- "8500:8500" # HTTP API / UI
|
||||
- "8300:8300" # Server RPC
|
||||
- "8301:8301" # LAN Serf
|
||||
- "8302:8302/udp"
|
||||
- "8302:8302/tcp"
|
||||
- "8600:8600/tcp"
|
||||
- "8600:8600/udp" # DNS
|
||||
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
|
||||
# 若夸主机访问 RPC 异常,可在宿主机上改为显式 advertise(示例,按实际 IP 修改):
|
||||
# command: >
|
||||
# agent -server -bootstrap-expect=1 -ui -client=0.0.0.0 -bind=0.0.0.0
|
||||
# -advertise=192.168.3.12 -data-dir=/consul/data
|
||||
|
||||
volumes:
|
||||
consul-data:
|
||||
@@ -0,0 +1,38 @@
|
||||
#!/usr/bin/env bash
|
||||
# 读取 redis-override.properties,输出一行 SPRING_APPLICATION_JSON(紧凑 JSON)。
|
||||
# 若环境变量 SPRING_REDIS_PASSWORD 已设置(含空字符串),则覆盖文件中的 spring.redis.password。
|
||||
# 用法: export SPRING_APPLICATION_JSON="$(./merge-redis-json.sh /path/to/redis-override.properties)"
|
||||
set -euo pipefail
|
||||
PROP="${1:?用法: merge-redis-json.sh <redis-override.properties>}"
|
||||
if [[ ! -f "$PROP" ]]; then
|
||||
echo "找不到文件: $PROP" >&2
|
||||
exit 1
|
||||
fi
|
||||
python3 - "$PROP" <<'PY'
|
||||
import json, os, pathlib, re, sys
|
||||
|
||||
prop = pathlib.Path(sys.argv[1])
|
||||
lines = prop.read_text(encoding="utf-8")
|
||||
|
||||
|
||||
def get(key):
|
||||
pat = r"^" + re.escape(key) + r"\s*=\s*(.*?)\s*$"
|
||||
m = re.search(pat, lines, re.MULTILINE)
|
||||
return m.group(1).strip() if m else ""
|
||||
|
||||
|
||||
host = get("spring.redis.host") or "127.0.0.1"
|
||||
port_raw = get("spring.redis.port") or "6379"
|
||||
try:
|
||||
port = int(port_raw)
|
||||
except ValueError:
|
||||
port = 6379
|
||||
|
||||
if "SPRING_REDIS_PASSWORD" in os.environ:
|
||||
pwd = os.environ["SPRING_REDIS_PASSWORD"]
|
||||
else:
|
||||
pwd = get("spring.redis.password")
|
||||
|
||||
payload = {"spring": {"redis": {"host": host, "port": port, "password": pwd}}}
|
||||
print(json.dumps(payload, separators=(",", ":")))
|
||||
PY
|
||||
+35
@@ -0,0 +1,35 @@
|
||||
#!/usr/bin/env bash
|
||||
# 将 V1 / V2 JAR 复制到与各目录 application.properties 同路径,便于 java -jar 启动。
|
||||
set -euo pipefail
|
||||
|
||||
DEPLOY="$(cd "$(dirname "$0")" && pwd)"
|
||||
MAVEN="$(cd "$DEPLOY/.." && pwd)"
|
||||
REPO="$(cd "$MAVEN/.." && pwd)"
|
||||
|
||||
V1_SRC="${REPO}/cw-elevator-application-V1.0.0.20211103/cw-elevator-application-V1.0.0.20211103.jar"
|
||||
V2_REL="${MAVEN}/releases/v2.0.0/cw-elevator-application-2.0.0.jar"
|
||||
V2_TGT="${MAVEN}/cw-elevator-application-starter/target/cw-elevator-application-2.0.0.jar"
|
||||
|
||||
if [[ ! -f "$V1_SRC" ]]; then
|
||||
echo "ERROR: 未找到 V1 JAR: $V1_SRC" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# 优先 target:本地 mvn package 后应与 deploy 同步,避免 releases 里旧包盖住新构建。
|
||||
V2_SRC=""
|
||||
if [[ -f "$V2_TGT" ]]; then
|
||||
V2_SRC="$V2_TGT"
|
||||
elif [[ -f "$V2_REL" ]]; then
|
||||
V2_SRC="$V2_REL"
|
||||
else
|
||||
echo "ERROR: 未找到 V2 JAR(请先 mvn package 或放入 releases):" >&2
|
||||
echo " $V2_TGT 或 $V2_REL" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
install -m0644 "$V1_SRC" "${DEPLOY}/v1-legacy/cw-elevator-application-V1.0.0.20211103.jar"
|
||||
install -m0644 "$V2_SRC" "${DEPLOY}/v2-maven/cw-elevator-application-2.0.0.jar"
|
||||
|
||||
echo "OK: V1 -> deploy/v1-legacy/"
|
||||
echo "OK: V2 -> deploy/v2-maven/"
|
||||
ls -la "${DEPLOY}/v1-legacy/"*.jar "${DEPLOY}/v2-maven/"*.jar
|
||||
@@ -0,0 +1,120 @@
|
||||
# deploy/v1-legacy \uFF1A\u5386\u53F2\u5305 cw-elevator-application-V1.0.0.20211103.jar\uFF08\u540C\u76EE\u5F55\u542F\u52A8\uFF09
|
||||
server.port=18080
|
||||
spring.application.name=elevator-app
|
||||
# OpenFeign 2.1.x\uFF1A\u591A\u4E2A @FeignClient \u5171\u540C name \u5360\u4F4D\u7B26\u65F6\u91CD\u590D\u6CE8\u518C FeignClientSpecification\uFF0C\u4E0E Spring \u9519\u8BEF\u63D0\u793A\u4E00\u81F4
|
||||
spring.main.allow-bean-definition-overriding=true
|
||||
|
||||
# spring\u914D\u7F6E
|
||||
spring.mvc.throw-exception-if-no-handler-found=true
|
||||
spring.mvc.locale=zh_CN
|
||||
# \u8D44\u6E90\u6587\u4EF6\u914D\u7F6E
|
||||
spring.messages.basename=access-control
|
||||
spring.messages.always-use-message-format=true
|
||||
spring.messages.encoding=utf-8
|
||||
# http\u914D\u7F6E
|
||||
spring.http.multipart.max-file-size=200MB
|
||||
spring.http.multipart.max-request-size=200MB
|
||||
spring.http.encoding.force=true
|
||||
spring.http.encoding.charset=UTF-8
|
||||
spring.http.encoding.enabled=true
|
||||
# \u65E5\u5FD7\u914D\u7F6E
|
||||
logging.config=classpath:logs/logback.xml
|
||||
logging.file=${spring.application.name}
|
||||
logging.path=logs
|
||||
logging.level.root=info
|
||||
logging.level.cn.cloudwalk=info
|
||||
# mybatis\u914D\u7F6E
|
||||
mybatis.mapper-locations=classpath*:cn/cloudwalk/elevator/**/*.xml
|
||||
mybatis.config-location=classpath:mapper/mybatis-config.xml
|
||||
# \u5E8F\u5217\u53F7\u914D\u7F6E
|
||||
cloudwalk.serial.enabled=true
|
||||
cloudwalk.serial.serial-length=8
|
||||
cloudwalk.serial.serial-type=redis
|
||||
cloudwalk.serial.serial-redis-key=CLOUDWALK-ACS-SERIAL-KEY
|
||||
# \u7F13\u5B58\u914D\u7F6E
|
||||
cloudwalk.spring.cache.expires=CACHE_NAME_APPLICATIONIDS#21600,ACS_DeviceTypesCache#7200,ACS_DeviceTypeFeaturesCache#7200,ACS_DeviceAttrsCache#7200,ACS_RecordStatisticsCache#90000,ACS_AreaTreeCache#60
|
||||
# \u5185\u90E8\u63A5\u53E3\u8C03\u7528\u5BA2\u6237\u7AEF\u53CA\u8D85\u65F6\u914D\u7F6E
|
||||
feign.hystrix.enable=true
|
||||
feign.httpclient.enable=false
|
||||
feign.okhttp.enable=true
|
||||
ribbon.http.client.enabled=false
|
||||
ribbon.okhttp.enabled=true
|
||||
ribbon.ReadTimeout=10000
|
||||
ribbon.ConnectTimeout=10000
|
||||
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
|
||||
# \u5065\u5EB7\u68C0\u67E5\u914D\u7F6E
|
||||
management.health.redis.enabled=false
|
||||
management.health.db.enabled=false
|
||||
# \u6570\u636E\u8131\u654F\u914D\u7F6E
|
||||
cloudwalk.datafield.enable=true
|
||||
cloudwalk.datafield.securityKey=d4b2aabc97394a12a27fc3cca6cd9ba1
|
||||
cloudwalk.datafield.encrypt=AES
|
||||
# redis\u914D\u7F6E\uFF08\u672C\u673A Docker\uFF1Aybs-redis 6379->6379\uFF0C\u82E5\u7528 craftlabs-redis \u6539\u4E3A 6380\uFF09
|
||||
spring.redis.host=127.0.0.1
|
||||
spring.redis.port=6379
|
||||
# \u672C\u673A Redis \u65E0\u5BC6\u7801\u65F6\u5FC5\u987B\u4FDD\u7559\u4E0B\u884C\u7A7A\u503C\uFF0C\u4EE5\u8986\u76D6 fat-jar \u5185\u5D4C\u65E7\u5BC6\u7801\uFF08\u5426\u5219 Redisson ERR AUTH\uFF09
|
||||
spring.redis.password=
|
||||
spring.redis.database=5
|
||||
spring.redis.timeout=0
|
||||
spring.redis.pool.max-active=10
|
||||
spring.redis.pool.max-idle=1
|
||||
spring.redis.pool.max-wait=10
|
||||
spring.redis.pool.min-idle=0
|
||||
# \u6570\u636E\u5E93sharding\u914D\u7F6E
|
||||
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://192.168.3.12:3307/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
|
||||
spring.shardingsphere.datasource.ds0.connection-timeout=60000
|
||||
spring.shardingsphere.datasource.ds0.maximum-pool-size=20
|
||||
spring.shardingsphere.datasource.ds0.minimum-idle=5
|
||||
spring.shardingsphere.datasource.ds0.max-lifetime=1765000
|
||||
spring.shardingsphere.datasource.ds0.auto-commit=true
|
||||
spring.shardingsphere.datasource.ds0.pool-name=ds0-pool
|
||||
spring.shardingsphere.props.sql.show=false
|
||||
spring.shardingsphere.sharding.default-data-source-name=ds0
|
||||
# \u5FAE\u670D\u52A1\u670D\u52A1\u540D\u914D\u7F6E
|
||||
feign.device.name=cwos-portal
|
||||
feign.resource.name=cwos-portal
|
||||
feign.cwos-portal.name=cwos-portal
|
||||
feign.ninca-crk-std.name=ninca-crk-std
|
||||
# Feign/Ribbon 默认从 Consul 按服务名发现 ninca-crk-std;Consul 无注册时出现「Load balancer does not have available server」。
|
||||
# 以下两行改为静态 ServerList(与下行 ninca-crk-std.ip 同目标时保持一致;若访客服务只部署在其它 IP/端口请一起修改):
|
||||
ninca-crk-std.ribbon.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
|
||||
ninca-crk-std.ribbon.listOfServers=10.128.161.95:16106
|
||||
feign.davinci-portal.name=cwos-portal
|
||||
feign.component-organization.name=ninca-common-component-organization
|
||||
feign.ninca-common.name=ninca-common
|
||||
feign.mqtt.name=cloudwalk-device-thirdparty
|
||||
# CWOS\u4E8B\u4EF6\u914D\u7F6E
|
||||
cloudwalk.event.bootstrap-servers=192.168.3.12:9092
|
||||
cloudwalk.event.group-id=cw-elevator-application-1
|
||||
cloudwalk.event.handler-executor-config.core-pool-size=10
|
||||
cloudwalk.event.handler-executor-config.maximum-pool-size=30
|
||||
# \u5206\u5E03\u5F0F\u9501\u914D\u7F6E
|
||||
intelligent.lock.enable=true
|
||||
intelligent.lock.config.default-wait-time=10000
|
||||
lockWatchdogTimeout=21000
|
||||
# PERSON_NAME_SPACE
|
||||
person.name.space=recordEvent
|
||||
elevator.application.key=xinghewan
|
||||
elevator.application.time=600
|
||||
elevator.application.keyA=5B7DEF88FF04
|
||||
ninca-crk-std.ip=10.128.161.95:16106
|
||||
#\u53D1\u9001\u7B2C\u4E09\u65B9\u6570\u636Eip
|
||||
sendRecord.ip=hrec.star-river.com:32165
|
||||
sendRecord.token.corpId=53db867a8bb747a1bd04dd1afcad8ca6
|
||||
sendRecord.token.appKey=293e2d708f0143c2957b702cef44d951
|
||||
sendRecord.token.appSecret=5f6995009b864669b52041b8f5dc4625
|
||||
sendRecord.boolean=true
|
||||
|
||||
# \u8BBE\u5907\u5904\u7406\u7EBF\u7A0B\u6C60\u914D\u7F6E
|
||||
ninca.update.floor.pool.corePoolSize=5
|
||||
ninca.update.floor.pool.maxPoolSize=5
|
||||
ninca.update.floor.pool.queueCapacity=100000
|
||||
ninca.update.floor.pool.keepAliveSeconds=150
|
||||
ninca.update.floor.pool.allowCoreThreadTimeOut=true
|
||||
#\u697C\u680Bid
|
||||
floor.building.id=605560539791228928
|
||||
@@ -0,0 +1,4 @@
|
||||
# 与 JAR 同目录,Spring Cloud 会加载本文件,覆盖 jar 内 bootstrap.properties 中的旧 Consul 地址。
|
||||
# 对应 192.168.3.12 上 Docker: hashicorp/consul:1.22(8500)
|
||||
spring.cloud.consul.host=192.168.3.12
|
||||
spring.cloud.consul.port=8500
|
||||
@@ -0,0 +1,5 @@
|
||||
# 必须在 spring.config.location 中排在 application.properties 之后加载。
|
||||
# run.sh 用 merge-redis-json.sh 将本文件转为 SPRING_APPLICATION_JSON,以压过 fat-jar 内 classpath 里的 Redis 配置。
|
||||
spring.redis.host=192.168.3.12
|
||||
spring.redis.port=6379
|
||||
spring.redis.password=1qaz!QAZ
|
||||
+42
@@ -0,0 +1,42 @@
|
||||
#!/usr/bin/env bash
|
||||
# 与当前目录下 application.properties 同路径启动 V1 历史包。
|
||||
# 默认优先使用系统 JDK 8(避免 Conda 的 JDK17+ 触发 CGLIB 模块错误)。
|
||||
# 若必须用当前环境的 JAVA_HOME: ELEVATOR_USE_ENV_JAVA=1 ./run.sh
|
||||
# 额外 JVM 参数: ELEVATOR_JAVA_OPTS="-Xmx512m" ./run.sh
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
# shellcheck source=../common-java.sh
|
||||
source "$(cd "$(dirname "$0")" && pwd)/../common-java.sh"
|
||||
|
||||
JAR="cw-elevator-application-V1.0.0.20211103.jar"
|
||||
if [[ ! -f "$JAR" ]]; then
|
||||
echo "缺少 $JAR,请在 deploy 目录执行: ./sync-jars.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_pick_java_home
|
||||
if [[ ! -x "${JAVA_HOME}/bin/java" ]]; then
|
||||
echo "ERROR: 未找到可执行的 JDK。请安装 openjdk-8-jdk,或设定 JAVA_HOME / ELEVATOR_USE_ENV_JAVA=1 ./run.sh(使用 Conda 等当前环境)。" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAVA="${JAVA_HOME}/bin/java"
|
||||
OPEN_FLAGS=()
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && OPEN_FLAGS+=("$line")
|
||||
done < <(_jdk8_open_flags "$JAVA")
|
||||
|
||||
# classpath:/application.properties 最后加载会盖住外置 properties;用 merge-redis-json.sh 把 redis-override.properties
|
||||
# 转成 SPRING_APPLICATION_JSON(含 host/port/password),优先级高于 jar。
|
||||
# 临时改密码:SPRING_REDIS_PASSWORD='其它' ./run.sh(含空字符串表示无密码)
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "需要 python3(deploy/merge-redis-json.sh)。请安装 python3。" >&2
|
||||
exit 1
|
||||
fi
|
||||
MERGE="$(cd "$(dirname "$0")" && pwd)/../merge-redis-json.sh"
|
||||
if [[ ! -x "$MERGE" ]]; then
|
||||
chmod +x "$MERGE" 2>/dev/null || true
|
||||
fi
|
||||
export SPRING_APPLICATION_JSON="$("$MERGE" "$PWD/redis-override.properties")"
|
||||
# shellcheck disable=SC2086
|
||||
exec "$JAVA" "${OPEN_FLAGS[@]}" ${ELEVATOR_JAVA_OPTS:-} -jar "$JAR" \
|
||||
--spring.config.location=file:./application.properties,file:./redis-override.properties
|
||||
@@ -0,0 +1,119 @@
|
||||
# deploy/v2-maven \uFF1Amaven \u6784\u5EFA cw-elevator-application-2.0.0.jar\uFF08\u540C\u76EE\u5F55\u542F\u52A8\uFF09
|
||||
server.port=18081
|
||||
spring.application.name=cw-elevator-application
|
||||
# Boot 1.5 \u65E0 spring.main.allow-bean-definition-overriding\uFF1B\u82E5\u91CD\u590D Bean \u9700\u5728\u4EE3\u7801\u4FA7\u6D88\u6B67\u4E49\u6216\u5347\u7EA7 Spring Boot
|
||||
|
||||
|
||||
# spring\u914D\u7F6E
|
||||
spring.mvc.throw-exception-if-no-handler-found=true
|
||||
spring.mvc.locale=zh_CN
|
||||
# \u8D44\u6E90\u6587\u4EF6\u914D\u7F6E
|
||||
spring.messages.basename=access-control
|
||||
spring.messages.always-use-message-format=true
|
||||
spring.messages.encoding=utf-8
|
||||
# http\u914D\u7F6E
|
||||
spring.http.multipart.max-file-size=200MB
|
||||
spring.http.multipart.max-request-size=200MB
|
||||
spring.http.encoding.force=true
|
||||
spring.http.encoding.charset=UTF-8
|
||||
spring.http.encoding.enabled=true
|
||||
# \u65E5\u5FD7\u914D\u7F6E
|
||||
logging.config=classpath:logs/logback.xml
|
||||
logging.file=${spring.application.name}
|
||||
logging.path=logs
|
||||
logging.level.root=info
|
||||
logging.level.cn.cloudwalk=info
|
||||
# mybatis\u914D\u7F6E
|
||||
mybatis.mapper-locations=classpath*:cn/cloudwalk/elevator/**/*.xml
|
||||
mybatis.config-location=classpath:mapper/mybatis-config.xml
|
||||
# \u5E8F\u5217\u53F7\u914D\u7F6E
|
||||
cloudwalk.serial.enabled=true
|
||||
cloudwalk.serial.serial-length=8
|
||||
cloudwalk.serial.serial-type=redis
|
||||
cloudwalk.serial.serial-redis-key=CLOUDWALK-ACS-SERIAL-KEY
|
||||
# \u7F13\u5B58\u914D\u7F6E
|
||||
cloudwalk.spring.cache.expires=CACHE_NAME_APPLICATIONIDS#21600,ACS_DeviceTypesCache#7200,ACS_DeviceTypeFeaturesCache#7200,ACS_DeviceAttrsCache#7200,ACS_RecordStatisticsCache#90000,ACS_AreaTreeCache#60
|
||||
# \u5185\u90E8\u63A5\u53E3\u8C03\u7528\u5BA2\u6237\u7AEF\u53CA\u8D85\u65F6\u914D\u7F6E
|
||||
feign.hystrix.enable=true
|
||||
feign.httpclient.enable=false
|
||||
feign.okhttp.enable=true
|
||||
ribbon.http.client.enabled=false
|
||||
ribbon.okhttp.enabled=true
|
||||
ribbon.ReadTimeout=10000
|
||||
ribbon.ConnectTimeout=10000
|
||||
hystrix.command.default.execution.isolation.thread.timeoutInMilliseconds=10000
|
||||
# \u5065\u5EB7\u68C0\u67E5\u914D\u7F6E
|
||||
management.health.redis.enabled=false
|
||||
management.health.db.enabled=false
|
||||
# \u6570\u636E\u8131\u654F\u914D\u7F6E
|
||||
cloudwalk.datafield.enable=true
|
||||
cloudwalk.datafield.securityKey=d4b2aabc97394a12a27fc3cca6cd9ba1
|
||||
cloudwalk.datafield.encrypt=AES
|
||||
# redis\u914D\u7F6E\uFF08\u672C\u673A Docker\uFF1Aybs-redis 6379->6379\uFF0C\u82E5\u7528 craftlabs-redis \u6539\u4E3A 6380\uFF09
|
||||
spring.redis.host=127.0.0.1
|
||||
spring.redis.port=6379
|
||||
# \u672C\u673A Redis \u65E0\u5BC6\u7801\u65F6\u5FC5\u987B\u4FDD\u7559\u4E0B\u884C\u7A7A\u503C\uFF0C\u4EE5\u8986\u76D6 fat-jar \u5185\u5D4C\u65E7\u5BC6\u7801\uFF08\u5426\u5219 Redisson ERR AUTH\uFF09
|
||||
spring.redis.password=
|
||||
spring.redis.database=5
|
||||
spring.redis.timeout=0
|
||||
spring.redis.pool.max-active=10
|
||||
spring.redis.pool.max-idle=1
|
||||
spring.redis.pool.max-wait=10
|
||||
spring.redis.pool.min-idle=0
|
||||
# \u6570\u636E\u5E93sharding\u914D\u7F6E
|
||||
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://192.168.3.12:3307/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
|
||||
spring.shardingsphere.datasource.ds0.connection-timeout=60000
|
||||
spring.shardingsphere.datasource.ds0.maximum-pool-size=20
|
||||
spring.shardingsphere.datasource.ds0.minimum-idle=5
|
||||
spring.shardingsphere.datasource.ds0.max-lifetime=1765000
|
||||
spring.shardingsphere.datasource.ds0.auto-commit=true
|
||||
spring.shardingsphere.datasource.ds0.pool-name=ds0-pool
|
||||
spring.shardingsphere.props.sql.show=false
|
||||
spring.shardingsphere.sharding.default-data-source-name=ds0
|
||||
# \u5FAE\u670D\u52A1\u670D\u52A1\u540D\u914D\u7F6E
|
||||
feign.device.name=cwos-portal
|
||||
feign.resource.name=cwos-portal
|
||||
feign.cwos-portal.name=cwos-portal
|
||||
feign.ninca-crk-std.name=ninca-crk-std
|
||||
# 见 v1-legacy 同段注释:Consul 无 ninca-crk-std 时用静态 Ribbon 列表。
|
||||
ninca-crk-std.ribbon.NIWSServerListClassName=com.netflix.loadbalancer.ConfigurationBasedServerList
|
||||
ninca-crk-std.ribbon.listOfServers=10.128.161.95:16106
|
||||
feign.davinci-portal.name=cwos-portal
|
||||
feign.component-organization.name=ninca-common-component-organization
|
||||
feign.ninca-common.name=ninca-common
|
||||
feign.mqtt.name=cloudwalk-device-thirdparty
|
||||
# CWOS\u4E8B\u4EF6\u914D\u7F6E
|
||||
cloudwalk.event.bootstrap-servers=192.168.3.12:9092
|
||||
cloudwalk.event.group-id=cw-elevator-application-1
|
||||
cloudwalk.event.handler-executor-config.core-pool-size=10
|
||||
cloudwalk.event.handler-executor-config.maximum-pool-size=30
|
||||
# \u5206\u5E03\u5F0F\u9501\u914D\u7F6E
|
||||
intelligent.lock.enable=true
|
||||
intelligent.lock.config.default-wait-time=10000
|
||||
lockWatchdogTimeout=21000
|
||||
# PERSON_NAME_SPACE
|
||||
person.name.space=recordEvent
|
||||
elevator.application.key=xinghewan
|
||||
elevator.application.time=600
|
||||
elevator.application.keyA=5B7DEF88FF04
|
||||
ninca-crk-std.ip=10.128.161.95:16106
|
||||
#\u53D1\u9001\u7B2C\u4E09\u65B9\u6570\u636Eip
|
||||
sendRecord.ip=hrec.star-river.com:32165
|
||||
sendRecord.token.corpId=53db867a8bb747a1bd04dd1afcad8ca6
|
||||
sendRecord.token.appKey=293e2d708f0143c2957b702cef44d951
|
||||
sendRecord.token.appSecret=5f6995009b864669b52041b8f5dc4625
|
||||
sendRecord.boolean=true
|
||||
|
||||
# \u8BBE\u5907\u5904\u7406\u7EBF\u7A0B\u6C60\u914D\u7F6E
|
||||
ninca.update.floor.pool.corePoolSize=5
|
||||
ninca.update.floor.pool.maxPoolSize=5
|
||||
ninca.update.floor.pool.queueCapacity=100000
|
||||
ninca.update.floor.pool.keepAliveSeconds=150
|
||||
ninca.update.floor.pool.allowCoreThreadTimeOut=true
|
||||
#\u697C\u680Bid
|
||||
floor.building.id=605560539791228928
|
||||
@@ -0,0 +1,3 @@
|
||||
# 覆盖 fat-jar 内嵌 Consul 地址,指向局域网 Docker Consul。
|
||||
spring.cloud.consul.host=192.168.3.12
|
||||
spring.cloud.consul.port=8500
|
||||
@@ -0,0 +1,4 @@
|
||||
# run.sh 将本文件合并为 SPRING_APPLICATION_JSON,覆盖 jar 内 Redis 配置。
|
||||
spring.redis.host=192.168.3.12
|
||||
spring.redis.port=6379
|
||||
spring.redis.password=1qaz!QAZ
|
||||
+39
@@ -0,0 +1,39 @@
|
||||
#!/usr/bin/env bash
|
||||
# 与当前目录下 application.properties 同路径启动 V2(maven 构建)包。
|
||||
# 默认优先系统 JDK 8;若只有 JDK11+ 会自动附加 --add-opens。
|
||||
# ELEVATOR_USE_ENV_JAVA=1 ./run.sh 使用当前 JAVA_HOME(如 Conda)。
|
||||
set -euo pipefail
|
||||
cd "$(dirname "$0")"
|
||||
# shellcheck source=../common-java.sh
|
||||
source "$(cd "$(dirname "$0")" && pwd)/../common-java.sh"
|
||||
|
||||
JAR="cw-elevator-application-2.0.0.jar"
|
||||
if [[ ! -f "$JAR" ]]; then
|
||||
echo "缺少 $JAR,请在 deploy 目录执行: ./sync-jars.sh" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
_pick_java_home
|
||||
if [[ ! -x "${JAVA_HOME}/bin/java" ]]; then
|
||||
echo "ERROR: 未找到可执行的 JDK。请安装 openjdk-8-jdk,或设定 JAVA_HOME / ELEVATOR_USE_ENV_JAVA=1 ./run.sh(使用 Conda 等当前环境)。" >&2
|
||||
exit 1
|
||||
fi
|
||||
JAVA="${JAVA_HOME}/bin/java"
|
||||
OPEN_FLAGS=()
|
||||
while IFS= read -r line; do
|
||||
[[ -n "$line" ]] && OPEN_FLAGS+=("$line")
|
||||
done < <(_jdk8_open_flags "$JAVA")
|
||||
|
||||
# 同 v1:由 redis-override.properties 合并出 SPRING_APPLICATION_JSON。
|
||||
if ! command -v python3 >/dev/null 2>&1; then
|
||||
echo "需要 python3(deploy/merge-redis-json.sh)。请安装 python3。" >&2
|
||||
exit 1
|
||||
fi
|
||||
MERGE="$(cd "$(dirname "$0")" && pwd)/../merge-redis-json.sh"
|
||||
if [[ ! -x "$MERGE" ]]; then
|
||||
chmod +x "$MERGE" 2>/dev/null || true
|
||||
fi
|
||||
export SPRING_APPLICATION_JSON="$("$MERGE" "$PWD/redis-override.properties")"
|
||||
# shellcheck disable=SC2086
|
||||
exec "$JAVA" "${OPEN_FLAGS[@]}" ${ELEVATOR_JAVA_OPTS:-} -jar "$JAR" \
|
||||
--spring.config.location=file:./application.properties,file:./redis-override.properties
|
||||
@@ -6,7 +6,7 @@
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.18.RELEASE</version>
|
||||
<version>1.5.17.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<version>2.0-SNAPSHOT</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>cw-elevator-application (Maven reactor)</name>
|
||||
<description>聚合模块:common → data → service → web。release/cw-elevator-v1-lib-min-risk:cloudwalk-common-service/event 与 V1 cw_lib 一致为 3.7.2;部分第三方与 cw-elevator-application-V1.0.0.20211103/lib 文件名对齐。对照目录见 cw.elevator.v1.lib.dir。</description>
|
||||
<description>聚合模块:common → data → service → web。内部件版本与 ../cw-elevator-application-V1.0.0.20211103/cw_lib 对齐(含 intelligent-cwoscomponent 2.9.2-xinghewan,不使用 3.0.0);cloudwalk-common 3.7.2;第三方与 V1 运行包一致:Spring Boot 1.5.17、Spring Cloud Edgware、mybatis-spring-boot 1.3.1 等。可选对照 V1 展开包 lib 目录见 cw.elevator.v1.lib.dir。</description>
|
||||
|
||||
<modules>
|
||||
<module>cw-elevator-application-common</module>
|
||||
@@ -32,24 +32,31 @@
|
||||
<!-- 与 V1.0.0.20211103/cw_lib 中 cloudwalk-common-* 一致,降低与历史运行包 API 差异 -->
|
||||
<cloudwalk.internal.version>3.7.2-Brussels-SRX</cloudwalk.internal.version>
|
||||
<cloudwalk.legacy.public.version>3.7.2-Brussels-SRX</cloudwalk.legacy.public.version>
|
||||
<intelligent.cwoscomponent.version>3.0.0-xinghewan</intelligent.cwoscomponent.version>
|
||||
<!-- 以下与 V1.0.0.20211103/lib 中 *.pom 主版本对齐(保持 Spring Boot 2.1.18 以兼容当前源码) -->
|
||||
<!-- 与 cw_lib 完全一致;禁止改为 3.0.0-xinghewan(与本产品线口径冲突) -->
|
||||
<intelligent.cwoscomponent.version>2.9.2-xinghewan</intelligent.cwoscomponent.version>
|
||||
<!-- 以下与 V1 fat-jar lib 目录文件名对齐(与 cw-elevator-application-V1.0.0.20211103 一致) -->
|
||||
<fastjson.version>1.2.73</fastjson.version>
|
||||
<guava.version>28.2-jre</guava.version>
|
||||
<poi.version>4.1.2</poi.version>
|
||||
<ant.version>1.10.12</ant.version>
|
||||
<guava.version>20.0</guava.version>
|
||||
<poi.version>3.15</poi.version>
|
||||
<ant.version>1.9.6</ant.version>
|
||||
<thumbnailator.version>0.4.8</thumbnailator.version>
|
||||
<commons-io.version>2.5</commons-io.version>
|
||||
<!-- Boot 1.5 父 POM 不托管 commons-lang3,显式与 1.5.x 栈常用版本一致 -->
|
||||
<commons-lang3.version>3.5</commons-lang3.version>
|
||||
<zip4j.version>2.6.2</zip4j.version>
|
||||
<zxing.version>3.3.3</zxing.version>
|
||||
<pagehelper.version>5.1.2</pagehelper.version>
|
||||
<pagehelper-spring-boot.version>1.2.5</pagehelper-spring-boot.version>
|
||||
<shardingsphere.version>4.0.0</shardingsphere.version>
|
||||
<mybatis.version>3.5.6</mybatis.version>
|
||||
<mybatis-spring.version>2.0.6</mybatis-spring.version>
|
||||
<mybatis-spring-boot.version>2.0.1</mybatis-spring-boot.version>
|
||||
<servlet-api.version>2.5</servlet-api.version>
|
||||
<mybatis.version>3.4.6</mybatis.version>
|
||||
<mybatis-spring.version>1.3.2</mybatis-spring.version>
|
||||
<mybatis-spring-boot.version>1.3.1</mybatis-spring-boot.version>
|
||||
<javax.servlet-api.version>3.1.0</javax.servlet-api.version>
|
||||
<javax.annotation-api.version>1.3.2</javax.annotation-api.version>
|
||||
<!-- javax.annotation.Nullable 来自 JSR-305,非 javax.annotation-api -->
|
||||
<jsr305.version>3.0.2</jsr305.version>
|
||||
<cwos.sdk.resource.version>1.0.0-SNAPSHOT</cwos.sdk.resource.version>
|
||||
<cwos.sdk.event.version>1.5.0-SNAPSHOT</cwos.sdk.event.version>
|
||||
<intelligent.lock.version>1.1.1-SNAPSHOT</intelligent.lock.version>
|
||||
<davinci.manager.storage.version>1.1.7-SNAPSHOT</davinci.manager.storage.version>
|
||||
<!-- Nexus UI: http://192.168.3.12/#browse/welcome -->
|
||||
@@ -57,8 +64,8 @@
|
||||
<nexus.public.repo>${nexus.baseUrl}/repository/maven-public/</nexus.public.repo>
|
||||
<formatter.maven.plugin.version>2.24.1</formatter.maven.plugin.version>
|
||||
<alibaba.eclipse.codestyle.path>${project.basedir}/../docs/style/alibaba-eclipse-codestyle.xml</alibaba.eclipse.codestyle.path>
|
||||
<!-- 与 Spring Boot 2.1.18 对齐的 OpenFeign/Cloud 版本(发布包可执行 JAR 需要) -->
|
||||
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
|
||||
<!-- 与 Spring Boot 1.5.x 配对的 Spring Cloud Edgware(spring-cloud-starter-openfeign;注解包为 netflix.feign) -->
|
||||
<spring-cloud.version>Edgware.SR6</spring-cloud.version>
|
||||
<!-- spring-boot-maven-plugin repackage 产出的可执行 JAR 文件名(不含 .jar) -->
|
||||
<elevator.release.finalName>cw-elevator-application-2.0.0</elevator.release.finalName>
|
||||
</properties>
|
||||
@@ -107,6 +114,11 @@
|
||||
<artifactId>cwos-java-sdk-resource</artifactId>
|
||||
<version>${cwos.sdk.resource.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cwos-sdk-event</artifactId>
|
||||
<version>${cwos.sdk.event.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.intelligent</groupId>
|
||||
<artifactId>cloudwalk-intelligent-component-lock</artifactId>
|
||||
@@ -147,6 +159,11 @@
|
||||
<artifactId>commons-io</artifactId>
|
||||
<version>${commons-io.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
<version>${commons-lang3.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
@@ -194,8 +211,18 @@
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.servlet</groupId>
|
||||
<artifactId>servlet-api</artifactId>
|
||||
<version>${servlet-api.version}</version>
|
||||
<artifactId>javax.servlet-api</artifactId>
|
||||
<version>${javax.servlet-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.annotation</groupId>
|
||||
<artifactId>javax.annotation-api</artifactId>
|
||||
<version>${javax.annotation-api.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.google.code.findbugs</groupId>
|
||||
<artifactId>jsr305</artifactId>
|
||||
<version>${jsr305.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.el</groupId>
|
||||
|
||||
+335
@@ -0,0 +1,335 @@
|
||||
#!/usr/bin/env bash
|
||||
# 仅编译本仓库(maven-cw-elevator-application),依赖从 Nexus 拉取,避免使用全局 ~/.m2 里
|
||||
# 其它反编译工程 mvn install 产生的同坐标覆盖。
|
||||
#
|
||||
# 方案要点:
|
||||
# - 使用独立 maven.repo.local(默认:仓库根目录下 .m2-elevator-nexus-only),与 ~/.m2 隔离。
|
||||
# - Nexus 常缺聚合父 POM cloudwalk-cloud-common:脚本会(1)尝试从全局 ~/.m2 复制该目录;
|
||||
# (2)若仍缺,且存在相邻仓库 maven-cloudwalk-legacy-public/cloudwalk-cloud-common,则自动
|
||||
# mvn -N install 到隔离库(仅父 POM,不编其它反编译模块)。
|
||||
# - 私服上 cloudwalk-common-web 等 POM 若传递依赖不完整,隔离构建会缺 spring-boot / commons-lang
|
||||
# 等编译包。可选(3)从全局 ~/.m2 复制 cloudwalk-common-* 同版本目录(与 Nexus 二选一:先复制则优先用本地该目录)。
|
||||
# - Nexus 常缺 SNAPSHOT 父 POM(cwos / davinci)及 cloudwalk-device-sdk 父 POM:若相邻仓库存在对应 pom.xml,则自动 mvn -N install
|
||||
# 到隔离库:maven-cwos-resource 的 cwos-component-resource、cwos-portal;maven-cloudwalk-intelligent-davinci-manager 根 POM;
|
||||
# maven-cloudwalk-device-sdk 根 POM(供 intelligent-cwoscomponent-interface 解析 protocol-entity)。
|
||||
# ELEVATOR_AUTO_INSTALL_LEGACY_SNAPSHOT_PARENTS=0 跳过上述「相邻父 POM install」整段。
|
||||
# - intelligent-cwoscomponent 固定为 2.9.2-xinghewan(与 cw_lib 一致,禁止 3.0.0):可先 ELEVATOR_BOOTSTRAP_INTELLIGENT_CWOSCOMPONENT_FROM_GLOBAL
|
||||
# 从 ~/.m2 复制 parent(artifactId intelligent-cwoscomponent)/ interface / rest;若仍缺 JAR,则对
|
||||
# cw-elevator-application-V1.0.0.20211103/cw_lib 内同名 jar+pom 执行 install-file,并先安装 scripts/legacy-poms 下父 POM 桩。
|
||||
# ELEVATOR_AUTO_INSTALL_INTELLIGENT_CWOSCOMPONENT=0 跳过 install;ELEVATOR_CW_LIB_DIR 覆盖 cw_lib 路径。
|
||||
# - cwos-sdk-event(默认 1.5.0-SNAPSHOT):ELEVATOR_BOOTSTRAP_CWOS_SDK_EVENT_FROM_GLOBAL 从 ~/.m2 复制;缺 JAR 时对
|
||||
# maven-cloudwalk-legacy-public/cwos-sdk-event mvn install。ELEVATOR_AUTO_INSTALL_CWOS_SDK_EVENT=0 跳过 install。
|
||||
# - ELEVATOR_BOOTSTRAP_FROM_GLOBAL_M2=0 跳过(1);ELEVATOR_AUTO_INSTALL_LEGACY_PARENT=0 跳过(2);
|
||||
# ELEVATOR_BOOTSTRAP_CLOUDWALK_MODULES_FROM_GLOBAL=0 跳过(3)(坚持纯 Nexus 时用)。
|
||||
#
|
||||
# 用法:
|
||||
# ./scripts/build_nexus_only.sh
|
||||
# ELEVATOR_M2_REPO=/path/to/custom-repo ./scripts/build_nexus_only.sh
|
||||
# ELEVATOR_SYNC_DEPLOY=1 ./scripts/build_nexus_only.sh # 另同步 V1/V2 到 deploy/v1-legacy 与 deploy/v2-maven
|
||||
# - 编译成功后默认将 starter fat jar 安装到 deploy/v2-maven/(覆盖 cw-elevator-application-2.0.0.jar)。
|
||||
# ELEVATOR_DEPLOY_V2_MAVEN=0 跳过;ELEVATOR_DEPLOY_V2_DIR=/path 覆盖目标目录(默认同 deploy/v2-maven)。
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
cd "$REPO"
|
||||
|
||||
if [[ -z "${JAVA_8:-}" ]]; then
|
||||
for d in /usr/lib/jvm/java-8-openjdk-amd64 /usr/lib/jvm/java-1.8.0-openjdk; do
|
||||
if [[ -x "$d/bin/java" ]]; then
|
||||
export JAVA_8="$d"
|
||||
break
|
||||
fi
|
||||
done
|
||||
fi
|
||||
: "${JAVA_8:=/usr/lib/jvm/java-8-openjdk-amd64}"
|
||||
export JAVA_HOME="$JAVA_8"
|
||||
export PATH="$JAVA_HOME/bin:$PATH"
|
||||
|
||||
if [[ ! -x "${JAVA_HOME}/bin/java" ]]; then
|
||||
echo "ERROR: 未找到 JDK 8,请设置 JAVA_8 或安装 openjdk-8-jdk。" >&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
M2_LOCAL="${ELEVATOR_M2_REPO:-$REPO/.m2-elevator-nexus-only}"
|
||||
mkdir -p "$M2_LOCAL"
|
||||
|
||||
LEGACY_VER="${CLOUDWALK_LEGACY_VERSION:-3.7.2-Brussels-SRX}"
|
||||
ICOMP_VER="${ELEVATOR_INTELLIGENT_CWOSCOMPONENT_VERSION:-2.9.2-xinghewan}"
|
||||
CWOS_EVENT_VER="${ELEVATOR_CWOS_SDK_EVENT_VERSION:-1.5.0-SNAPSHOT}"
|
||||
GLOBAL_M2="${ELEVATOR_GLOBAL_M2:-$HOME/.m2/repository}"
|
||||
|
||||
parent_marker_file() {
|
||||
echo "$M2_LOCAL/cn/cloudwalk/cloud/cloudwalk-cloud-common/${LEGACY_VER}/cloudwalk-cloud-common-${LEGACY_VER}.pom"
|
||||
}
|
||||
|
||||
bootstrap_from_global_m2() {
|
||||
local rel="cn/cloudwalk/cloud/cloudwalk-cloud-common/${LEGACY_VER}"
|
||||
local src="$GLOBAL_M2/$rel"
|
||||
if [[ ! -d "$src" ]]; then
|
||||
echo "WARN: 全局仓库中无父 POM 目录: $src(将尝试相邻 legacy 自动 install)" >&2
|
||||
return 0
|
||||
fi
|
||||
mkdir -p "$M2_LOCAL/$rel"
|
||||
cp -a "$src/." "$M2_LOCAL/$rel/"
|
||||
echo "==> 已从全局 ~/.m2 预置父 POM(仅该目录): $rel"
|
||||
}
|
||||
|
||||
purge_last_updated_under() {
|
||||
local base="$1"
|
||||
[[ -d "$base" ]] || return 0
|
||||
find "$base" -name '*.lastUpdated' -type f -print -delete 2>/dev/null || true
|
||||
}
|
||||
|
||||
# 参数: marker_pom_path pom_file human_desc
|
||||
install_one_snapshot_parent_to_local_repo() {
|
||||
local marker="$1"
|
||||
local pom="$2"
|
||||
local desc="$3"
|
||||
if [[ -f "$marker" ]]; then
|
||||
echo "==> 隔离库已有: $desc"
|
||||
return 0
|
||||
fi
|
||||
if [[ ! -f "$pom" ]]; then
|
||||
echo "ERROR: 隔离库缺少 $desc,且未找到 POM: $pom" >&2
|
||||
echo " 请将对应父 POM 发布到 Nexus,或设置 ELEVATOR_LEGACY_CWOS_RESOURCE_DIR / ELEVATOR_LEGACY_DAVINCI_MANAGER_POM。" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> 向隔离库安装(mvn -N install): $desc -> $pom"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" -f "$pom" -N install -DskipTests
|
||||
if [[ ! -f "$marker" ]]; then
|
||||
echo "ERROR: install 后仍缺少: $marker" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
install_legacy_cloudwalk_parent_to_local_repo() {
|
||||
local marker
|
||||
marker="$(parent_marker_file)"
|
||||
if [[ -f "$marker" ]]; then
|
||||
echo "==> 隔离库已有父 POM: $marker"
|
||||
return 0
|
||||
fi
|
||||
if [[ "${ELEVATOR_AUTO_INSTALL_LEGACY_PARENT:-1}" != "1" ]]; then
|
||||
echo "ERROR: 隔离库缺少父 POM 且 ELEVATOR_AUTO_INSTALL_LEGACY_PARENT=0:$marker" >&2
|
||||
exit 1
|
||||
fi
|
||||
local def_pom
|
||||
def_pom="$(cd "$REPO/.." && pwd)/maven-cloudwalk-legacy-public/cloudwalk-cloud-common/pom.xml"
|
||||
local legacy_pom="${ELEVATOR_LEGACY_CLOUDWALK_COMMON_POM:-$def_pom}"
|
||||
if [[ ! -f "$legacy_pom" ]]; then
|
||||
echo "ERROR: 隔离库无 cloudwalk-cloud-common POM,且未找到:$legacy_pom" >&2
|
||||
echo " 请设置 ELEVATOR_LEGACY_CLOUDWALK_COMMON_POM,或将该父 POM 发布到 Nexus。" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> 向隔离库安装父 POM(mvn -N install): $legacy_pom"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" -f "$legacy_pom" -N install -DskipTests
|
||||
if [[ ! -f "$marker" ]]; then
|
||||
echo "ERROR: install 后仍缺少: $marker" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
if [[ "${ELEVATOR_BOOTSTRAP_FROM_GLOBAL_M2:-1}" == "1" ]]; then
|
||||
echo "==> 尝试从全局 ~/.m2 预置 cloudwalk-cloud-common(${LEGACY_VER})"
|
||||
bootstrap_from_global_m2
|
||||
fi
|
||||
|
||||
install_legacy_cloudwalk_parent_to_local_repo
|
||||
|
||||
# 私服缺 cwos-portal / cwos-component-resource / cloudwalk-intelligent-davinci-manager 等父 POM 时,用相邻反应堆根 POM 闭合描述符。
|
||||
install_legacy_snapshot_parents_to_local_repo() {
|
||||
if [[ "${ELEVATOR_AUTO_INSTALL_LEGACY_SNAPSHOT_PARENTS:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local root
|
||||
root="$(cd "$REPO/.." && pwd)"
|
||||
local def_cwos="$root/maven-cwos-resource"
|
||||
local def_davinci="$root/maven-cloudwalk-intelligent-davinci-manager/pom.xml"
|
||||
local cwos_base="${ELEVATOR_LEGACY_CWOS_RESOURCE_DIR:-$def_cwos}"
|
||||
|
||||
install_one_snapshot_parent_to_local_repo \
|
||||
"$M2_LOCAL/cn/cloudwalk/cloud/cwos-component-resource/1.0.0-SNAPSHOT/cwos-component-resource-1.0.0-SNAPSHOT.pom" \
|
||||
"$cwos_base/cwos-component-resource/pom.xml" \
|
||||
"cwos-component-resource 1.0.0-SNAPSHOT"
|
||||
|
||||
install_one_snapshot_parent_to_local_repo \
|
||||
"$M2_LOCAL/cn/cloudwalk/cwos-portal/1.0.0-SNAPSHOT/cwos-portal-1.0.0-SNAPSHOT.pom" \
|
||||
"$cwos_base/cwos-portal/pom.xml" \
|
||||
"cwos-portal 1.0.0-SNAPSHOT"
|
||||
|
||||
install_one_snapshot_parent_to_local_repo \
|
||||
"$M2_LOCAL/cn/cloudwalk/intelligent/cloudwalk-intelligent-davinci-manager/1.1.7-SNAPSHOT/cloudwalk-intelligent-davinci-manager-1.1.7-SNAPSHOT.pom" \
|
||||
"${ELEVATOR_LEGACY_DAVINCI_MANAGER_POM:-$def_davinci}" \
|
||||
"cloudwalk-intelligent-davinci-manager 1.1.7-SNAPSHOT"
|
||||
|
||||
install_one_snapshot_parent_to_local_repo \
|
||||
"$M2_LOCAL/cn/cloudwalk/cloudwalk-device-sdk/2.2.0/cloudwalk-device-sdk-2.2.0.pom" \
|
||||
"${ELEVATOR_LEGACY_DEVICE_SDK_POM:-$root/maven-cloudwalk-device-sdk/pom.xml}" \
|
||||
"cloudwalk-device-sdk 2.2.0"
|
||||
}
|
||||
|
||||
install_legacy_snapshot_parents_to_local_repo
|
||||
|
||||
bootstrap_intelligent_cwoscomponent_from_global() {
|
||||
if [[ "${ELEVATOR_BOOTSTRAP_INTELLIGENT_CWOSCOMPONENT_FROM_GLOBAL:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local copied=0
|
||||
# 2.9.2 线:父 artifactId 为 intelligent-cwoscomponent(非 reactor/parent 3.x 命名)
|
||||
for art in intelligent-cwoscomponent intelligent-cwoscomponent-interface intelligent-cwoscomponent-rest; do
|
||||
local rel="cn/cloudwalk/intelligent/${art}/${ICOMP_VER}"
|
||||
if [[ -d "$GLOBAL_M2/$rel" ]]; then
|
||||
mkdir -p "$M2_LOCAL/$rel"
|
||||
cp -a "$GLOBAL_M2/$rel/." "$M2_LOCAL/$rel/"
|
||||
echo "==> 已从 ~/.m2 预置 intelligent-cwoscomponent: $rel"
|
||||
copied=1
|
||||
fi
|
||||
done
|
||||
if [[ "$copied" -eq 0 ]]; then
|
||||
echo "WARN: ~/.m2 中未找到 intelligent-cwoscomponent-*(${ICOMP_VER}),将视情况从 cw_lib install-file。" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
install_intelligent_cwoscomponent_from_cw_lib_if_missing() {
|
||||
if [[ "${ELEVATOR_AUTO_INSTALL_INTELLIGENT_CWOSCOMPONENT:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
if [[ "$ICOMP_VER" == "3.0.0-xinghewan" ]]; then
|
||||
echo "ERROR: 本产品线禁止使用 intelligent-cwoscomponent 3.0.0-xinghewan;请使用 2.9.2-xinghewan(cw_lib)。" >&2
|
||||
exit 1
|
||||
fi
|
||||
local marker="$M2_LOCAL/cn/cloudwalk/intelligent/intelligent-cwoscomponent-rest/${ICOMP_VER}/intelligent-cwoscomponent-rest-${ICOMP_VER}.jar"
|
||||
if [[ -f "$marker" ]]; then
|
||||
echo "==> 隔离库已有 intelligent-cwoscomponent-rest(${ICOMP_VER})"
|
||||
return 0
|
||||
fi
|
||||
local root cw_lib
|
||||
root="$(cd "$REPO/.." && pwd)"
|
||||
cw_lib="${ELEVATOR_CW_LIB_DIR:-$root/cw-elevator-application-V1.0.0.20211103/cw_lib}"
|
||||
local stub="$REPO/scripts/legacy-poms/intelligent-cwoscomponent-2.9.2-xinghewan-parent.pom"
|
||||
local ij="$cw_lib/intelligent-cwoscomponent-interface-${ICOMP_VER}.jar"
|
||||
local ip="$cw_lib/intelligent-cwoscomponent-interface-${ICOMP_VER}.pom"
|
||||
local rj="$cw_lib/intelligent-cwoscomponent-rest-${ICOMP_VER}.jar"
|
||||
local rp="$cw_lib/intelligent-cwoscomponent-rest-${ICOMP_VER}.pom"
|
||||
if [[ ! -f "$stub" ]]; then
|
||||
echo "ERROR: 缺少父 POM 桩: $stub" >&2
|
||||
exit 1
|
||||
fi
|
||||
if [[ ! -f "$ij" || ! -f "$ip" || ! -f "$rj" || ! -f "$rp" ]]; then
|
||||
echo "ERROR: cw_lib 缺少 intelligent-cwoscomponent 2.9.2 构件,无法安装到隔离库。" >&2
|
||||
echo " 期望目录: $cw_lib(设 ELEVATOR_CW_LIB_DIR 可覆盖)" >&2
|
||||
echo " 需要: intelligent-cwoscomponent-interface/rest 的 .jar 与 .pom" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> 向隔离库安装 intelligent-cwoscomponent 父 POM(桩): $stub"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" -f "$stub" -N install -DskipTests
|
||||
echo "==> install-file intelligent-cwoscomponent-interface(${ICOMP_VER})"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file \
|
||||
-DpomFile="$ip" -Dfile="$ij" -Dpackaging=jar
|
||||
echo "==> install-file intelligent-cwoscomponent-rest(${ICOMP_VER})"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file \
|
||||
-DpomFile="$rp" -Dfile="$rj" -Dpackaging=jar
|
||||
if [[ ! -f "$marker" ]]; then
|
||||
echo "ERROR: install 后仍缺少: $marker" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_intelligent_cwoscomponent_from_global
|
||||
install_intelligent_cwoscomponent_from_cw_lib_if_missing
|
||||
|
||||
bootstrap_cwos_sdk_event_from_global() {
|
||||
if [[ "${ELEVATOR_BOOTSTRAP_CWOS_SDK_EVENT_FROM_GLOBAL:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local rel="cn/cloudwalk/cloud/cwos-sdk-event/${CWOS_EVENT_VER}"
|
||||
if [[ -d "$GLOBAL_M2/$rel" ]]; then
|
||||
mkdir -p "$M2_LOCAL/$rel"
|
||||
cp -a "$GLOBAL_M2/$rel/." "$M2_LOCAL/$rel/"
|
||||
echo "==> 已从 ~/.m2 预置 cwos-sdk-event: $rel"
|
||||
else
|
||||
echo "WARN: ~/.m2 中无 cwos-sdk-event(${CWOS_EVENT_VER}),将视情况 mvn install legacy 模块。" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
install_cwos_sdk_event_if_missing() {
|
||||
if [[ "${ELEVATOR_AUTO_INSTALL_CWOS_SDK_EVENT:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local marker="$M2_LOCAL/cn/cloudwalk/cloud/cwos-sdk-event/${CWOS_EVENT_VER}/cwos-sdk-event-${CWOS_EVENT_VER}.jar"
|
||||
if [[ -f "$marker" ]]; then
|
||||
echo "==> 隔离库已有 cwos-sdk-event(${CWOS_EVENT_VER})"
|
||||
return 0
|
||||
fi
|
||||
local root def_pom
|
||||
root="$(cd "$REPO/.." && pwd)"
|
||||
def_pom="$root/maven-cloudwalk-legacy-public/cwos-sdk-event/pom.xml"
|
||||
local ev_pom="${ELEVATOR_LEGACY_CWOS_SDK_EVENT_POM:-$def_pom}"
|
||||
if [[ ! -f "$ev_pom" ]]; then
|
||||
echo "ERROR: 私服缺少 cwos-sdk-event:${CWOS_EVENT_VER},且未找到: $ev_pom" >&2
|
||||
exit 1
|
||||
fi
|
||||
echo "==> 向隔离库 install cwos-sdk-event: $ev_pom"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" -f "$ev_pom" install -DskipTests
|
||||
if [[ ! -f "$marker" ]]; then
|
||||
echo "ERROR: install 后仍缺少: $marker" >&2
|
||||
exit 1
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_cwos_sdk_event_from_global
|
||||
install_cwos_sdk_event_if_missing
|
||||
|
||||
bootstrap_cloudwalk_modules_from_global() {
|
||||
if [[ "${ELEVATOR_BOOTSTRAP_CLOUDWALK_MODULES_FROM_GLOBAL:-1}" != "1" ]]; then
|
||||
return 0
|
||||
fi
|
||||
local _arts="${ELEVATOR_CLOUDWALK_BOOTSTRAP_ARTIFACTS:-cloudwalk-common-result cloudwalk-common-web cloudwalk-common-serial cloudwalk-common-service}"
|
||||
local copied=0
|
||||
for a in $_arts; do
|
||||
[[ -n "$a" ]] || continue
|
||||
local rel="cn/cloudwalk/cloud/${a}/${LEGACY_VER}"
|
||||
if [[ -d "$GLOBAL_M2/$rel" ]]; then
|
||||
mkdir -p "$M2_LOCAL/$rel"
|
||||
cp -a "$GLOBAL_M2/$rel/." "$M2_LOCAL/$rel/"
|
||||
echo "==> 已从 ~/.m2 预置 cloudwalk 构件目录: $rel"
|
||||
copied=1
|
||||
fi
|
||||
done
|
||||
if [[ "$copied" -eq 0 ]]; then
|
||||
echo "WARN: ~/.m2 中未找到上述 cloudwalk-common-* 目录(${LEGACY_VER}),将完全依赖 Nexus 传递依赖。" >&2
|
||||
fi
|
||||
}
|
||||
|
||||
bootstrap_cloudwalk_modules_from_global
|
||||
|
||||
echo "==> 清理 cn/cloudwalk 下失败缓存 (*.lastUpdated)"
|
||||
purge_last_updated_under "$M2_LOCAL/cn/cloudwalk"
|
||||
|
||||
MVN_GOALS="${ELEVATOR_MVN_GOALS:-clean package}"
|
||||
|
||||
echo "==> 使用隔离本地仓库: $M2_LOCAL"
|
||||
echo "==> mvn -Dmaven.repo.local=... $MVN_GOALS -DskipTests -U"
|
||||
mvn -Dmaven.repo.local="$M2_LOCAL" -U $MVN_GOALS -DskipTests
|
||||
|
||||
JAR="$REPO/cw-elevator-application-starter/target/cw-elevator-application-2.0.0.jar"
|
||||
echo "==> 产物: $JAR"
|
||||
test -f "$JAR" && ls -la "$JAR"
|
||||
|
||||
if [[ -f "$JAR" && "${ELEVATOR_DEPLOY_V2_MAVEN:-1}" == "1" ]]; then
|
||||
V2_DEPLOY_DIR="${ELEVATOR_DEPLOY_V2_DIR:-$REPO/deploy/v2-maven}"
|
||||
mkdir -p "$V2_DEPLOY_DIR"
|
||||
install -m0644 "$JAR" "$V2_DEPLOY_DIR/cw-elevator-application-2.0.0.jar"
|
||||
echo "==> 已发布到 deploy/v2-maven(替换 JAR): $V2_DEPLOY_DIR/cw-elevator-application-2.0.0.jar"
|
||||
ls -la "$V2_DEPLOY_DIR/cw-elevator-application-2.0.0.jar"
|
||||
fi
|
||||
|
||||
if [[ "${ELEVATOR_SYNC_DEPLOY:-0}" == "1" ]]; then
|
||||
SYNC="$REPO/deploy/sync-jars.sh"
|
||||
if [[ -x "$SYNC" ]] || chmod +x "$SYNC" 2>/dev/null; then
|
||||
echo "==> ELEVATOR_SYNC_DEPLOY=1 -> $SYNC"
|
||||
bash "$SYNC"
|
||||
else
|
||||
echo "WARN: 未找到可执行的 deploy/sync-jars.sh,跳过同步。" >&2
|
||||
fi
|
||||
fi
|
||||
+115
@@ -0,0 +1,115 @@
|
||||
#!/usr/bin/env bash
|
||||
# 将 cw_lib 中的 intelligent-cwoscomponent(父 POM 桩 + interface + rest)部署到 Nexus
|
||||
# 与 docs/operations/deploy_cw_elevator_v1_lib_to_nexus.py 使用同一 server id / 仓库 URL 约定。
|
||||
#
|
||||
# 需要 ~/.m2/settings.xml 中配置 server id:nexus-releases(密码等)。
|
||||
#
|
||||
# 环境变量(可选):
|
||||
# NEXUS_RELEASES_URL 默认 http://192.168.3.12:8081/repository/maven-releases/
|
||||
# NEXUS_RELEASES_ID 默认 nexus-releases
|
||||
# ELEVATOR_CW_LIB_DIR 默认 反编译根下 cw-elevator-application-V1.0.0.20211103/cw_lib
|
||||
# ELEVATOR_ICOMP_VER 默认 2.9.2-xinghewan
|
||||
# DRY_RUN=1 仅打印命令
|
||||
# ELEVATOR_INSTALL_LOCAL_M2=1(默认)部署后再把 cw_lib 安装到 ~/.m2(与 build_nexus_only
|
||||
# 一致;因私服上子 POM 缺版本段时 dependency:get 可能无效模型,本地以桩父 POM+install-file 为准)
|
||||
# ELEVATOR_INSTALL_LOCAL_M2=0 跳过本机安装
|
||||
set -euo pipefail
|
||||
|
||||
SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
|
||||
REPO_ROOT="$(cd "$SCRIPT_DIR/../.." && pwd)"
|
||||
REPO_ROOT="${ELEVATOR_REPO_ROOT_OVERRIDE:-$REPO_ROOT}"
|
||||
|
||||
NEXUS_RELEASES_URL="${NEXUS_RELEASES_URL:-http://192.168.3.12:8081/repository/maven-releases/}"
|
||||
NEXUS_RELEASES_ID="${NEXUS_RELEASES_ID:-nexus-releases}"
|
||||
ICOMP_VER="${ELEVATOR_ICOMP_VER:-2.9.2-xinghewan}"
|
||||
CW_LIB="${ELEVATOR_CW_LIB_DIR:-$REPO_ROOT/cw-elevator-application-V1.0.0.20211103/cw_lib}"
|
||||
PARENT_STUB="$SCRIPT_DIR/legacy-poms/intelligent-cwoscomponent-2.9.2-xinghewan-parent.pom"
|
||||
|
||||
die() { echo "ERROR: $*" >&2; exit 1; }
|
||||
|
||||
[[ -f "$PARENT_STUB" ]] || die "缺少父 POM 桩: $PARENT_STUB"
|
||||
[[ -d "$CW_LIB" ]] || die "cw_lib 目录不存在: $CW_LIB"
|
||||
|
||||
IJ="$CW_LIB/intelligent-cwoscomponent-interface-${ICOMP_VER}.jar"
|
||||
IP="$CW_LIB/intelligent-cwoscomponent-interface-${ICOMP_VER}.pom"
|
||||
RJ="$CW_LIB/intelligent-cwoscomponent-rest-${ICOMP_VER}.jar"
|
||||
RP="$CW_LIB/intelligent-cwoscomponent-rest-${ICOMP_VER}.pom"
|
||||
|
||||
for f in "$IJ" "$IP" "$RJ" "$RP"; do
|
||||
[[ -f "$f" ]] || die "缺少文件: $f"
|
||||
done
|
||||
|
||||
# 在无 reactor pom 的目录执行 deploy:deploy-file,避免误挂到 cw-elevator-application-reactor。
|
||||
# Release 仓库已存在同版本时 Nexus 返回 400 cannot be updated — 视为已发布,跳过。
|
||||
deploy_file_or_skip() {
|
||||
local label="$1"
|
||||
shift
|
||||
if [[ "${DRY_RUN:-}" == "1" ]]; then
|
||||
echo "DRY-RUN [$label]:" "$@"
|
||||
return 0
|
||||
fi
|
||||
local out rc
|
||||
set +e
|
||||
out="$(cd "$CW_LIB" && mvn -B -q deploy:deploy-file "$@" 2>&1)"
|
||||
rc=$?
|
||||
set -e
|
||||
if [[ "$rc" -eq 0 ]]; then
|
||||
echo "OK [$label]"
|
||||
return 0
|
||||
fi
|
||||
if echo "$out" | grep -qE 'cannot be updated|status code: 400'; then
|
||||
echo "SKIP [$label](Nexus 已存在该 release 坐标,不可覆盖)"
|
||||
return 0
|
||||
fi
|
||||
echo "$out" >&2
|
||||
return "$rc"
|
||||
}
|
||||
|
||||
echo "==> Nexus releases: $NEXUS_RELEASES_URL (id=$NEXUS_RELEASES_ID)"
|
||||
echo "==> cw_lib: $CW_LIB"
|
||||
|
||||
echo "==> [1/3] deploy 父 POM cn.cloudwalk.intelligent:intelligent-cwoscomponent:${ICOMP_VER}"
|
||||
deploy_file_or_skip "parent pom" \
|
||||
-DrepositoryId="$NEXUS_RELEASES_ID" \
|
||||
-Durl="$NEXUS_RELEASES_URL" \
|
||||
-Dfile="$PARENT_STUB" \
|
||||
-DpomFile="$PARENT_STUB" \
|
||||
-DgroupId=cn.cloudwalk.intelligent \
|
||||
-DartifactId=intelligent-cwoscomponent \
|
||||
-Dversion="$ICOMP_VER" \
|
||||
-Dpackaging=pom
|
||||
|
||||
echo "==> [2/3] deploy intelligent-cwoscomponent-interface"
|
||||
deploy_file_or_skip "interface" \
|
||||
-DrepositoryId="$NEXUS_RELEASES_ID" \
|
||||
-Durl="$NEXUS_RELEASES_URL" \
|
||||
-Dfile="$IJ" \
|
||||
-DpomFile="$IP" \
|
||||
-Dpackaging=jar
|
||||
|
||||
echo "==> [3/3] deploy intelligent-cwoscomponent-rest"
|
||||
deploy_file_or_skip "rest" \
|
||||
-DrepositoryId="$NEXUS_RELEASES_ID" \
|
||||
-Durl="$NEXUS_RELEASES_URL" \
|
||||
-Dfile="$RJ" \
|
||||
-DpomFile="$RP" \
|
||||
-Dpackaging=jar
|
||||
|
||||
if [[ "${DRY_RUN:-}" == "1" ]]; then
|
||||
echo "DRY-RUN 结束。"
|
||||
exit 0
|
||||
fi
|
||||
|
||||
if [[ "${ELEVATOR_INSTALL_LOCAL_M2:-1}" == "1" ]]; then
|
||||
echo "==> 本机 ~/.m2:父 POM 桩 + install-file(与 build_nexus_only 闭包一致)"
|
||||
mvn -B -q -N -f "$PARENT_STUB" install -DskipTests
|
||||
mvn -B -q org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file \
|
||||
-DpomFile="$IP" -Dfile="$IJ" -Dpackaging=jar
|
||||
mvn -B -q org.apache.maven.plugins:maven-install-plugin:3.1.1:install-file \
|
||||
-DpomFile="$RP" -Dfile="$RJ" -Dpackaging=jar
|
||||
else
|
||||
echo "==> 跳过本机 ~/.m2(ELEVATOR_INSTALL_LOCAL_M2=0)"
|
||||
fi
|
||||
|
||||
echo "完成。"
|
||||
echo " ~/.m2: ls \"\$HOME/.m2/repository/cn/cloudwalk/intelligent/intelligent-cwoscomponent-rest/$ICOMP_VER/\""
|
||||
+56
@@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
|
||||
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
<parent>
|
||||
<groupId>org.springframework.boot</groupId>
|
||||
<artifactId>spring-boot-starter-parent</artifactId>
|
||||
<version>2.1.18.RELEASE</version>
|
||||
<relativePath/>
|
||||
</parent>
|
||||
<groupId>cn.cloudwalk.intelligent</groupId>
|
||||
<artifactId>intelligent-cwoscomponent</artifactId>
|
||||
<version>2.9.2-xinghewan</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>intelligent-cwoscomponent (2.9.2 parent stub)</name>
|
||||
<description>与 V1 cw_lib 内 intelligent-cwoscomponent-*.pom 中 parent 坐标一致;DM 与电梯 reactor 3.7.2 / Greenwich.SR6 对齐,供 install-file 安装到本机库或隔离库。</description>
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
|
||||
<cloudwalk.legacy.public.version>3.7.2-Brussels-SRX</cloudwalk.legacy.public.version>
|
||||
<cloudwalk.internal.version>3.7.2-Brussels-SRX</cloudwalk.internal.version>
|
||||
<fastjson.version>1.2.73</fastjson.version>
|
||||
<cloudwalk.device.sdk.version>2.2.0</cloudwalk.device.sdk.version>
|
||||
</properties>
|
||||
<dependencyManagement>
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>org.springframework.cloud</groupId>
|
||||
<artifactId>spring-cloud-dependencies</artifactId>
|
||||
<version>${spring-cloud.version}</version>
|
||||
<type>pom</type>
|
||||
<scope>import</scope>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cloudwalk-common-result</artifactId>
|
||||
<version>${cloudwalk.legacy.public.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk.cloud</groupId>
|
||||
<artifactId>cloudwalk-common-service</artifactId>
|
||||
<version>${cloudwalk.internal.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.alibaba</groupId>
|
||||
<artifactId>fastjson</artifactId>
|
||||
<version>${fastjson.version}</version>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>cn.cloudwalk</groupId>
|
||||
<artifactId>cloudwalk-device-sdk-protocol-entity</artifactId>
|
||||
<version>${cloudwalk.device.sdk.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</dependencyManagement>
|
||||
</project>
|
||||
@@ -29,6 +29,6 @@ echo " (对拍为 HTTP; 需另开终端分别 java -jar --server.port=18080/
|
||||
echo "==> 单元(无联调): test_unit_compare"
|
||||
export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
|
||||
(cd "$TOOL" && python3 -m pytest tests/test_unit_compare.py -q --tb=short)
|
||||
echo "==> 对拍(无两实例时跳过 test_parity_endpoints): 全部"
|
||||
(cd "$TOOL" && python3 -m pytest tests/ -q --tb=line)
|
||||
echo "==> 对拍(无两实例时跳过 test_parity_endpoints);不含单机 smoke"
|
||||
(cd "$TOOL" && python3 -m pytest tests/test_unit_compare.py tests/test_parity_endpoints.py -q --tb=line)
|
||||
echo "报告: $TOOL/report/ (对拍有执行且成功时由 pytest 会话写出 parity-*.md)"
|
||||
|
||||
@@ -0,0 +1,64 @@
|
||||
#!/usr/bin/env bash
|
||||
# 完整接口测试:V1/V2 单机冒烟 + 双端对拍 + 套件总览 Markdown。
|
||||
# 前置:两 JAR 已分别启动(默认 http://127.0.0.1:18080 为 V1、18081 为 V2),且配置同一数据源/Redis。
|
||||
# 用法:在 maven-cw-elevator-application 目录执行 ./scripts/run_full_elevator_api_suite.sh
|
||||
set -euo pipefail
|
||||
|
||||
REPO="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
TOOL="${REPO}/tools/elevator_api_parity"
|
||||
MARKER="${TOOL}/.suite_run_marker"
|
||||
|
||||
export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
|
||||
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
||||
export ELEVATOR_BASE_OLD="${ELEVATOR_BASE_OLD:-http://127.0.0.1:18080}"
|
||||
export ELEVATOR_BASE_NEW="${ELEVATOR_BASE_NEW:-http://127.0.0.1:18081}"
|
||||
|
||||
cd "$TOOL"
|
||||
python3 -m pip install -q -r requirements.txt 2>/dev/null || true
|
||||
|
||||
touch "$MARKER"
|
||||
|
||||
# -rs 在结束时打印 skip 原因;s=skip 通常表示目标端口无服务或 /actuator/health 无 200
|
||||
PTF="-q -rs --tb=line"
|
||||
|
||||
echo "==> 单元(compare 逻辑)"
|
||||
python3 -m pytest tests/test_unit_compare.py -q --tb=short
|
||||
|
||||
echo "==> 冒烟 V1 ($ELEVATOR_BASE_OLD) [需该地址可访问 /actuator/health 等健康端点]"
|
||||
python3 -m pytest tests/test_smoke_catalog.py -m smoke \
|
||||
--smoke-base="$ELEVATOR_BASE_OLD" --smoke-label=v1_legacy $PTF || true
|
||||
|
||||
echo "==> 冒烟 V2 ($ELEVATOR_BASE_NEW) [需该地址可访问 /actuator/health 等健康端点]"
|
||||
python3 -m pytest tests/test_smoke_catalog.py -m smoke \
|
||||
--smoke-base="$ELEVATOR_BASE_NEW" --smoke-label=v2_build $PTF || true
|
||||
|
||||
echo "==> 横向对拍(两实例均需通过健康探针)"
|
||||
python3 -m pytest tests/test_parity_endpoints.py -m live $PTF \
|
||||
--base-old="$ELEVATOR_BASE_OLD" --base-new="$ELEVATOR_BASE_NEW" || true
|
||||
|
||||
SUITE_TS="$(date +%Y%m%d-%H%M%S)"
|
||||
_pick_newer() {
|
||||
local pattern="$1"
|
||||
local f=""
|
||||
for f in $(ls -t "${TOOL}/report"/${pattern} 2>/dev/null); do
|
||||
if [[ -f "$f" && "$f" -nt "$MARKER" ]]; then echo "$f"; return; fi
|
||||
done
|
||||
}
|
||||
SMOKE_V1="$(_pick_newer 'smoke-v1_legacy-*.md')"
|
||||
SMOKE_V2="$(_pick_newer 'smoke-v2_build-*.md')"
|
||||
PARITY="$(_pick_newer 'parity-*.md')"
|
||||
|
||||
OUT="${TOOL}/report/SUITE-${SUITE_TS}.md"
|
||||
python3 report/generate_suite_summary.py --out "$OUT" \
|
||||
--catalog "${TOOL}/api_catalog.json" \
|
||||
${PARITY:+--parity "$PARITY"} \
|
||||
${SMOKE_V1:+--smoke-v1 "$SMOKE_V1"} \
|
||||
${SMOKE_V2:+--smoke-v2 "$SMOKE_V2"}
|
||||
|
||||
echo "==> 套件总览: $OUT"
|
||||
if [[ -z "$SMOKE_V1" && -z "$SMOKE_V2" && -z "$PARITY" ]]; then
|
||||
echo "==> 提示: 未生成 smoke/parity 子报告(多为本机未起 JAR 或健康检查未通过)。"
|
||||
echo " 先起两进程: java -jar V1.jar --server.port=18080 --spring.config.location=... 与 V2 用 18081"
|
||||
echo " 快速探活: curl -s -o /dev/null -w '%{http_code}' $ELEVATOR_BASE_OLD/actuator/health"
|
||||
fi
|
||||
rm -f "$MARKER"
|
||||
@@ -2,3 +2,7 @@ __pycache__/
|
||||
*.pyc
|
||||
.pytest_cache/
|
||||
report/parity-*.md
|
||||
report/smoke-*.md
|
||||
report/SUITE-*.md
|
||||
report/*.json
|
||||
.suite_run_marker
|
||||
|
||||
@@ -1,44 +1,92 @@
|
||||
# elevator_api_parity — 新旧 JAR 接口对拍
|
||||
# elevator_api_parity — V1/V2 接口冒烟与对拍
|
||||
|
||||
## 功能概览
|
||||
|
||||
| 能力 | 说明 |
|
||||
|------|------|
|
||||
| **单机冒烟** | 对单个 Base URL 遍历 `api_catalog.json` 中的接口(`include_in_smoke=true`),记录 HTTP 状态、耗时、业务 `code`、响应摘要 → `report/smoke-{label}-*.md` |
|
||||
| **横向对拍** | 旧 JAR(`--base-old`)与新 JAR(`--base-new`)并行调用;仅 **`include_in_parity=true`** 的条目参与 **HTTP 状态 + 业务 code** 一致性断言 → `report/parity-*.md` |
|
||||
| **套件总览** | 合并本次产生的冒烟×2 + 对拍 → `report/SUITE-*.md` |
|
||||
|
||||
接口清单与请求体见 **`api_catalog.json`**(支持 `fixture` 文件或内联 `body`)。
|
||||
|
||||
## 环境
|
||||
|
||||
- Python 3.8+
|
||||
- 安装:`pip install -r requirements.txt`(在**本目录**下执行)
|
||||
- Python 3.8+,`pip install -r requirements.txt`
|
||||
- 若遇全局 pytest 插件冲突:`export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1`
|
||||
|
||||
## 环境说明
|
||||
## 启动两实例(示例)
|
||||
|
||||
本机若安装过 `allure_pytest` 等全局 pytest 插件且与 Python 版本冲突,请执行:
|
||||
|
||||
`export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1`(`run_elevator_parity.sh` 已自动设置)后再跑 `pytest`。
|
||||
|
||||
## 快速使用
|
||||
|
||||
1. 构建新 JAR(在 reactor 根目录、JDK8)
|
||||
`mvn -DskipTests clean package`
|
||||
得到:`../cw-elevator-application-starter/target/cw-elevator-application-2.0.0.jar`
|
||||
2. 将历史 JAR 放到 `cw-elevator-application-V1.0.0.20211103/cw-elevator-application-V1.0.0.20211103.jar` 或设 `ELEVATOR_JAR_LEGACY`。
|
||||
3. 准备**与现网相同**的 `application.yml`(或目录),如 `ELEVATOR_SPRING_CONFIG=file:/path/to/application-elevator.yml`。
|
||||
4. 从仓库**反编译**根或本目录执行:
|
||||
`../../scripts/run_elevator_parity.sh`(见该脚本内环境变量说明)
|
||||
|
||||
或手动启动两实例后:
|
||||
同一套 `application.properties`(或外部配置),仅端口不同:
|
||||
|
||||
```bash
|
||||
export ELEVATOR_BASE_OLD=http://127.0.0.1:18080
|
||||
export ELEVATOR_BASE_NEW=http://127.0.0.1:18081
|
||||
export ELEVATOR_HEADER_BUSINESSID=... # 与现网/联调一致
|
||||
# ...
|
||||
cd tools/elevator_api_parity
|
||||
python -m pytest tests/ -v --tb=short
|
||||
export JAVA_HOME=/usr/lib/jvm/java-8-openjdk-amd64
|
||||
CFG="file:/path/to/application.properties"
|
||||
java -jar cw-elevator-application-V1.0.0.20211103.jar --server.port=18080 --spring.config.location=$CFG
|
||||
java -jar cw-elevator-application-2.0.0.jar --server.port=18081 --spring.config.location=$CFG
|
||||
```
|
||||
|
||||
## 配置
|
||||
## 一键完整套件(推荐)
|
||||
|
||||
- `api_catalog.json`:对拍端点、方法、fixture 文件名、说明。
|
||||
- `fixtures/*.json`:各接口请求体。
|
||||
- 归一化忽略键可扩展 `parity/compare.py` 中 `DEFAULT_STRIP_PATHS`(如 `data.rows` 内动态字段,谨慎)。
|
||||
在 **`maven-cw-elevator-application`** 目录:
|
||||
|
||||
## 报告
|
||||
```bash
|
||||
./scripts/run_full_elevator_api_suite.sh
|
||||
```
|
||||
|
||||
- `report/parity-YYYYMMDD-HHMMSS.md`:实跑时由 `conftest` 的 session hook 与 `report/generate_report.py` 组合生成。
|
||||
- JUnit(可选):`--junitxml=report/junit.xml`
|
||||
环境变量(可选):
|
||||
|
||||
- `ELEVATOR_BASE_OLD` — 默认 `http://127.0.0.1:18080`(V1)
|
||||
- `ELEVATOR_BASE_NEW` — 默认 `http://127.0.0.1:18081`(V2)
|
||||
- `ELEVATOR_HEADER_BUSINESSID`、`ELEVATOR_HEADER_LOGINID`、`ELEVATOR_HEADER_AUTHORIZATION` 等 — 与现网一致时业务码更有可比性
|
||||
|
||||
## 分步执行
|
||||
|
||||
```bash
|
||||
cd tools/elevator_api_parity
|
||||
export PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
|
||||
|
||||
# 仅逻辑单测
|
||||
python3 -m pytest tests/test_unit_compare.py -q
|
||||
|
||||
# V1 单机冒烟
|
||||
python3 -m pytest tests/test_smoke_catalog.py -m smoke \
|
||||
--smoke-base=http://127.0.0.1:18080 --smoke-label=v1_legacy -q
|
||||
|
||||
# V2 单机冒烟
|
||||
python3 -m pytest tests/test_smoke_catalog.py -m smoke \
|
||||
--smoke-base=http://127.0.0.1:18081 --smoke-label=v2_build -q
|
||||
|
||||
# 双端对拍(双端 /actuator/health 等可达时执行,否则跳过)
|
||||
python3 -m pytest tests/test_parity_endpoints.py -m live -q \
|
||||
--base-old=http://127.0.0.1:18080 --base-new=http://127.0.0.1:18081
|
||||
```
|
||||
|
||||
### 强制要求联调(失败即中断)
|
||||
|
||||
```bash
|
||||
export ELEVATOR_PARITY_REQUIRE_LIVE=1 # 对拍
|
||||
export ELEVATOR_SMOKE_REQUIRE=1 # 冒烟
|
||||
```
|
||||
|
||||
## 仅对拍(不含冒烟)
|
||||
|
||||
```bash
|
||||
./scripts/run_elevator_parity.sh
|
||||
```
|
||||
|
||||
(脚本内会先 `mvn package`,再跑单测 + 对拍。)
|
||||
|
||||
## 报告位置
|
||||
|
||||
- `report/smoke-v1_legacy-*.md` + **同名 `.json`**(结构化结果,供套件矩阵消费)
|
||||
- `report/smoke-v2_build-*.md` + **同名 `.json`**
|
||||
- `report/parity-*.md` + **同名 `.json`**
|
||||
- **`report/SUITE-*.md`**:始终包含 **第二节「全量接口清单」**(来源于 `api_catalog.json`);**第三节「测试结果矩阵」** 在有上述 JSON 时填入 V1/V2 HTTP、业务 code、对拍 Y/N;若本次因未起服务而 **skip**,矩阵中为 **—**(参见第三节说明)。
|
||||
|
||||
## 扩展接口
|
||||
|
||||
编辑 **`api_catalog.json`**:
|
||||
|
||||
- `include_in_parity: false` — 只做冒烟,不参与 V1/V2 等值断言(避免依赖不一致导致误报)。
|
||||
- `include_in_parity: true` — 纳入横向对拍(当前默认对 **访客/人员/规则分页/记录分页** 四个核心场景开启)。
|
||||
|
||||
@@ -1,21 +1,26 @@
|
||||
{
|
||||
"version": 1,
|
||||
"version": 2,
|
||||
"description": "电梯应用 HTTP 接口清单:冒烟与对拍共用。include_in_parity=false 的项仅做单机路由/业务响应探测,不参与 V1/V2 等值断言。",
|
||||
"endpoints": [
|
||||
{
|
||||
"id": "person_add_visitor_min",
|
||||
"name": "访客派梯-最小体(多依赖业务失败,仅比对 HTTP+业务code)",
|
||||
"name": "访客派梯-最小体",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/add/visitor",
|
||||
"fixture": "person_add_visitor_min.json",
|
||||
"compare_mode": "code_only"
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": true,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_detail",
|
||||
"name": "人员详情-最小分页体",
|
||||
"name": "人员详情-分页",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/detail",
|
||||
"fixture": "person_detail_min.json",
|
||||
"compare_mode": "code_only"
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": true,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_page",
|
||||
@@ -23,7 +28,9 @@
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/page",
|
||||
"fixture": "passrule_page.json",
|
||||
"compare_mode": "code_only"
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": true,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_page",
|
||||
@@ -31,7 +38,399 @@
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/page",
|
||||
"fixture": "record_page.json",
|
||||
"compare_mode": "code_only"
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": true,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_add",
|
||||
"name": "人员-从现有人员添加",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/add",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_edit",
|
||||
"name": "人员-编辑",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/edit",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_delete",
|
||||
"name": "人员-删除",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/delete",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_page",
|
||||
"name": "人员-分页",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/page",
|
||||
"body": { "pageNo": 1, "pageSize": 1 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_time_detail",
|
||||
"name": "人员-时间详情",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/timeDetail",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "person_page_by_app",
|
||||
"name": "人员-应用分页",
|
||||
"method": "POST",
|
||||
"path": "/elevator/person/pageByApp",
|
||||
"body": { "pageNo": 1, "pageSize": 1 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_floor",
|
||||
"name": "通行规则-楼层列表",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/floor",
|
||||
"body": { "pageNo": 1, "pageSize": 1, "zoneId": "" },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_add",
|
||||
"name": "通行规则-新增",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/add",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_edit",
|
||||
"name": "通行规则-修改",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/edit",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_delete",
|
||||
"name": "通行规则-删除",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/delete",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_detail",
|
||||
"name": "通行规则-详情",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/detail",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_image",
|
||||
"name": "通行规则-按人像查楼层权限",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/image",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "passrule_image_list",
|
||||
"name": "通行规则-批量人像",
|
||||
"method": "POST",
|
||||
"path": "/elevator/passRule/image/list",
|
||||
"body": { "personList": [] },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_analyse_cycle",
|
||||
"name": "记录-周期统计",
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/analyse/cycle",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_analyse_count",
|
||||
"name": "记录-次数统计",
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/analyse/count",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_page_request",
|
||||
"name": "记录-请求分页",
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/page/request",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_device_list",
|
||||
"name": "记录-设备列表",
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/device/list",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "record_zone_tree",
|
||||
"name": "记录-区域树",
|
||||
"method": "POST",
|
||||
"path": "/intelligent/acs/elevator/record/zone/tree",
|
||||
"body": { "parentId": "", "businessId": "" },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "device_v2_39201",
|
||||
"name": "设备网关-39201 设备列表",
|
||||
"method": "POST",
|
||||
"path": "/device/v2/39201",
|
||||
"body": { "deviceName": "_api_probe", "currentPage": 1, "rowsOfPage": 10 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "device_v2_39202",
|
||||
"name": "设备网关-39202 区域电梯码",
|
||||
"method": "POST",
|
||||
"path": "/device/v2/39202",
|
||||
"body": { "deviceName": "_api_probe", "currentPage": 1, "rowsOfPage": 10 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "device_v2_39203",
|
||||
"name": "设备网关-39203 添加记录",
|
||||
"method": "POST",
|
||||
"path": "/device/v2/39203",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "device_v2_39204",
|
||||
"name": "设备网关-39204 密钥时间戳(已废弃)",
|
||||
"method": "POST",
|
||||
"path": "/device/v2/39204",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_add",
|
||||
"name": "设备-新增",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/add",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_edit",
|
||||
"name": "设备-编辑",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/edit",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_get_by_id",
|
||||
"name": "设备-按ID查询",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/getById",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_delete",
|
||||
"name": "设备-删除",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/delete",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_edit_code",
|
||||
"name": "设备-改码",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/editCode",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_get",
|
||||
"name": "设备-查询列表",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/get",
|
||||
"body": { "deviceName": "_api_probe", "currentPage": 1, "rowsOfPage": 10 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_export",
|
||||
"name": "设备-导出",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/export",
|
||||
"body": { "deviceName": "_api_probe", "currentPage": 1, "rowsOfPage": 10 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_zone_tree_code",
|
||||
"name": "设备-区域树编码",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/zone/treeCode",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "elevator_device_page",
|
||||
"name": "设备-分页",
|
||||
"method": "POST",
|
||||
"path": "/elevator/device/devicePage",
|
||||
"body": { "deviceName": "_api_probe", "currentPage": 1, "rowsOfPage": 10 },
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_unbind_floors",
|
||||
"name": "改造-未绑定楼层",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/unbind/floors",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_floors",
|
||||
"name": "改造-楼层列表",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/floors",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_condition",
|
||||
"name": "改造-条件",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/condition",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_condition_labels",
|
||||
"name": "改造-条件标签",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/condition/labels",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_binding",
|
||||
"name": "改造-绑定",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/binding",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_binding_person",
|
||||
"name": "改造-绑定人员",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/binding/person",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_task_progress",
|
||||
"name": "改造-任务进度",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/task/progress",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
},
|
||||
{
|
||||
"id": "restructure_task_stop",
|
||||
"name": "改造-停止任务",
|
||||
"method": "POST",
|
||||
"path": "/elevator/restructure/task/stop",
|
||||
"body": {},
|
||||
"compare_mode": "code_only",
|
||||
"include_in_parity": false,
|
||||
"include_in_smoke": true
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,18 +1,20 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import os
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
import requests
|
||||
|
||||
from parity.client import can_reach_both, default_headers
|
||||
from parity.client import can_reach_both, can_reach_one, default_headers
|
||||
|
||||
_DIR = Path(__file__).resolve().parent
|
||||
|
||||
|
||||
def pytest_configure(config):
|
||||
config._parity_rows = [] # type: ignore[attr-defined]
|
||||
config._smoke_rows = [] # type: ignore[attr-defined]
|
||||
|
||||
|
||||
def pytest_addoption(parser):
|
||||
@@ -24,6 +26,14 @@ def pytest_addoption(parser):
|
||||
"--base-new",
|
||||
default=os.environ.get("ELEVATOR_BASE_NEW", "http://127.0.0.1:18081"),
|
||||
)
|
||||
parser.addoption(
|
||||
"--smoke-base",
|
||||
default=os.environ.get("ELEVATOR_SMOKE_BASE", "http://127.0.0.1:18080"),
|
||||
)
|
||||
parser.addoption(
|
||||
"--smoke-label",
|
||||
default=os.environ.get("ELEVATOR_SMOKE_LABEL", "v1_legacy"),
|
||||
)
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
@@ -36,6 +46,16 @@ def base_new(request):
|
||||
return str(request.config.getoption("--base-new")).rstrip("/")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def smoke_base(request):
|
||||
return str(request.config.getoption("--smoke-base")).rstrip("/")
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def smoke_label(request):
|
||||
return str(request.config.getoption("--smoke-label"))
|
||||
|
||||
|
||||
@pytest.fixture(scope="session")
|
||||
def session_http():
|
||||
s = requests.Session()
|
||||
@@ -55,27 +75,56 @@ def two_instances_ready(base_old, base_new, session_http, request):
|
||||
return True
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
rows = getattr(session.config, "_parity_rows", None)
|
||||
if not rows:
|
||||
return
|
||||
try:
|
||||
from report import generate_report
|
||||
@pytest.fixture(scope="session")
|
||||
def smoke_instance_ready(smoke_base, session_http, request):
|
||||
ok, _ = can_reach_one(smoke_base, session_http)
|
||||
require = os.environ.get("ELEVATOR_SMOKE_REQUIRE", "")
|
||||
if not ok and not require:
|
||||
pytest.skip(f"单机 {smoke_base} 健康检查不通过(跳过 smoke)")
|
||||
if not ok and require:
|
||||
pytest.fail(f"ELEVATOR_SMOKE_REQUIRE=1 且 {smoke_base} 不可达")
|
||||
return True
|
||||
|
||||
p = _DIR / "report" / generate_report.timestamped_name("parity")
|
||||
p.parent.mkdir(parents=True, exist_ok=True)
|
||||
generate_report.write_file(
|
||||
|
||||
def _write_smoke_report(config, srows: list, report_dir: Path) -> None:
|
||||
from report import generate_smoke_report
|
||||
|
||||
label = str(config.getoption("--smoke-label", default="smoke"))
|
||||
p2 = report_dir / f"smoke-{label}-{datetime.now().strftime('%Y%m%d-%H%M%S')}.md"
|
||||
generate_smoke_report.write_file(
|
||||
p2,
|
||||
str(config.getoption("--smoke-base", default="")),
|
||||
label,
|
||||
srows,
|
||||
)
|
||||
print(f"\n[smoke] 报告: {p2}")
|
||||
|
||||
|
||||
def pytest_sessionfinish(session, exitstatus):
|
||||
import importlib
|
||||
|
||||
config = session.config
|
||||
report_dir = _DIR / "report"
|
||||
report_dir.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
rows = getattr(config, "_parity_rows", None) or []
|
||||
if rows:
|
||||
try:
|
||||
gen = importlib.import_module("report.generate_report")
|
||||
p = report_dir / gen.timestamped_name("parity")
|
||||
gen.write_file(
|
||||
p,
|
||||
str(session.config.getoption("--base-old", default="")),
|
||||
str(session.config.getoption("--base-new", default="")),
|
||||
str(config.getoption("--base-old", default="")),
|
||||
str(config.getoption("--base-new", default="")),
|
||||
rows,
|
||||
)
|
||||
print(f"\n[parity] 报告: {p}")
|
||||
print(f"\n[parity] 对拍报告: {p}")
|
||||
except Exception as e:
|
||||
print(f"\n[parity] 报告未生成: {e}")
|
||||
print(f"\n[parity] 对拍报告未生成: {e}")
|
||||
|
||||
|
||||
def load_catalog() -> dict:
|
||||
from parity import catalog_loader
|
||||
|
||||
return catalog_loader.load()
|
||||
srows = getattr(config, "_smoke_rows", None) or []
|
||||
if srows:
|
||||
try:
|
||||
_write_smoke_report(config, srows, report_dir)
|
||||
except Exception as e:
|
||||
print(f"\n[smoke] 报告未生成: {e}")
|
||||
|
||||
@@ -1,8 +1,42 @@
|
||||
import json
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, Optional
|
||||
|
||||
_ROOT = Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def load() -> dict:
|
||||
return json.loads((_ROOT / "api_catalog.json").read_text(encoding="utf-8"))
|
||||
|
||||
|
||||
def endpoint_body(ep: Dict[str, Any]) -> Dict[str, Any]:
|
||||
"""Resolve request JSON: inline ``body`` wins, else load ``fixture`` file."""
|
||||
if ep.get("body") is not None:
|
||||
return dict(ep["body"])
|
||||
fix = ep.get("fixture")
|
||||
if fix:
|
||||
p = _ROOT / "fixtures" / fix
|
||||
return json.loads(p.read_text(encoding="utf-8"))
|
||||
return {}
|
||||
|
||||
|
||||
def iter_endpoints(catalog: dict, *, tag: Optional[str] = None) -> list[dict]:
|
||||
"""If ``tag`` is set, only endpoints with ``tags`` containing it (or legacy entries with no tags = all)."""
|
||||
out: list[dict] = []
|
||||
for ep in catalog.get("endpoints", []):
|
||||
tags = ep.get("tags") or []
|
||||
if tag is None:
|
||||
out.append(ep)
|
||||
elif not tags:
|
||||
out.append(ep)
|
||||
elif tag in tags:
|
||||
out.append(ep)
|
||||
return out
|
||||
|
||||
|
||||
def include_in_parity(ep: dict) -> bool:
|
||||
return bool(ep.get("include_in_parity", True))
|
||||
|
||||
|
||||
def include_in_smoke(ep: dict) -> bool:
|
||||
return bool(ep.get("include_in_smoke", True))
|
||||
|
||||
@@ -2,6 +2,7 @@ from __future__ import annotations
|
||||
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from dataclasses import dataclass
|
||||
from typing import Any, Optional
|
||||
|
||||
@@ -124,6 +125,52 @@ def _safe_json(text: str) -> Any:
|
||||
return None
|
||||
|
||||
|
||||
def call_single(
|
||||
name: str,
|
||||
method: str,
|
||||
path: str,
|
||||
body: Any,
|
||||
base_url: str,
|
||||
session: requests.Session | None = None,
|
||||
) -> dict[str, Any]:
|
||||
"""One HTTP call for smoke coverage; returns a flat dict for reporting."""
|
||||
s = session or requests.Session()
|
||||
h = default_headers()
|
||||
url = base_url.rstrip("/") + path
|
||||
data = (
|
||||
None
|
||||
if body is None
|
||||
else (body if isinstance(body, str) else json.dumps(body, ensure_ascii=False))
|
||||
)
|
||||
m = method.upper()
|
||||
t0 = time.perf_counter()
|
||||
if m == "GET":
|
||||
r = s.get(url, headers=h, timeout=120)
|
||||
else:
|
||||
r = s.post(url, headers=h, data=data, timeout=120)
|
||||
elapsed_ms = int((time.perf_counter() - t0) * 1000)
|
||||
txt = r.text or ""
|
||||
oj = _safe_json(txt)
|
||||
bc = compare.business_code(oj) if oj is not None else None
|
||||
head = txt[:400].replace("\n", " ")
|
||||
return {
|
||||
"name": name,
|
||||
"method": m,
|
||||
"path": path,
|
||||
"http_status": r.status_code,
|
||||
"elapsed_ms": elapsed_ms,
|
||||
"business_code": bc,
|
||||
"response_head": head,
|
||||
"reachable": True,
|
||||
}
|
||||
|
||||
|
||||
def can_reach_one(base_url: str, s: requests.Session | None = None) -> tuple[bool, str]:
|
||||
s = s or requests.Session()
|
||||
_, ok, _ = probe_healthy(base_url, s)
|
||||
return ok, base_url
|
||||
|
||||
|
||||
def can_reach_both(
|
||||
base_old: str, base_new: str, s: requests.Session | None = None
|
||||
) -> tuple[bool, str]:
|
||||
|
||||
@@ -4,5 +4,6 @@ addopts = -q --strict-markers
|
||||
testpaths = tests
|
||||
markers =
|
||||
live: 需要两实例可访问
|
||||
smoke: 单机全量 HTTP 探测
|
||||
unit: 纯逻辑单测
|
||||
pythonpath = .
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
@@ -40,3 +41,16 @@ def write_file(
|
||||
f"\n## 汇总\n- 通过: {ok},不一致: {bad}。\n- **上线前请人工与联调/业务确认**。\n"
|
||||
)
|
||||
out_path.write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
payload = {
|
||||
"meta": {
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"base_old": base_old,
|
||||
"base_new": base_new,
|
||||
"markdown": str(out_path.resolve()),
|
||||
},
|
||||
"rows": rows,
|
||||
"summary": {"match_ok": ok, "match_bad": bad},
|
||||
}
|
||||
json_path = out_path.with_suffix(".json")
|
||||
json_path.write_text(json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8")
|
||||
|
||||
+53
@@ -0,0 +1,53 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import List
|
||||
|
||||
|
||||
def write_file(
|
||||
out_path: Path,
|
||||
base_url: str,
|
||||
label: str,
|
||||
rows: List[dict],
|
||||
) -> None:
|
||||
lines = [
|
||||
f"# 电梯应用 API 单机冒烟报告 — {label}\n",
|
||||
f"- **时间**: {datetime.now().isoformat()}\n",
|
||||
f"- **Base URL**: {base_url}\n",
|
||||
"\n## 接口探测\n\n",
|
||||
"| 用例 | 方法+路径 | HTTP | ms | 业务code | 响应摘要 |\n",
|
||||
"| ---- | -------- | ---- | -- | -------- | -------- |\n",
|
||||
]
|
||||
ok_http = 0
|
||||
for r in rows:
|
||||
name = r.get("name", "")
|
||||
pth = f"{r.get('method', '')} {r.get('path', '')}"
|
||||
hs = r.get("http_status", "")
|
||||
ms = r.get("elapsed_ms", "")
|
||||
bc = r.get("business_code", "") or ""
|
||||
head = (r.get("response_head", "") or "").replace("|", "\\|")[:180]
|
||||
if isinstance(hs, int) and 200 <= hs < 300:
|
||||
ok_http += 1
|
||||
lines.append(f"| {name} | `{pth}` | {hs} | {ms} | {bc} | {head} |\n")
|
||||
lines.append(
|
||||
f"\n## 汇总\n"
|
||||
f"- 用例数: {len(rows)};HTTP 2xx 数量: {ok_http}。\n"
|
||||
f"- 业务失败(非 0 code)仍可能为**预期**(缺数据/缺 token);本报告仅证明路由可达且返回 Cloudwalk 风格 JSON。\n"
|
||||
)
|
||||
out_path.write_text("".join(lines), encoding="utf-8")
|
||||
|
||||
payload = {
|
||||
"meta": {
|
||||
"generated_at": datetime.now().isoformat(),
|
||||
"base_url": base_url,
|
||||
"label": label,
|
||||
"markdown": str(out_path.resolve()),
|
||||
},
|
||||
"rows": rows,
|
||||
"summary": {"total": len(rows), "http_2xx": ok_http},
|
||||
}
|
||||
out_path.with_suffix(".json").write_text(
|
||||
json.dumps(payload, ensure_ascii=False, indent=2), encoding="utf-8"
|
||||
)
|
||||
+232
@@ -0,0 +1,232 @@
|
||||
#!/usr/bin/env python3
|
||||
"""合并电梯 API 测试套件总览:全量清单(catalog)+ 测试结果矩阵(JSON)+ 可选附录(子报告 MD)。"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import argparse
|
||||
import json
|
||||
from datetime import datetime
|
||||
from pathlib import Path
|
||||
from typing import Any, Dict, List, Optional
|
||||
|
||||
|
||||
def _tool_root() -> Path:
|
||||
return Path(__file__).resolve().parent.parent
|
||||
|
||||
|
||||
def _load_catalog(path: Path) -> List[dict]:
|
||||
data = json.loads(path.read_text(encoding="utf-8"))
|
||||
return list(data.get("endpoints") or [])
|
||||
|
||||
|
||||
def _safe_json_load(p: Optional[Path]) -> Optional[dict]:
|
||||
if not p or not p.is_file():
|
||||
return None
|
||||
try:
|
||||
return json.loads(p.read_text(encoding="utf-8"))
|
||||
except (OSError, json.JSONDecodeError):
|
||||
return None
|
||||
|
||||
|
||||
def _rows_by_id(rows: Any) -> Dict[str, dict]:
|
||||
out: Dict[str, dict] = {}
|
||||
if not isinstance(rows, list):
|
||||
return out
|
||||
for r in rows:
|
||||
if not isinstance(r, dict):
|
||||
continue
|
||||
eid = r.get("id")
|
||||
if not eid:
|
||||
# 对拍行可能在 id 字段,否则用 path 不可靠
|
||||
continue
|
||||
out[str(eid)] = r
|
||||
return out
|
||||
|
||||
|
||||
def _parity_by_id(rows: Any) -> Dict[str, dict]:
|
||||
out: Dict[str, dict] = {}
|
||||
if not isinstance(rows, list):
|
||||
return out
|
||||
for r in rows:
|
||||
if isinstance(r, dict) and r.get("id"):
|
||||
out[str(r["id"])] = r
|
||||
return out
|
||||
|
||||
|
||||
def _resolve_json_arg(md_or_json: Optional[str]) -> Optional[Path]:
|
||||
if not md_or_json:
|
||||
return None
|
||||
p = Path(md_or_json)
|
||||
if p.suffix.lower() == ".json" and p.is_file():
|
||||
return p
|
||||
if p.suffix.lower() == ".md":
|
||||
cand = p.with_suffix(".json")
|
||||
if cand.is_file():
|
||||
return cand
|
||||
return None
|
||||
|
||||
|
||||
def _markdown_table_catalog(endpoints: List[dict]) -> str:
|
||||
lines = [
|
||||
"| ID | 名称 | 方法 | Path | 冒烟 | 横向对拍 |\n",
|
||||
"| ---- | ---- | ---- | ---- | ---- | -------- |\n",
|
||||
]
|
||||
for ep in endpoints:
|
||||
eid = ep.get("id", "")
|
||||
name = (ep.get("name") or "").replace("|", "\\|")
|
||||
method = ep.get("method") or "POST"
|
||||
path = (ep.get("path") or "").replace("|", "\\|")
|
||||
sm = "是" if ep.get("include_in_smoke", True) else "否"
|
||||
py = "是" if ep.get("include_in_parity", True) else "否"
|
||||
lines.append(f"| `{eid}` | {name} | {method} | `{path}` | {sm} | {py} |\n")
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
def _markdown_matrix(
|
||||
endpoints: List[dict],
|
||||
smoke_v1: Optional[dict],
|
||||
smoke_v2: Optional[dict],
|
||||
parity: Optional[dict],
|
||||
) -> str:
|
||||
m1 = _rows_by_id((smoke_v1 or {}).get("rows"))
|
||||
m2 = _rows_by_id((smoke_v2 or {}).get("rows"))
|
||||
mp = _parity_by_id((parity or {}).get("rows"))
|
||||
|
||||
meta_v1 = (smoke_v1 or {}).get("meta") or {}
|
||||
meta_v2 = (smoke_v2 or {}).get("meta") or {}
|
||||
meta_pr = (parity or {}).get("meta") or {}
|
||||
|
||||
head = ""
|
||||
head += (
|
||||
f"- **V1 冒烟数据来源**: {meta_v1.get('markdown') or meta_v1.get('base_url') or '(无 JSON,未执行或已跳过)'}\n"
|
||||
)
|
||||
head += (
|
||||
f"- **V2 冒烟数据来源**: {meta_v2.get('markdown') or meta_v2.get('base_url') or '(无)'}\n"
|
||||
)
|
||||
head += f"- **对拍数据来源**: {meta_pr.get('markdown') or '(无)'}\n\n"
|
||||
|
||||
lines = [
|
||||
head,
|
||||
"### 测试结果矩阵(按 catalog `id` 对齐)\n\n",
|
||||
"| catalog id | V1 HTTP | V1 code | V2 HTTP | V2 code | 对拍一致(仅参与对拍的条目) | 备注 |\n",
|
||||
"| ---------- | ------- | ------- | ------- | ------- | ---------------------------- | ---- |\n",
|
||||
]
|
||||
for ep in endpoints:
|
||||
eid = str(ep.get("id", ""))
|
||||
include_p = ep.get("include_in_parity", True)
|
||||
v1 = m1.get(eid)
|
||||
v2 = m2.get(eid)
|
||||
pr_row = mp.get(eid) if include_p else None
|
||||
|
||||
def cell_smoke(row: Optional[dict]) -> tuple[str, str]:
|
||||
if not row:
|
||||
return "—", "—"
|
||||
return str(row.get("http_status", "—")), str(row.get("business_code") or "—")
|
||||
|
||||
h1, c1 = cell_smoke(v1)
|
||||
h2, c2 = cell_smoke(v2)
|
||||
if not include_p:
|
||||
par_c = "(不参与)"
|
||||
elif not parity:
|
||||
par_c = "—(未执行)"
|
||||
elif pr_row:
|
||||
par_c = "**Y**" if pr_row.get("match") else "**N**"
|
||||
if not pr_row.get("match"):
|
||||
par_c += " " + (pr_row.get("message") or "")[:60].replace("|", "\\|")
|
||||
else:
|
||||
par_c = "—(本次对拍清单无此项)"
|
||||
|
||||
remark = ""
|
||||
if v1 is None and v2 is None:
|
||||
remark = "冒烟未执行或无该 id 结果"
|
||||
|
||||
lines.append(
|
||||
f"| `{eid}` | {h1} | {c1} | {h2} | {c2} | {par_c} | {remark} |\n"
|
||||
)
|
||||
|
||||
lines.append(
|
||||
"\n**说明**:`code` 为 CloudwalkResult 顶层业务码;HTTP 为传输层状态。"
|
||||
"对拍列为 **Y** 表示旧/新 HTTP 状态一致且业务 code 一致(`code_only` 模式)。\n"
|
||||
)
|
||||
return "".join(lines)
|
||||
|
||||
|
||||
def main() -> None:
|
||||
ap = argparse.ArgumentParser(description="Generate SUITE markdown with catalog + matrix")
|
||||
ap.add_argument("--out", required=True, help="Output SUITE-*.md path")
|
||||
ap.add_argument("--catalog", help="api_catalog.json path")
|
||||
ap.add_argument("--smoke-v1", help="smoke-v1_*.md 或同名 .json")
|
||||
ap.add_argument("--smoke-v2", help="smoke-v2_*.md 或同名 .json")
|
||||
ap.add_argument("--parity", help="parity-*.md 或同名 .json")
|
||||
ap.add_argument(
|
||||
"--embed-full",
|
||||
action="store_true",
|
||||
help="附录中嵌入子报告 Markdown 全文(较长)",
|
||||
)
|
||||
args = ap.parse_args()
|
||||
|
||||
root = _tool_root()
|
||||
catalog_path = Path(args.catalog or (root / "api_catalog.json"))
|
||||
endpoints = _load_catalog(catalog_path)
|
||||
|
||||
js_v1 = _resolve_json_arg(args.smoke_v1)
|
||||
js_v2 = _resolve_json_arg(args.smoke_v2)
|
||||
js_pr = _resolve_json_arg(args.parity)
|
||||
|
||||
doc_v1 = _safe_json_load(js_v1)
|
||||
doc_v2 = _safe_json_load(js_v2)
|
||||
doc_pr = _safe_json_load(js_pr)
|
||||
|
||||
out = Path(args.out)
|
||||
out.parent.mkdir(parents=True, exist_ok=True)
|
||||
|
||||
lines: List[str] = [
|
||||
"# 电梯应用 API 测试套件总览\n\n",
|
||||
f"- **生成时间**: {datetime.now().isoformat()}\n",
|
||||
f"- **清单来源**: `{catalog_path.resolve()}`(共 **{len(endpoints)}** 条接口定义)\n\n",
|
||||
"## 1. 说明\n\n",
|
||||
"- **全量清单**:第二节,来自 `api_catalog.json`,含是否参与冒烟/对拍。\n",
|
||||
"- **测试结果矩阵**:第三节,与本次运行生成的 **JSON** 侧车文件对齐(与 `.md` 同名的 `.json`)。"
|
||||
"若应用未启动导致 pytest **skip**,则无 JSON,矩阵中表现为 **—**。\n",
|
||||
"- **横向对拍**:仅 `include_in_parity=true` 的条目会写入对拍 JSON 并参与对比。\n\n",
|
||||
"## 2. 全量接口测试清单(catalog)\n\n",
|
||||
_markdown_table_catalog(endpoints),
|
||||
"\n",
|
||||
"## 3. 测试结果矩阵\n\n",
|
||||
_markdown_matrix(endpoints, doc_v1, doc_v2, doc_pr),
|
||||
]
|
||||
|
||||
sec = 4
|
||||
if args.embed_full:
|
||||
for title, path_s in (
|
||||
("V1 单机冒烟 Markdown", args.smoke_v1),
|
||||
("V2 单机冒烟 Markdown", args.smoke_v2),
|
||||
("横向对拍 Markdown", args.parity),
|
||||
):
|
||||
if not path_s:
|
||||
continue
|
||||
pp = Path(path_s)
|
||||
if pp.suffix.lower() != ".md":
|
||||
pp = pp.with_suffix(".md") if pp.with_suffix(".md").is_file() else pp
|
||||
if not pp.is_file():
|
||||
continue
|
||||
lines.append(f"## {sec}. {title}\n\n")
|
||||
lines.append(f"源文件: `{pp.resolve()}`\n\n---\n\n")
|
||||
lines.append(pp.read_text(encoding="utf-8"))
|
||||
lines.append("\n\n")
|
||||
sec += 1
|
||||
|
||||
lines.append(
|
||||
f"\n## {sec}. 原始报告路径(便于回放)\n\n"
|
||||
f"- V1 冒烟: `{args.smoke_v1 or '(未生成)'}`\n"
|
||||
f"- V2 冒烟: `{args.smoke_v2 or '(未生成)'}`\n"
|
||||
f"- 对拍: `{args.parity or '(未生成)'}`\n"
|
||||
f"- 同名 **`.json`** 与 `.md` 一并生成时可自动填充第三节矩阵。\n"
|
||||
)
|
||||
|
||||
out.write_text("".join(lines), encoding="utf-8")
|
||||
print(out.resolve())
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
+6
-13
@@ -1,15 +1,11 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import json
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
from parity.catalog_loader import endpoint_body as cb_body
|
||||
from parity.catalog_loader import include_in_parity as cb_parity
|
||||
from parity.catalog_loader import load as load_catalog
|
||||
from parity.client import call_both
|
||||
|
||||
_DIR = Path(__file__).resolve().parent.parent
|
||||
_FIX = _DIR / "fixtures"
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("two_instances_ready")
|
||||
@pytest.mark.live
|
||||
@@ -19,16 +15,13 @@ def test_parity_from_catalog(
|
||||
base_new,
|
||||
session_http,
|
||||
):
|
||||
"""按 api_catalog 双端对拍。compare_mode: deep | code_only | status_only"""
|
||||
"""按 api_catalog 双端对拍(仅 ``include_in_parity`` 为 true 的条目)。compare_mode: deep | code_only | status_only"""
|
||||
cat = load_catalog()["endpoints"] # type: ignore[operator]
|
||||
for ep in cat:
|
||||
if not cb_parity(ep):
|
||||
continue
|
||||
name = ep["id"]
|
||||
fixture = ep.get("fixture")
|
||||
body: dict
|
||||
if fixture:
|
||||
body = json.loads((_FIX / fixture).read_text(encoding="utf-8"))
|
||||
else:
|
||||
body = {}
|
||||
body = cb_body(ep)
|
||||
pr = call_both(
|
||||
name=ep.get("name", name),
|
||||
method=ep.get("method", "POST"),
|
||||
|
||||
@@ -0,0 +1,31 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import pytest
|
||||
from parity.catalog_loader import endpoint_body as cb_body
|
||||
from parity.catalog_loader import include_in_smoke as cb_smoke
|
||||
from parity.catalog_loader import load as load_catalog
|
||||
from parity.client import call_single
|
||||
|
||||
"""单机按 catalog 逐接口 POST/GET,写入 _smoke_rows(sessionfinish 落盘)。"""
|
||||
|
||||
|
||||
@pytest.mark.usefixtures("smoke_instance_ready")
|
||||
@pytest.mark.smoke
|
||||
def test_smoke_from_catalog(request, smoke_base, smoke_label, session_http):
|
||||
cat = load_catalog()["endpoints"]
|
||||
for ep in cat:
|
||||
if not cb_smoke(ep):
|
||||
continue
|
||||
name = ep["id"]
|
||||
body = cb_body(ep)
|
||||
row = call_single(
|
||||
name=ep.get("name", name),
|
||||
method=ep.get("method", "POST"),
|
||||
path=ep["path"],
|
||||
body=body,
|
||||
base_url=smoke_base,
|
||||
session=session_http,
|
||||
)
|
||||
row["id"] = name
|
||||
row["label"] = smoke_label
|
||||
request.config._smoke_rows.append(row) # type: ignore
|
||||
@@ -30,6 +30,19 @@
|
||||
<groupId>cn.cloudwalk</groupId>
|
||||
<artifactId>cloudwalk-device-sdk-protocol-entity</artifactId>
|
||||
</dependency>
|
||||
<!-- 私服上 cloudwalk-common-result POM 常缺 dependencyManagement,传递依赖不生效;编译 interface 需下列包 -->
|
||||
<dependency>
|
||||
<groupId>org.apache.commons</groupId>
|
||||
<artifactId>commons-lang3</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>javax.validation</groupId>
|
||||
<artifactId>validation-api</artifactId>
|
||||
</dependency>
|
||||
<dependency>
|
||||
<groupId>com.fasterxml.jackson.core</groupId>
|
||||
<artifactId>jackson-annotations</artifactId>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
|
||||
<build>
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
<version>3.0.0-xinghewan</version>
|
||||
<packaging>pom</packaging>
|
||||
<name>intelligent-cwoscomponent (Maven reactor)</name>
|
||||
<description>聚合模块:interface → rest。原父 intelligent-cwoscomponent 缺失。</description>
|
||||
<description>聚合模块:interface → rest(反应堆版本 3.0.0-xinghewan)。注:maven-cw-elevator-application 上线口径固定依赖 cw_lib 的 2.9.2-xinghewan,不以此反应堆产物替代;见电梯 scripts/build_nexus_only.sh。</description>
|
||||
|
||||
<modules>
|
||||
<module>intelligent-cwoscomponent-parent</module>
|
||||
@@ -26,7 +26,8 @@
|
||||
<properties>
|
||||
<java.version>1.8</java.version>
|
||||
<spring-cloud.version>Greenwich.SR6</spring-cloud.version>
|
||||
<cloudwalk.internal.version>4.0.0-Brussels-SRX</cloudwalk.internal.version>
|
||||
<!-- 与 maven-cw-elevator-application / V1 cw_lib 对齐;4.0.0 常不在私服,会导致 intelligent-cwoscomponent-rest 无法解析 -->
|
||||
<cloudwalk.internal.version>3.7.2-Brussels-SRX</cloudwalk.internal.version>
|
||||
<cloudwalk.legacy.public.version>3.7.2-Brussels-SRX</cloudwalk.legacy.public.version>
|
||||
<fastjson.version>1.2.83</fastjson.version>
|
||||
<!-- 与 V1 运行包 lib 内 cloudwalk-device-sdk-protocol-entity-2.2.0.jar 一致 -->
|
||||
|
||||
Executable
+4
@@ -0,0 +1,4 @@
|
||||
#!/usr/bin/env bash
|
||||
# 转发至 maven-cw-elevator-application 内的一键 API 套件脚本。
|
||||
ROOT="$(cd "$(dirname "$0")/.." && pwd)"
|
||||
exec bash "${ROOT}/maven-cw-elevator-application/scripts/run_full_elevator_api_suite.sh" "$@"
|
||||
Reference in New Issue
Block a user