这篇记录一次 Orion Key 发卡系统的生产部署排障。环境是 Docker Compose 跑 api、web、bepusdt,前面用 OpenResty/1Panel 做反向代理。问题集中在三个地方:前端连不上后端、默认管理员账号认知偏差、支付宝支付渠道重复创建。
下文用 <your-domain> 代表你的实际域名,用 <db-host>、<db-password> 等占位符代替敏感信息。不要把真实数据库密码、支付 Token、后台密码写进公开博客。
部署结构
核心服务大概是这样:
1 | services: |
这里有两个关键点:
127.0.0.1:8083和127.0.0.1:3000只允许服务器本机访问,公网访问必须经过 OpenResty。- 容器之间不能用
127.0.0.1互相访问,web访问api应该用 Compose 服务名:http://api:8083。
问题一:前端提示 ECONNREFUSED,无法连后端
最开始浏览器报错类似:
1 | Failed to proxy http://localhost:8083/api/auth/login |
后端其实是活着的:
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 | environment: |
如果使用已经修过 runtime proxy 的镜像,可以在 .env 里指定:
1 | WEB_IMAGE=wenmoux/orion-key-web:runtime-proxy |
然后重启:
1 | cd /opt/orion-key |
验证 OpenResty 反代:
1 | curl -I -H 'Host: <your-domain>' http://127.0.0.1/ |
两个都能返回 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 | cd /opt/orion-key |
提升用户角色:
1 | docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \ |
如果项目开启了明文密码模式:
1 | PASSWORD_PLAIN=true |
那登录密码就是你注册时填写的密码。一般不建议公开博客里写真实密码。
问题三:支付宝付款报 10006
支付宝下单返回:
1 | { |
请求体本身没问题:
1 | { |
API 日志里的关键异常是:
1 | IncorrectResultSizeDataAccessException: |
原因
后台支付渠道里同时创建了两个支付宝渠道:一个是“自己的支付宝/原生支付宝”,另一个是“易支付支付宝”。它们最终都使用了同一个渠道代码:
1 | channel_code = 'alipay' |
而前端下单只会传:
1 | { |
后端用 channel_code = 'alipay' AND is_deleted = 0 查询支付渠道时,本来期待只有一条,结果查到两条,于是直接抛出 Query did not return a unique result,前端看到兜底错误 10006。
所以这不是请求体问题,也不是易支付网关一定不可用,而是支付渠道配置重复了。
解决方法
最简单的处理方式:删除其中一个支付宝渠道,只保留你实际要用的那一个。
如果你准备用易支付,就删除“自己的支付宝/原生支付宝”那条;如果你准备用原生支付宝,就删除“易支付支付宝”那条。不过当前项目里原生支付宝代码还提示“尚未实现”,实际部署更建议保留易支付渠道。
可以先查重复记录:
1 | docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \ |
然后在后台“支付渠道管理”里删除多余那条即可。也可以用 SQL 软删除,比如保留易支付,删除非 epay 的支付宝渠道:
1 | docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \ |
如果你已经确认要保留某个具体 id,也可以按 id 删除另一条,这样最稳:
1 | docker run --rm -e PGPASSWORD='<db-password>' postgres:16-alpine \ |
建议顺手加唯一索引,避免后台再次创建重复渠道:
1 | CREATE UNIQUE INDEX IF NOT EXISTS uk_payment_channels_active_code |
易支付支付宝渠道配置应类似:
1 | { |
api_url 只填易支付根地址,不要写到 mapi.php,代码会自动拼接:
1 | https://pay.example.com/mapi.php |
最后整理一份检查清单
部署后先跑这些命令:
1 | cd /opt/orion-key |
支付问题优先看日志:
1 | docker compose logs api --since=30m |
数据库里重点查:
1 | SELECT channel_code, provider_type, is_enabled, is_deleted, config_data |
这次踩坑的结论
这套系统本身能跑,但 Docker 部署文档还有几个容易误导的点:
- 前端
/api代理不能写死localhost:8083,容器内应使用http://api:8083。 - Docker 首次部署不一定自动创建默认管理员账号,注册用户也不会自动拥有后台权限。
- 支付渠道不要重复创建同一个
channel_code,比如“自己的支付宝”和“易支付支付宝”只能保留一个。
公开部署前,记得轮换所有曾经贴到日志、截图或聊天里的数据库密码、支付 Token 和后台密码。
项目推荐
整体来说,Orion Key 还是值得推荐的。它的前后端分离、Docker Compose 编排、商品/卡密/订单/支付/后台管理这些核心模块都比较完整,适合想快速搭一个自用发卡站的人。
这次踩到的问题更多集中在部署文档和边界处理上:比如默认管理员账号不够明确、前端代理在容器环境下容易误连 localhost、支付渠道缺少唯一性约束。把这些地方理顺之后,系统本身跑起来并不复杂。
如果你愿意自己动手排一下部署细节,或者后续顺手给它补几个初始化和配置校验,这个项目作为轻量发卡系统的起点是很合适的。