欢迎光临
我们一直在努力

在生产环境旁搭建一套隔离的第三方接口测试环境(SIP 推单对接实战)

> 本文记录了一个真实场景:某第三方推单方要对接我们的订单推送接口(SIP),为不影响正在跑的生产环境,在云端生产应用旁边独立搭建了一套测试环境,通过 Nginx 同一域名下用路径区分 prod / test,做到「环境完全隔离、配置可控、随时可测」。不编造、不猜测,踩过的坑都写出来。

一、背景:为什么需要一套独立的测试环境

我们的订单匹配系统在云端 Ubuntu 上跑着一套 Flask + SQLite 应用,对外提供「订单推送接收接口」(下文简称 SIP 接口),第三方供应商通过这个接口把订单推给我们。

这次有一个新的推单方要对接。按惯例,第三方对接调试时不能直接在生产环境上测,原因很现实:

  • 会污染生产数据——调试用的脏订单、假数据一旦进来,会被误判为真实订单,甚至自动导入到 ERP 系统,造成脏数据。
  • 安全风险——测试期间 IP 白名单、密钥等安全策略往往要临时放宽,等于把生产接口的安全口子打开给外部测试,这是不可接受的。
  • 不可控——第三方测试人员什么时间测、怎么测不可控,可能随时触发生产逻辑,影响在跑的正式业务。
  • 所以方案很明确:在生产应用旁边,单独搭一套"长得一模一样、但完全隔离"的测试环境,让第三方在测试环境里随便折腾,测通了再切生产。

    二、生产环境的现状

    先摸清生产环境长什么样,测试环境才能照着搭。生产环境是一套 Flask + SQLite 的订单系统:

    项目

    生产环境

    代码目录

    /opt/qorder-match/app/

    进程方式

    Gunicorn(systemd 服务 qorder-match.service)

    监听端口

    127.0.0.1:5000(Nginx 反代)

    数据库

    SQLite(data.db)

    SIP 接口

    /api/sip/order/create

    SIP 配置存储

    存在 SQLite system_settings 表里

    SIP 相关键

    厂商号 vendorNo、验签公钥、我方私钥、API 环境标记、IP 白名单

    > 关键点:SIP 的配置(vendorNo、公钥、私钥、白名单等)不是写死在代码里,而是存在数据库 system_settings 表里,通过环境变量 ORDER_MATCH_DB 指定数据库文件路径。这一点是后面能轻松搭出隔离环境的基石——换个库文件就等于换一套配置。

    三、测试环境的设计思路(隔离点)

    要让测试环境「像生产但又不影响生产」,我定了这几个隔离点:

    隔离点

    生产

    测试

    代码目录

    /opt/qorder-match/app

    /opt/qorder-sip-test/app

    虚拟环境

    /opt/qorder-match/venv

    /opt/qorder-sip-test/venv

    数据库

    data.db(生产库)

    空的 data.db

    端口

    127.0.0.1:5000

    127.0.0.1:5001

    systemd 服务

    qorder-match.service

    qorder-sip-test.service

    Nginx 路径

    /api/sip/

    /api/sip-test/

    API 环境标记

    production

    test

    IP 白名单

    只放行若干业务 IP

    空(放开,便于测试)

    吉客云密钥

    已配置

    不配置

    最后一条是最关键的防呆设计:测试环境故意不配 ERP(吉客云)的对接密钥。这样就算第三方在测试环境发了脏单,接口也只会返回处理结果,而绝不会真正导入到 ERP 系统,等于在数据层面加了一道物理防火墙。

    四、搭建步骤

    4.1 建独立目录和虚拟环境

    # 1. 代码目录

    mkdir -p /opt/qorder-sip-test/app

    # 2. 独立虚拟环境

    python3 -m venv /opt/qorder-sip-test/venv

    # 3. 复用生产 venv 的已装包(避免重复下载大包,也保证依赖版本一致)

    #    生产环境已经有 playwright、cryptography 等一堆大包,直接复用它的 site-packages

    rm -rf /opt/qorder-sip-test/venv/lib/python3.12/site-packages

    cp -r /opt/qorder-match/venv/lib/python3.12/site-packages \\

          /opt/qorder-sip-test/venv/lib/python3.12/site-packages

    > 经验:搭建测试环境时,复用生产环境的 site-packages 是很省事的做法。既不用重新 pip install 一堆大包(省时间、避免网络超时),又能保证测试环境的依赖版本和生产完全一致,避免「测试好好的、上生产却报错」这种版本差异坑。

    4.2 复制代码并整理

    # 把生产应用代码复制到测试目录(只复制运行时文件)

    cd /opt/qorder-match/app

    cp -r app.py sip_rsa.py secrets.env templates static \\

          /opt/qorder-sip-test/app/

    4.3 初始化一个独立的空库

    应用代码在模块加载时(init_db())会自动建表,所以只需要给测试环境配一个全新的数据库文件即可。

    # 用环境变量指定测试库路径,启动一次让代码自动建表

    cd /opt/qorder-sip-test/app

    ORDER_MATCH_DB=/opt/qorder-sip-test/app/data.db \\

      /opt/qorder-sip-test/venv/bin/python -c "import app"

    # 此时会生成一个空的 data.db(含全部表结构)

    4.4 注入测试用的 SIP 配置

    生产环境的 SIP 配置(vendorNo、验签公钥、我方私钥、IP 白名单)存在生产 data.db 的 system_settings 表里。测试环境需要一个「具备生产配置、但关键项被测试化」的配置。用一段 seed 脚本写入测试库:

    # seed_sip.py —— 把生产 SIP 配置灌进测试库,并置为 test 环境

    import sqlite3

    conn = sqlite3.connect('/opt/qorder-sip-test/app/data.db')

    c = conn.cursor()

    # 从生产库读出真实配置

    prod = sqlite3.connect('/opt/qorder-match/app/data.db')

    rows = prod.execute("SELECT key, value FROM system_settings WHERE key LIKE 'sip_%'").fetchall()

    # 写入测试库

    for k, v in rows:

        c.execute("INSERT OR REPLACE INTO system_settings (key, value) VALUES (?, ?)", (k, v))

    # 关键:覆盖成测试环境标记

    c.execute("INSERT OR REPLACE INTO system_settings (key, value) VALUES ('sip_api_env', 'test')")

    # 关键:放开 IP 白名单,方便第三方任意 IP 测试

    c.execute("INSERT OR REPLACE INTO system_settings (key, value) VALUES ('sip_allowed_ips', '')")

    conn.commit()

    conn.close()

    prod.close()

    print('测试库 SIP 配置写入完成')

    > 要点:测试库复用了生产的真实密钥和公钥(保证验签逻辑和生产一致),但只改两个字段——环境标记 test、IP 白名单放开。这样第三方测出来的验签行为跟生产一致,但 IP 限制放松了,测起来不会因为 IP 不对被拒。

    4.5 配置 systemd 服务(独立端口)

    # /etc/systemd/system/qorder-sip-test.service

    [Unit]

    Description=SIP Test Env (Flask + Gunicorn)

    After=network.target

    [Service]

    User=root

    WorkingDirectory=/opt/qorder-sip-test/app

    # 关键:指定独立的测试数据库 + 测试端口

    Environment=ORDER_MATCH_DB=/opt/qorder-sip-test/app/data.db

    ExecStart=/opt/qorder-sip-test/venv/bin/gunicorn \\

        –workers 1 \\

        –threads 4 \\

        –bind 127.0.0.1:5001 \\

        –access-logfile /var/log/qorder-sip-test-access.log \\

        –error-logfile /var/log/qorder-sip-test-error.log \\

        wsgi:app

    Restart=always

    RestartSec=5

    [Install]

    WantedBy=multi-user.target

    systemctl daemon-reload

    systemctl enable qorder-sip-test

    systemctl start qorder-sip-test

    systemctl is-active qorder-sip-test   # active

    > 注意:–bind 用 127.0.0.1:5001 而不是 0.0.0.0,测试环境只让本机 Nginx 反代访问,不直接暴露公网端口。生产环境同理也是只绑内网,由 Nginx 统一对外。

    五、Nginx 同一域名用路径区分 prod / test

    这里是最巧妙的一步。生产和测试都共用同一个域名(api.xx.com.cn),通过 URL 路径前缀区分:

    • 生产:/api/sip/… → 反代到 127.0.0.1:5000(生产)
    • 测试:/api/sip-test/… → 内部 rewrite 成 /api/sip/… → 反代到 127.0.0.1:5001(测试)

    为什么要 rewrite?因为应用里接口路由写死是 /api/sip/order/create,测试环境代码没改,所以 Nginx 要把 /api/sip-test/ 前缀剥掉,转成 /api/sip/ 再转发到测试实例,这样代码一行不用改。

    # /etc/nginx/sites-available/api-xx —— 关键配置片段

    server {

        listen 443 ssl;

        server_name api.xx.com.cn;

        # 生产 SIP 接口 → 5000

        location /api/sip/ {

            proxy_pass http://127.0.0.1:5000;

            proxy_set_header Host $host;

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header X-Forwarded-Proto $scheme;

        }

        # 测试 SIP 接口 → 先 rewrite 成 /api/sip/ 再转到 5001

        location /api/sip-test/ {

            rewrite ^/api/sip-test/(.*)$ /api/sip/$1 break;

            proxy_pass http://127.0.0.1:5001;

            proxy_set_header Host $host;

            proxy_set_header X-Real-IP $remote_addr;

            proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;

            proxy_set_header X-Forwarded-Proto $scheme;

        }

    }

    nginx -t

    systemctl reload nginx

    > 经验:同一套代码、同一个域名,用 location 前缀 + rewrite 就能在 Nginx 层天然切分出「生产/测试」两个入口,应用层完全不用感知。这让「测试环境验证通过 → 切生产」只需要换一个 URL 前缀,心理负担和数据风险都降到最低。

    六、验证:生产 / 测试返回对比

    搭完后,用同一个请求打两个地址,对比响应,能非常直观地验证环境是否正确隔离:

    # 测试环境(无业务 IP,预期返回「缺少必填字段」,说明已走到参数校验,路由和实例都通了)

    curl -s https://api.xx.com.cn/api/sip-test/order/create

    # → {"code":xxx,"msg":"缺少必填字段"}

    # 生产环境(预期返回「IP 不在白名单」,说明生产防护正常)

    curl -s https://api.xx.com.cn/api/sip/order/create

    # → {"code":xxx,"msg":"IP 不在白名单"}

    地址

    返回

    含义

    /api/sip-test/order/create

    缺少必填字段

    测试环境路由通了,走到参数校验,且没被 IP 白名单拦住(因为放开了)

    /api/sip/order/create

    IP 不在白名单

    生产环境防护正常,陌生 IP 被拒之门外

    这个对比非常关键:同一套代码,两个入口,行为完全符合预期——测试环境放开了、生产环境守着。说明隔离成功,可以放心交给第三方去测。

    同时确认两个监听端口都从外部 HTTPS 可达(443/8443 双入口都通)。

    七、踩坑与经验

  • `if __name__ == '__main__'` 里的后台线程不会在 Gunicorn 里跑。SIP 等接口背后的文件监控、定时任务、投递线程,原来写在 __main__ 里,用 Gunicorn 后不会自动执行,需要在 wsgi.py 入口里手动调用启动函数。这是 Flask + Gunicorn 的老坑。
  • SIP 配置别写死在代码里,存数据库 + 环境变量指定库文件。这次能轻松搭出隔离环境,全靠「配置在库、库由环境变量指定」。换一个空库文件就等于换一套配置,隔离零成本。这是架构设计的红利。
  • 测试环境故意不配 ERP 密钥。这是最关键的防脏数据设计。测试环境配了密钥,第三方脏单可能就真的进去了;不配,最多报错,永远不会污染生产 ERP。
  • 测试环境 IP 白名单放开,生产保持收紧。测试环境白名单留空(放开),第三方用什么 IP 都能测,不会因为 IP 不对而卡住;生产环境白名单保持不变,陌生 IP 进不来。
  • 复用生产 venv 的 site-packages。避免重复装大包,也保证依赖版本和生产完全一致,杜绝「测试通过、生产报错」的版本差异。
  • 八、总结

    关键动作

    为什么

    独立目录 + 独立 venv + 独立空库

    环境层面物理隔离

    独立端口 5001 + 独立 systemd 服务

    进程层面隔离,互不影响

    Nginx 路径前缀 + rewrite

    同一域名区分 prod/test,应用代码零改动

    测试环境不配 ERP 密钥

    防脏单污染生产 ERP,物理级安全防线

    测试放开 IP、生产保持白名单

    测试方便、生产安全,互不干扰

    这次「生产环境旁搭隔离测试环境」的核心思路就一句话:用「独立库文件 + 独立端口 + Nginx 路径区分 + 关键能力留空」的组合拳,把测试环境隔离得干净、干净、可回滚。同样的思路可以复用到任何「第三方对接、需安全测试」的场景。

    如果你也踩过类似的坑,欢迎评论区交流。觉得有用可以点赞收藏

    赞(0)
    未经允许不得转载:171主机测评 » 在生产环境旁搭建一套隔离的第三方接口测试环境(SIP 推单对接实战)
    分享到: 更多 (0)

    评论 抢沙发

    • 昵称 (必填)
    • 邮箱 (必填)
    • 网址