47.94.88.244(qcsj88888.com / api.qcsj88888.com / doc.qcsj88888.com 三条 A 记录均指向此)iZ2zed18i1o6sivyqop2plZ172.20.205.37(原手册所记,本次未复核)qcsj88888.com、api.qcsj88888.com、doc.qcsj88888.com这台机器在别处没有任何记录。排查时容易和 antom(
8.138.203.55)、国内 credit-pay (47.107.96.60)、美西(132.148.75.28)三台混淆——它是独立的第四台。
| 容器 | 镜像(2026-09-24 实测) | 端口映射 | 网络(未复核) |
|---|---|---|---|
| java-backend | pay:latest ⚠️ |
0.0.0.0:8001→8001 | app-tier, deploy_app-tier |
| frontend | xjz/frontend:2.5.4 |
0.0.0.0:5173→80 | app-tier |
| payment-doc | xjz/payment-doc-server:1.0.0 |
0.0.0.0:7878→7878 | — |
| postgres | postgres:18.3 |
0.0.0.0:5432→5432 ⚠️ | 宿主机, deploy_app-tier |
| redis | redis:8.6.2-alpine |
0.0.0.0:6379→6379 ⚠️ | deploy_app-tier |
| nginx-proxy-manager | jc21/nginx-proxy-manager:latest |
0.0.0.0:80-81, 443 | deploy_app-tier |
| gogs | gogs/gogs:latest |
0.0.0.0:3000, 2222 | deploy_app-tier |
⚠️ 两处与旧版手册不符,上服务器前先核实:
- java-backend 实际跑的是
pay:latest,不是手册和仓库 compose 里写的xjz/java-backend:1.0.0。照「升级部署」一节构建xjz/java-backend:1.0.0再docker compose up -d java-backend,很可能不会替换掉正在运行的容器。服务器上的 compose 与仓库里的docker-compose.yml已经不同步,部署前先比对。- postgres / redis 绑的是
0.0.0.0,不是127.0.0.1。 目前靠阿里云安全组挡着 (2026-09-24 从公网实测 5432 / 6379 均不可达),但容器层面是无保护的, 安全组一旦放宽就直接暴露。建议改成127.0.0.1:5432:5432这类回环绑定。
qcsj88888.com (HTTPS)
↓ NPM
├── /api/v1/* → java-backend:8001
└── 其余 → frontend:5173
/root/payments/code/payment-platform/root/payments/code/payment-platform/docker-compose.yml/root/payments/deploy/nginx-proxy-manager/datacd /root/payments/code/payment-platform
docker compose ps
docker stats --no-stream
docker compose logs java-backend --tail 50 # 最近50行
docker compose logs java-backend -f # 实时跟踪
docker compose logs java-backend --since 5m # 最近5分钟
docker compose restart java-backend
docker compose stop java-backend
docker compose start java-backend
curl http://localhost:8001/api/v1/payment/notify/health
# 预期返回: {"code":0,"data":{"status":"ok"}}
# === 本地 ===
cd D:\project2\payment-platform\java
mvn clean package -DskipTests
scp target/payment-platform-1.0.0.jar root@172.20.205.37:/root/payments/code/payment-platform/java/target/
# === 服务器 ===
cd /root/payments/code/payment-platform/java
docker build -t xjz/java-backend:1.0.0 .
cd ..
docker compose up -d java-backend
docker compose logs java-backend --tail 20 -f
新增 SQL 脚本放在 java/sql/,服务器执行:
docker exec -i postgres psql -U admin -d payment_platform_prod < /root/payments/code/payment-platform/java/sql/xxx.sql
docker stop java-backend
docker rm java-backend
cd /root/payments/code/payment-platform
git checkout master
docker compose up -d backend
JAVA_OPTS: "-Xms128m -Xmx256m -XX:+UseSerialGC -XX:MaxRAMPercentage=50 -XX:TieredStopAtLevel=1"
-Xms128m: 初始堆 128MB-Xmx256m: 最大堆 256MB-XX:+UseSerialGC: 单线程 GC(2C 服务器避免并发 GC 开销)-XX:MaxRAMPercentage=50: JVM 最多用 50% 物理内存-XX:TieredStopAtLevel=1: 减少 JIT 编译层级,启动更快SPRING_DATASOURCE_DRUID_URL: jdbc:postgresql://postgres:5432/payment_platform_prod?currentSchema=public
SPRING_DATASOURCE_DRUID_USERNAME: admin
SPRING_DATASOURCE_DRUID_PASSWORD: 'xjz#123321'
SPRING_DATA_REDIS_HOST: redis
SPRING_DATA_REDIS_PORT: 6379
SPRING_DATA_REDIS_DATABASE: 0
SPRING_DATA_REDIS_PASSWORD: '123456#xjz'
从公网直连 47.94.88.244 的探测结果:
| 端口 | 服务 | 公网 | 说明 |
|---|---|---|---|
| 80 / 443 | NPM | ✅ 开放 | 对外服务,正常 |
| 81 | NPM 管理面板 | ❌ 不可达 | 安全组已挡,保持 |
| 3000 | gogs | ⚠️ 开放 | 见下 |
| 2222 | gogs SSH | ❌ 不可达 | |
| 5432 | postgres | ❌ 不可达 | 靠安全组挡,容器绑的是 0.0.0.0 |
| 6379 | redis | ❌ 不可达 | 同上 |
| 8001 | java-backend | ❌ 不可达 | 经 NPM 转发 |
| 7878 | payment-doc | ❌ 不可达 | 经 NPM 转发 |
gogs 的 3000 是本机除 80/443 外唯一对公网开放的服务。 若是有意为之(外部拉取代码) 则无需处理;若是无意的,建议安全组关掉,改由 NPM 反代到域名下。
本节 2026-09-24 新增。当天三个域名 HTTPS 中断三天,根因与处置见下。
TLS 由 nginx-proxy-manager(NPM,容器名 nginx-proxy-manager)终结。宿主机上
只有它占 80/443。
| 域名 | 容器内代理配置 | 证书目录 | 证书名 |
|---|---|---|---|
qcsj88888.com |
/data/nginx/proxy_host/2.conf |
/etc/letsencrypt/live/npm-2/ |
npm-2 |
api.qcsj88888.com |
/data/nginx/proxy_host/3.conf |
/etc/letsencrypt/live/npm-3/ |
npm-3 |
doc.qcsj88888.com |
/data/nginx/proxy_host/4.conf |
/etc/letsencrypt/live/npm-4/ |
npm-4 |
三个域名各自一张独立证书(NPM 一个 Proxy Host 一张,SAN 不合并)。
live/ 下是符号链接,指向 archive/npm-<id>/ 里的实际文件。
宿主机数据目录:/root/payments/deploy/nginx-proxy-manager/data/
(证书不在这里面,在容器内 /etc/letsencrypt)。
证书走 DNS challenge + dns-01,认证器是 certbot 的阿里云插件:
Requested authenticator dns-aliyun
Var pref_challs=['dns-01']
Var dns_aliyun_credentials=/etc/letsencrypt/credentials/credentials-*
这是 NPM 自带的功能,不要误判成定制改造。 可选 provider 列在
/app/certbot/dns-plugins.json(上游路径 backend/certbot/dns-plugins.json),
provider 的显示名从该文件读取,所以日志里会出现 via Aliyun 这种拼接文案——
这是正常的,不是补丁痕迹。
镜像来源确认(2026-09-24):
Created: 2026-02-17T05:43:40Z
RepoDigests: jc21/nginx-proxy-manager@sha256:2aa69b382a384b676c0d4f1d6f2eac40ecd478fcf7af1cfb3f9f1d3cd0c81e12
所以这台能直连 Let's Encrypt。 不要把 antom 那台"必须用 ZeroSSL(国际出口不通)" 的结论套到这台上——两台出网情况不同。
NPM 每小时 :27:42 跑一次检查,把所有已进入「到期前 30 天」窗口的证书串行续掉。
三张分散在几个整点完成,是因为各自 expires_on 差了几小时、跨过窗口的时刻不同,
不是「每次只续一张」。
⚠️ 续期成功后 NPM 不会 reload nginx,根因见下。
docker logs nginx-proxy-manager 2>&1 | grep -i 'SSL'
# 服务器本机(127.0.0.1 + SNI,绕开 DNS 缓存)
echo | openssl s_client -connect 127.0.0.1:443 -servername qcsj88888.com 2>/dev/null | openssl x509 -noout -dates
# 外网视角(在任一有国际出口的机器上跑)
curl -sS -o /dev/null -w 'verify=%{ssl_verify_result}\n' https://qcsj88888.com/
verify=0 表示客户端校验通过。
为什么必须这样验: 2026-09-24 那次,磁盘上证书完全正常(certbot certificates
报 VALID: 59 days),但 nginx 一直发 6/25 签发的旧证书。只扫证书文件的到期检查
会报"一切正常",而生产已经挂了。
症状:磁盘证书是新的,公网发出的却是旧的。此时不要重签(浪费 CA 配额且治不好)。
docker exec nginx-proxy-manager nginx -t
必须先输出 syntax is ok + test is successful 才能继续。证书与私钥不匹配时
nginx -t 会拦下——绝不让 nginx 加载坏配置(9-13 antom 那次就是栽在
"新证书配旧私钥"上,reload 后 nginx 起不来、域名全挂)。
docker exec nginx-proxy-manager nginx -s reload
NPM 只在「签发新证书」时 reload nginx,定时续期这条路线上根本没有这一步。
全 /app 范围内 Nginx.reload() 的调用点(grep -rn "Nginx.reload()" /app/):
proxy-host.js:296,397 redirection-host.js:294,395
access-list.js:201,305 stream.js:243,344
dead-host.js:242,324 ip_ranges.js:113
setting.js:51,63 certificate.js:155,158,164,171,177,184
nginx.js:94(定义处) migrations/2021...regenerate_default_host.js:22
全是 CRUD 流程。certificate.js 那 6 处在 insertAndFetch(data) 之后,属于
POST /certificates(签发),服务于「临时禁用 host → 签发 → 恢复 host」。
续期链路一次都没有:
processExpiringHosts (:49) 定时任务,查 expires_on 进入 30 天窗口的证书
→ renew (:893) 串行 forEach 调用
→ renewLetsEncryptSsl (:929) http-01
→ renewLetsEncryptSslWithDnsChallenge (:970) dns-01
→ utils.execFile(certbotCommand, args, adds.opts)
→ logger.info(result) → return result
两个 renew 函数体里只有 execFile 和 logger.info,不 import nginx 模块,也没有 reload。
代码与现象互相印证: 8/25 续期日志显示 certbot 成功、数据库写入,全程无 reload 记录、 无报错 —— 不是「reload 步骤丢了」,是这条路径上本来就没有。
为什么只有这台暴露: reload 只在改配置时发生(动 NPM 界面会顺带把新证书带进来)。
这台三个月没人碰过 NPM 配置,database.sqlite 上一次写入就是 8/25 的续期本身,
所以 nginx 从 6/25 签发后再没重载过。
未核实上游是否已在更新版本中修掉。可确认的事实只有:这个 digest (
sha256:2aa69b38…)的镜像,续期路径不 reload。
NPM 在到期前 30 天续期,即约 10/23 再次静默续期。届时若不干预,nginx 仍发 8/24 那张, 11/22 三个域名再次过期。
建议处置(尚未实施):宿主机加定时自愈 —— 比对「实际发出的证书」与「磁盘当前证书」,
不一致就 nginx -t && nginx -s reload,并推钉钉告警。
不要改镜像。 /app/internal/certificate.js 是控制面代码,改错会让
nginx-proxy-manager 起不来,连带 80/443 全挂 —— 而 80/443 正是用来签发和续期的入口。
docker compose logs java-backend --tail 100
常见原因:
Connection refused: postgres:5432 → postgres 没加入 deploy_app-tier,执行 docker network connect deploy_app-tier postgresOutOfMemoryError → 调大 -Xmx 到 384mlsof -i:8001,停掉占用进程检查后端是否正常:
curl http://localhost:8001/api/v1/payment/notify/health
后端正常则查 NPM 是否在线:docker ps | grep nginx-proxy-manager
docker system prune -a --filter "until=72h" # 清理72小时前的旧镜像
# 数据库
docker exec postgres pg_dump -U admin payment_platform_prod > /root/backup/payment_$(date +%Y%m%d).sql