跳到主要内容

plantpulse-server(Web控制台 + IIoT引擎)

角色​

这是平台的门面与大脑——核心模块。操作员看到的Web控制台、外部系统调用的REST API,以及实际处理工业数据的IIoT引擎,全部在这个模块内运行。

项目值
模块名plantpulse-server
容器plantpulse-server-web(自身镜像,自身容器)
容器内路径/opt/kopens/plantpulse-platform/plantpulse-server/
用户接入80/443 — 由 plantpulse-proxy 接收并转发到该容器。管理控制台也使用同一地址
主机端口无 — 仅在代理后运行
内存限制DOCKER_SERVER_MEMORY 默认 14g / 堆 DOCKER_SERVER_HEAP 默认 8g
运行时Apache Tomcat 10.1 + Java 21
吞吐量每秒40,000条消息(36线程管道)
此容器不开放主机端口

用户流量始终经由 plantpulse-proxy(80/443)。因此即使后端迁移或改名,也无需修改用户和设备配置。容器状态通过 bin/status.sh 查看,日志通过 bin/logs.sh plantpulse-server-web 查看。

职责范围​

目录结构​

plantpulse-server/
├── config/ # 서버 관리 설정 (외부화 — 배포해도 유지)
│ ├── plantpulse-engine.properties # IIoT 엔진 튜닝
│ ├── plantpulse-storage.properties # DB 연결
│ ├── plantpulse-mq.properties # 메시징 연결
│ └── log4j2.xml # 로깅 설정 (자가 시딩 — 최초 기동 시 생성)
├── bin/
│ ├── start.sh
│ ├── stop.sh
│ └── log-viewer.sh
├── logs/
│ └── system.log
├── path/ # 경로 매핑
└── server/ # 내장 Tomcat
├── conf/
│ ├── server.xml # 커넥터 / 포트
│ ├── context.xml # 컨텍스트 / 데이터소스
│ ├── catalina.properties
│ ├── logging.properties
│ ├── tomcat-users.xml
│ ├── web.xml
│ └── Catalina/localhost/
│ └── ROOT.xml # 컨텍스트 설정 (sessionCookieName 등)
└── webapps/
├── ROOT.war # 웹 애플리케이션 (WAR 통배포)
└── ROOT/ # Tomcat 이 unpack 한 디렉토리 (수정 금지)

部署方式:Web应用采用 server/webapps/ROOT.war 替换方式部署(WAR 整体部署,正在转型中)。现有的 app/plantpulse-server-web exploded 目录方式计划废弃,并且 server.xml 的 Context docBase 覆盖已移除。直接修改 ROOT/ 内部的文件会在下次部署时丢失,因此配置变更请始终在 config/ 中进行。

主要配置文件​

配置文件位于模块的 config/ 目录中,服务器按 -Dpp.conf.dir → ${catalina.base}/../config → 类路径(fallback)顺序搜索。当外部配置被加载时,启动日志会输出 Properties loaded from external conf:。

application.properties 已移除(2026.06)。主题/主页等控制台运行配置在控制台的系统 > 设置管理(mm_config 表)中管理,alarm.duplicate.check.minutes 已迁移至 plantpulse-engine.properties。

plantpulse-engine.properties​

IIoT引擎管道调优。

项目默认值说明
engine.pipeline.threads36并行处理的线程数
engine.pipeline.ratelimit40000每秒最大消息数
engine.pipeline.batch.size1000批处理大小
engine.pipeline.queue.size1200000队列容量
engine.dedup.enabledtrue启用去重

plantpulse-storage.properties​

数据存储连接。

# Cassandra
cassandra.host=${PP_CASSANDRA_HOST}
cassandra.port=${PP_CASSANDRA_PORT}
cassandra.keyspace=${PP_KEYSPACE}
cassandra.user=${PP_CASSANDRA_USER}
cassandra.password=${PP_CASSANDRA_PASSWORD}

# PostgreSQL (Spring DataSource)
postgres.url=jdbc:postgresql://${PP_POSTGRES_HOST}:${PP_POSTGRES_PORT}/${PP_DB_NAME}
postgres.user=${PP_PG_USER}
postgres.password=${PP_PG_PASSWORD}

# Valkey
redis.host=${PP_REDIS_HOST}
redis.port=${PP_REDIS_PORT}
redis.password=${PP_REDIS_PASSWORD}

此应用不渲染配置。 拆分至容器后,Web服务器无需数据湖的配置渲染器即可启动,并从三处读取值 —— ① 操作员放置的外部文件 → ② WAR 内置的默认值 → ③ compose 传递的环境变量。其中环境变量会覆盖内置默认值。

因此,应用所看到值的唯一定义处是 compose/docker-compose.yml。仅存在于数据湖、但不在 compose 中的名称不会到达此应用——曾在仅修改数据湖标识符的站点中,五个应用继续使用旧名称的事故就是一个例子(2026-08-29)。

