这篇记录一次 Orion Key 发卡系统的生产部署排障。环境是 Docker Compose 跑 apiwebbepusdt,前面用 OpenResty/1Panel 做反向代理。问题集中在三个地方:前端连不上后端、默认管理员账号认知偏差、支付宝支付渠道重复创建。

下文用 <your-domain> 代表你的实际域名,用 <db-host><db-password> 等占位符代替敏感信息。不要把真实数据库密码、支付 Token、后台密码写进公开博客。

部署结构

核心服务大概是这样:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
services:
api:
ports:
- "127.0.0.1:8083:8083"

web:
ports:
- "127.0.0.1:3000:3000"
environment:
- BACKEND_URL=http://api:8083

bepusdt:
ports:
- "127.0.0.1:8881:8080"

这里有两个关键点:

  • 127.0.0.1:8083127.0.0.1:3000 只允许服务器本机访问,公网访问必须经过 OpenResty。
  • 容器之间不能用 127.0.0.1 互相访问,web 访问 api 应该用 Compose 服务名:http://api:8083

问题一:前端提示 ECONNREFUSED,无法连后端

最开始浏览器报错类似:

1
2
Failed to proxy http://localhost:8083/api/auth/login
AggregateError: ECONNREFUSED

后端其实是活着的:

1
curl -i http://127.0.0.1:8083/api/auth/captcha

能返回验证码 JSON,说明 API 没挂。

还有一个容易误判的检查:

1
curl -i http://127.0.0.1:8083/api/health

如果返回 401 未登录或会话已过期,不代表服务挂了,只是这个接口被鉴权拦住了。

原因