请勿修改 Web应用内部(server/webapps/ROOT/)的文件——部署时会丢失。

server/conf/server.xml​

Tomcat 连接器配置。端口更改/TLS/压缩/线程池。

<Connector port="80" protocol="HTTP/1.1"
connectionTimeout="20000"
maxThreads="500"
acceptCount="200"
URIEncoding="UTF-8"
compression="on"
compressibleMimeType="text/html,text/css,application/json,application/javascript" />

<Connector port="443" protocol="org.apache.coyote.http11.Http11Nio2Protocol"
SSLEnabled="true"
maxThreads="500"
sslEnabledProtocols="TLSv1.3,TLSv1.2">
<SSLHostConfig>
<Certificate certificateKeystoreFile="/var/security/plantpulse/server.jks"
certificateKeystorePassword="${PP_TLS_KEYSTORE_PASSWORD}" />
</SSLHostConfig>
</Connector>

运维命令​

在主机上——以容器为单位​

重新启动容器即为此模块的重启。基础设施和其他应用不受影响。

cd /opt/kopens/plantpulse-platform-docker

# 서버만 재시작
docker compose -f compose/docker-compose.yml restart plantpulse-server-web

# 상태 · 로그
bin/status.sh
bin/logs.sh plantpulse-server-web -n 200

进入容器内部​

cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
无法通过数据湖的 restart-server.sh 重启

该脚本是一个薄委托,用于在每个应用的自身容器内调用该模块的启动器。数据湖容器中没有 Web 服务器可执行文件,因此会以«模块不存在»错误结束。请使用上述容器级别的重启。

热加载(无需重启即可应用配置)​

部分配置可在控制台的系统 > 设置管理菜单中进行热加载。config/ 的属性文件或 server.xml 等启动时配置需要重启。

日志​

日志路径内容
主日志logs/system.log应用逻辑、错误
Tomcat catalinaserver/logs/catalina.outTomcat 标准输出
访问日志server/logs/localhost_access_log.YYYY-MM-DD.txtHTTP 请求日志
引擎日志logs/engine.logIIoT 引擎处理详情
慢查询logs/slow.log超过200ms的查询

日志级别变更——日志配置(log4j2.xml)已外部化至模块的 config/(自引导:首次启动时,WAR 默认值被复制到 config/log4j2.xml,应用时启动日志输出 Logging reconfigured from external conf:):

# 웹 서버 컨테이너 안에서
cd /opt/kopens/plantpulse-platform-docker/bin
./shell.sh plantpulse-server-web
vi /opt/kopens/plantpulse-platform/plantpulse-server/config/log4j2.xml
# <Logger name="plantpulse" level="DEBUG"/> 등 수정 후 exit

# 호스트에서 그 컨테이너만 재시작
cd /opt/kopens/plantpulse-platform-docker
docker compose -f compose/docker-compose.yml restart plantpulse-server-web

由于是外部文件,WAR 重新部署后也会持久保存。请勿修改 Web应用内部(server/webapps/ROOT/WEB-INF/classes/log4j2.xml)——部署时会丢失。

性能调优​

JVM 堆​

# server/bin/setenv.sh (없으면 생성)
export CATALINA_OPTS="-Xms16g -Xmx32g \
-XX:+UseG1GC -XX:MaxGCPauseMillis=200 \
-XX:+HeapDumpOnOutOfMemoryError \
-XX:HeapDumpPath=/var/log/plantpulse/heap/ \
-Xlog:gc*:file=/var/log/plantpulse/gc.log:time,uptime:filecount=10,filesize=100M"

Tomcat 线程池​

在 server.xml 的 Connector 中调整 maxThreads / acceptCount。在大规模并发环境下,建议如下。

并发连接数maxThreadsacceptCount
~ 100200100
~ 500500200
500+1000500

IIoT 引擎吞吐量​

将 plantpulse-engine.properties 的 engine.pipeline.threads 设置为主机核心数的 70~80%。当消息积压时,同时调高 engine.pipeline.queue.capacity。

更详细的调优请参考性能调优页面。

上传内容存放在哪里 —— RustFS​

设备文档·设备图像·个人资料图像存储在**对象存储(RustFS)**中。数据库中仅存储 s3://<버킷>/<key> 而非本体。

项目值
桶PP_S3_UPLOAD_BUCKET(默认 upload)— 与数据湖使用的 plantpulse 桶不同
连接PP_S3_HOST · PP_S3_PORT · PP_S3_USER · PP_S3_PASSWORD
存放位置仅在 plantpulse-server-web。不会进入工作器

分开桶的原因是为了防止一方的保留规则影响另一方。

桶必须预先存在

2026.09 不会在启动时创建桶。 指向不存在的桶会导致上传失败。全新安装会在迁移过程中创建桶,但如果手动修改值指向其他桶,请先创建该桶。