前端容器里 /api/* 代理在构建时被写死到了 localhost:8083。在容器或 Next.js standalone 场景里,localhost 指的是前端容器自己,不是后端 API 容器,所以连接被拒绝。

另外还踩了两个小坑:

1
curl -i https://127.0.0.1:3000/

会报:

1
OpenSSL wrong version number

因为 3000 端口是 HTTP,不是 HTTPS。

还有:

1
docker compose exec web ...

如果不在 compose 文件目录执行,会提示:

1
no configuration file provided: not found

先用下面命令找到项目目录:

1
docker compose ls

然后进入对应目录,比如:

1
cd /opt/orion-key

解决方法

前端代理改成运行时读取 BACKEND_URL,不要在构建时写死 localhost:8083

生产环境中 web 容器应设置:

1
2
environment:
- BACKEND_URL=http://api:8083

如果使用已经修过 runtime proxy 的镜像,可以在 .env 里指定:

1
WEB_IMAGE=wenmoux/orion-key-web:runtime-proxy

然后重启:

1
2
3
cd /opt/orion-key
docker compose pull web
docker compose up -d web

验证 OpenResty 反代:

1
2
curl -I -H 'Host: <your-domain>' http://127.0.0.1/
curl -i -H 'Host: <your-domain>' http://127.0.0.1/api/auth/captcha

两个都能返回 200,说明本机反代链路基本没问题。

如果服务器上能通,自己电脑访问超时,并且 nslookup 解析到 198.18.x.x,通常是代理软件的 fake-ip DNS。给域名加 DIRECT 规则,或者临时写 hosts 到真实服务器 IP。

问题二:默认管理员账号和密码不对

README 里容易让人误以为 Docker 部署后会自动有默认 admin 账号,但实际数据库里可能没有。

代码里虽然有 data.sql,但应用配置主要是 ddl-auto: update,没有自动强制执行初始化 SQL。也就是说 Docker 首次启动不一定会插入默认管理员。

另一个坑是文档、注释和 SQL 里的默认密码不完全一致。有的地方写 admin / 123456,有的注释写 admin123,SQL 里又出现过不同值。实际部署时不要赌默认账号,最好自己创建并提升权限。

解决方法

先注册一个普通用户,然后把它提升为管理员。

查看 API 容器里的数据库配置:

1
2
cd /opt/orion-key
docker compose exec api printenv | grep -E 'DB_|PASSWORD_PLAIN'

提升用户角色:

1
2
3
docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \
psql -h <db-host> -U <db-user> -d <db-name> \
-c "UPDATE users SET role='ADMIN', failed_login_attempts=0, lock_until=NULL, is_deleted=0 WHERE username='<username>';"

如果项目开启了明文密码模式:

1
PASSWORD_PLAIN=true

那登录密码就是你注册时填写的密码。一般不建议公开博客里写真实密码。

问题三:支付宝付款报 10006

支付宝下单返回:

1
2
3
4
{
"code": 10006,
"message": "System error, please try again later"
}

请求体本身没问题:

1
2
3
4
5
6
7
8
9
{
"product_id": "1d2f4e50-1c81-4c01-85af-e63371b730dc",
"spec_id": null,
"quantity": 1,
"email": "1@qq.com",
"payment_method": "alipay",
"idempotency_key": "5900d65f-de13-4379-b245-1dc8de3e6321",
"device": "mobile"
}

API 日志里的关键异常是:

1
2
3
4
5
IncorrectResultSizeDataAccessException:
Query did not return a unique result: 2 results were returned

PaymentChannelRepository.findByChannelCodeAndIsDeleted
OrderServiceImpl.validatePaymentMethod

原因

后台支付渠道里同时创建了两个支付宝渠道:一个是“自己的支付宝/原生支付宝”,另一个是“易支付支付宝”。它们最终都使用了同一个渠道代码:

1
2
channel_code = 'alipay'
is_deleted = 0

而前端下单只会传:

1
2
3
{
"payment_method": "alipay"
}

后端用 channel_code = 'alipay' AND is_deleted = 0 查询支付渠道时,本来期待只有一条,结果查到两条,于是直接抛出 Query did not return a unique result,前端看到兜底错误 10006

所以这不是请求体问题,也不是易支付网关一定不可用,而是支付渠道配置重复了。

解决方法

最简单的处理方式:删除其中一个支付宝渠道,只保留你实际要用的那一个

如果你准备用易支付,就删除“自己的支付宝/原生支付宝”那条;如果你准备用原生支付宝,就删除“易支付支付宝”那条。不过当前项目里原生支付宝代码还提示“尚未实现”,实际部署更建议保留易支付渠道。

可以先查重复记录:

1
2
3
docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \
psql -h <db-host> -U <db-user> -d <db-name> \
-c "SELECT id, channel_code, channel_name, provider_type, is_enabled, is_deleted, created_at FROM payment_channels WHERE channel_code='alipay' ORDER BY created_at DESC;"

然后在后台“支付渠道管理”里删除多余那条即可。也可以用 SQL 软删除,比如保留易支付,删除非 epay 的支付宝渠道:

1
2
3
4
5
6
7
docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \
psql -h <db-host> -U <db-user> -d <db-name> \
-c "UPDATE payment_channels
SET is_deleted=1, is_enabled=false
WHERE channel_code='alipay'
AND is_deleted=0
AND provider_type <> 'epay';"

如果你已经确认要保留某个具体 id,也可以按 id 删除另一条,这样最稳:

1
2
3
docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \
psql -h <db-host> -U <db-user> -d <db-name> \
-c "UPDATE payment_channels SET is_deleted=1, is_enabled=false WHERE id='<delete-channel-id>';"

建议顺手加唯一索引,避免后台再次创建重复渠道:

1
2
3
CREATE UNIQUE INDEX IF NOT EXISTS uk_payment_channels_active_code
ON payment_channels(channel_code)
WHERE is_deleted = 0;

易支付支付宝渠道配置应类似:

1
2
3
4
5
6
7
{
"pid": "商户ID",
"key": "商户密钥",
"api_url": "https://pay.example.com/",
"notify_url": "https://<your-domain>/api/payments/webhook/epay",
"return_url": "https://<your-domain>/order/query"
}

api_url 只填易支付根地址,不要写到 mapi.php,代码会自动拼接:

1
https://pay.example.com/mapi.php

最后整理一份检查清单

部署后先跑这些命令:

1
2
3
4
5
6
7
cd /opt/orion-key

docker compose ps
curl -i http://127.0.0.1:8083/api/auth/captcha
curl -I http://127.0.0.1:3000/
curl -I -H 'Host: <your-domain>' http://127.0.0.1/
curl -i -H 'Host: <your-domain>' http://127.0.0.1/api/auth/captcha

支付问题优先看日志:

1
2
docker compose logs api --since=30m
docker compose logs web --since=30m

数据库里重点查:

1
2
3
4
5
6
7
8
SELECT channel_code, provider_type, is_enabled, is_deleted, config_data
FROM payment_channels
ORDER BY channel_code, created_at DESC;

SELECT event_id, channel_code, order_id, process_result, created_at
FROM webhook_events
ORDER BY created_at DESC
LIMIT 20;

这次踩坑的结论

这套系统本身能跑,但 Docker 部署文档还有几个容易误导的点:

  • 前端 /api 代理不能写死 localhost:8083,容器内应使用 http://api:8083
  • Docker 首次部署不一定自动创建默认管理员账号,注册用户也不会自动拥有后台权限。
  • 支付渠道不要重复创建同一个 channel_code,比如“自己的支付宝”和“易支付支付宝”只能保留一个。

公开部署前,记得轮换所有曾经贴到日志、截图或聊天里的数据库密码、支付 Token 和后台密码。

项目推荐

整体来说,Orion Key 还是值得推荐的。它的前后端分离、Docker Compose 编排、商品/卡密/订单/支付/后台管理这些核心模块都比较完整,适合想快速搭一个自用发卡站的人。

这次踩到的问题更多集中在部署文档和边界处理上:比如默认管理员账号不够明确、前端代理在容器环境下容易误连 localhost、支付渠道缺少唯一性约束。把这些地方理顺之后,系统本身跑起来并不复杂。

如果你愿意自己动手排一下部署细节,或者后续顺手给它补几个初始化和配置校验,这个项目作为轻量发卡系统的起点是很合适的。


本站由 @wenmoux 使用 Stellar 主题创建。
本博客所有文章除特别声明外,均采用 CC BY-NC-SA 4.0 许可协议,转载请注明出处。
陕ICP备20007652号-1 | 陕公网安备 61072202000146号

本页面访问 次 | 👀总访问 次 | 🥷总访客