环境必须配置好才能启动

如果连接值为空,则无法创建对象存储,上传路径也不会显示。容器环境在创建时固定,因此如果添加了值,需要重新创建容器而不是 docker restart 才能生效。

上传限制——无法用一行概述​

限制有两层,实际最先触发的基本总是下层(端点)。

端点保护——先触发​

上传内容端点限制
设备文档POST /asset/tree/document/upload50 MiB
设备文档 (API v5)POST /api/v5/assets/{asset_id}/documents50 MiB
设备图像POST /asset/image/upload10 MiB
画布 SVGPOST /canvas/svgupload10 MiB
仅告知「上传限制 50 MiB」在图像上会出错

设备图像和画布 SVG 会在 10 MiB 处被拒绝。只有文档是 50 MiB。

超出限制时显示的内容​

四个端点使用相同的验证函数,消息仅显示字节数字。

file_size: 12582912 > max 10485760

以下是上传 12 MiB 图像时的示例——10485760 为 10 MiB,52428800 为 50 MiB。

无单位也无文件名

消息中没有单位,操作员必须心算字节,也不显示被阻止的文件是哪个。如果一次上传多个文件被拒绝,需要直接测量大小来查找。

容器限制——其上层的天花板​

项目值位置
单个文件50 MiBweb.xml 的 <max-file-size>
整个 multipart 请求52 MiBweb.xml 的 <max-request-size>
表单参数(文件部分除外)2 MiBTomcat 默认值——两个连接器均未声明 maxPostSize
2 MiB 不是「文件」限制——大文件也能通过代理上传

maxPostSize 是 Tomcat 转换为请求参数的字节的限制。x-www-form-urlencoded 计算整个正文,multipart/form-data 仅统计非文件部分。JSON 等原始正文不受影响。

实测(2026-09-18):通过代理发送 3 MiB multipart 时,正文全部传输,到达应用认证层(因不是大小而是 401 而结束)。传输路径上没有 2 MiB 墙,nginx 是 client_max_body_size 200m。

在 2026-09-18 之前,只有 443 连接器有 maxPostSize="-1",因此同一服务器根据端口强制不同限制。现在 80 · 443 相同。

健康检查​

# 표준 헬스체크 (익명, readiness — 엔진이 전 단계 기동을 완료(RUNNING)했을 때만 UP. 배포 자동검증 / 모니터링용)
curl -fsS http://127.0.0.1/api/health
# 준비 완료: 200 {"status":"UP","service":"plantpulse-server-web","ts":1765500000000,"checks":{"engine":"RUNNING"}}
# 기동 중: 503 {"status":"STARTING",...} / 기동 실패·중지: 503 {"status":"DEGRADED",...}
# 경로는 /api/health 다 — «/api/v5/health» 는 없다. 인증도 필요 없다(익명 허용 경로).

# 콘솔 ping
curl -fsS http://127.0.0.1/api/v5/ping

# 공개 상태 페이지의 데이터 (익명 — 24시간 가용성·p99·엔드포인트별 상태)
curl -fsS http://127.0.0.1/api/v5/public/status

# WebSocket 핸드셰이크 확인
curl -i -N \
-H "Connection: Upgrade" -H "Upgrade: websocket" \
-H "Sec-WebSocket-Key: x3JJHMbDL1EzLkh9GBhXDw==" \
-H "Sec-WebSocket-Version: 13" \
http://127.0.0.1:8000/ws

常见问题​

症状原因措施
OutOfMemoryError堆不足在 setenv.sh 中调高 -Xmx + 分析 GC 日志
Too many open files文件描述符限制将 /etc/security/limits.conf 的 nofile 设置为 65535+
控制台响应慢数据库慢查询检查 PostgreSQL 慢查询日志、Valkey 缓存命中率
登录无限循环会话/Cookie 问题清除浏览器缓存,检查 server/conf/Catalina/localhost/ROOT.xml 的 sessionCookieName
502 / 504(反向代理)后端响应超时调高 nginx proxy_read_timeout
WebSocket 断开防火墙空闲超时防火墙 timeout > 60s,调整 tomcat.websocket.session.timeout

详细诊断请参考问题排查页面。

安全清单​

  • 更改默认 admin 密码(admin / admin123!)
  • 移除 tomcat-users.xml 的 manager 账户或设置强密码
  • 阻止外部访问 server.xml 的 shutdown port (7000)
  • 应用 HTTPS 证书(plantpulse-certs 烘焙的 pp-security 卷中的材料或外部 CA)
  • 确认设置了 X-Frame-Options / CSP / HSTS 头
  • 将 API Bearer Token 存储在运维 vault 中
  • 禁用 config/ 属性文件中的调试选项并限制权限(chmod 600)

更详细的设置请参考安全设置页面。

相关文档​