前言:为什么2026年选择 Docker 部署转卡码平台?

转卡码(卡密兑换/卡券直冲)系统在源码交易、虚拟商品销售、会员充值等场景中应用广泛。传统的手动部署方式需要逐一配置 PHP 环境、Nginx、MySQL、Redis,环境依赖冲突、版本不兼容等问题频发。

2026年,Docker 容器化部署已成为转卡码平台搭建的最佳实践。本文将从零开始,手把手教你用 Docker Compose 在 Linux 服务器上一键部署完整的转卡码平台,涵盖:

  • 服务器选购与基础环境配置
  • Docker + Docker Compose 安装
  • Nginx 反向代理与 SSL 证书
  • MariaDB 主从 + Redis 缓存
  • 核心转卡码系统容器化
  • 自动发货与对账脚本集成
  • 监控与日志采集

一、服务器选型与基础环境

1.1 服务器推荐配置

场景配置推荐厂商
入门(日活 <1000)2核4G / 50G SSD / 5Mbps阿里云轻量、腾讯云轻量
进阶(日活 1000-5000)4核8G / 80G SSD / 10Mbps腾讯云CVM、阿里云ECS
高可用(日活 >5000)8核16G + 多节点集群阿里云+香港节点混合

建议选择 CentOS 7.9+Ubuntu 22.04 LTS 作为宿主系统。本文示例基于 Ubuntu 22.04。

1.2 初始化服务器

SSH 登录后,先做基础安全配置:

# 更新系统并安装基础工具
sudo apt update && sudo apt upgrade -y
sudo apt install -y curl wget git vim ufw

# 配置防火墙
sudo ufw allow 22/tcp
sudo ufw allow 80/tcp
sudo ufw allow 443/tcp
sudo ufw --force enable

# 创建部署用户(非 root 运行 Docker)
sudo adduser deploy
sudo usermod -aG sudo deploy
su - deploy

二、安装 Docker 运行环境

2.1 安装 Docker Engine

# 官方一键安装脚本(2026年最新)
curl -fsSL https://get.docker.com -o get-docker.sh
sudo sh get-docker.sh

# 将 deploy 用户加入 docker 组
sudo usermod -aG docker deploy

# 验证安装
docker --version
# Docker version 27.3.1, build 9f0c9b6

# 安装 Docker Compose v2 插件
sudo apt install -y docker-compose-plugin
docker compose version
# Docker Compose version v2.29.7

2.2 配置 Docker 镜像加速

国内服务器建议配置镜像加速器以提升拉取速度:

sudo mkdir -p /etc/docker
sudo tee /etc/docker/daemon.json << 'EOF'
{
  "registry-mirrors": [
    "https://docker.1panel.live",
    "https://docker.xuanyuan.me"
  ],
  "log-driver": "json-file",
  "log-opts": {
    "max-size": "10m",
    "max-file": "3"
  }
}
EOF
sudo systemctl daemon-reload
sudo systemctl restart docker

三、项目目录结构与 docker-compose.yml

3.1 创建项目目录

mkdir -p ~/card-qr-platform/{nginx,app,mariadb,redis,logs,scripts}
cd ~/card-qr-platform

3.2 docker-compose.yml 完整配置

下面是一份生产级 docker-compose.yml,包含 Nginx、PHP-FPM、MariaDB、Redis、以及转卡码核心应用:

version: '3.8'

networks:
  card-qr-net:
    driver: bridge
    ipam:
      config:
        - subnet: 172.20.0.0/16

volumes:
  mariadb_data:
  redis_data:

services:
  # MariaDB 数据库
  mariadb:
    image: mariadb:10.11
    container_name: card-qr-db
    restart: unless-stopped
    networks:
      card-qr-net:
        ipv4_address: 172.20.0.10
    volumes:
      - mariadb_data:/var/lib/mysql
      - ./mariadb/init:/docker-entrypoint-initdb.d
    environment:
      MYSQL_ROOT_PASSWORD: ${DB_ROOT_PASS:-ChangeMe123!}
      MYSQL_DATABASE: card_qr
      MYSQL_USER: card_qr_user
      MYSQL_PASSWORD: ${DB_USER_PASS:-CardQrPass456!}
    command: >
      --character-set-server=utf8mb4
      --collation-server=utf8mb4_unicode_ci
      --max_connections=500
      --innodb_buffer_pool_size=1G
    healthcheck:
      test: ["CMD", "mysqladmin", "ping", "-h", "localhost"]
      interval: 10s
      timeout: 5s
      retries: 5

  # Redis 缓存与队列
  redis:
    image: redis:7-alpine
    container_name: card-qr-redis
    restart: unless-stopped
    networks:
      card-qr-net:
        ipv4_address: 172.20.0.11
    volumes:
      - redis_data:/data
      - ./redis/redis.conf:/usr/local/etc/redis/redis.conf:ro
    command: redis-server /usr/local/etc/redis/redis.conf
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 10s
      timeout: 3s
      retries: 5

  # PHP-FPM 运行转卡码核心应用
  app:
    image: php:8.3-fpm-alpine
    container_name: card-qr-app
    restart: unless-stopped
    networks:
      card-qr-net:
        ipv4_address: 172.20.0.12
    volumes:
      - ./app:/var/www/html
      - ./logs/php:/var/log/php
    depends_on:
      mariadb:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      DB_HOST: mariadb
      DB_PORT: 3306
      DB_NAME: card_qr
      DB_USER: card_qr_user
      DB_PASS: ${DB_USER_PASS:-CardQrPass456!}
      REDIS_HOST: redis
      REDIS_PORT: 6379
      APP_ENV: production
    # 安装必要 PHP 扩展
    command: >
      sh -c "docker-php-ext-install pdo pdo_mysql mysqli
      && docker-php-ext-install bcmath
      && php-fpm"

  # Nginx 反向代理
  nginx:
    image: nginx:1.27-alpine
    container_name: card-qr-nginx
    restart: unless-stopped
    networks:
      card-qr-net:
        ipv4_address: 172.20.0.13
    ports:
      - "80:80"
      - "443:443"
    volumes:
      - ./nginx/conf.d:/etc/nginx/conf.d:ro
      - ./nginx/ssl:/etc/nginx/ssl:ro
      - ./app:/var/www/html
      - ./logs/nginx:/var/log/nginx
    depends_on:
      - app

  # 自动发货队列消费者
  worker:
    image: php:8.3-cli-alpine
    container_name: card-qr-worker
    restart: unless-stopped
    networks:
      card-qr-net:
        ipv4_address: 172.20.0.14
    volumes:
      - ./app:/var/www/html
      - ./scripts:/scripts
    depends_on:
      mariadb:
        condition: service_healthy
      redis:
        condition: service_healthy
    environment:
      DB_HOST: mariadb
      DB_NAME: card_qr
      DB_USER: card_qr_user
      DB_PASS: ${DB_USER_PASS:-CardQrPass456!}
      REDIS_HOST: redis
      REDIS_PORT: 6379
    command: sh -c "docker-php-ext-install pdo pdo_mysql mysqli bcmath
      && php /var/www/html/worker/consume.php"
⚠️ 注意:docker-compose.yml 中的敏感变量建议通过 .env 文件注入,不要在配置中硬编码密码。

3.3 创建 .env 环境变量文件

cat > ~/card-qr-platform/.env << 'EOF'
DB_ROOT_PASS=MyStrongRootPass!2026
DB_USER_PASS=CardQrSecurePass!2026
EOF
chmod 600 ~/card-qr-platform/.env

四、Nginx 配置与 SSL

4.1 Nginx 站点配置

创建 ./nginx/conf.d/card-qr.conf

server {
    listen 80;
    server_name cardqr.yourdomain.com;
    return 301 https://$host$request_uri;
}

server {
    listen 443 ssl http2;
    server_name cardqr.yourdomain.com;

    ssl_certificate     /etc/nginx/ssl/fullchain.pem;
    ssl_certificate_key /etc/nginx/ssl/privkey.pem;
    ssl_protocols       TLSv1.2 TLSv1.3;
    ssl_ciphers         HIGH:!aNULL:!MD5;
    ssl_session_cache   shared:SSL:10m;

    root /var/www/html/public;
    index index.php index.html;

    # 转卡码支付回调地址特殊处理
    location /api/notify/ {
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_pass http://app:9000;
    }

    location / {
        try_files $uri $uri/ /index.php?$query_string;
    }

    location ~ \.php$ {
        fastcgi_pass   app:9000;
        fastcgi_index  index.php;
        fastcgi_param  SCRIPT_FILENAME  /var/www/html/public$fastcgi_script_name;
        include        fastcgi_params;
    }

    # 安全头
    add_header X-Frame-Options "SAMEORIGIN";
    add_header X-Content-Type-Options "nosniff";
    add_header X-XSS-Protection "1; mode=block";

    # 静态文件缓存
    location ~* \.(js|css|png|jpg|jpeg|gif|ico|svg)$ {
        expires 30d;
        add_header Cache-Control "public, immutable";
    }

    access_log /var/log/nginx/card-qr-access.log;
    error_log  /var/log/nginx/card-qr-error.log;
}

4.2 申请 SSL 证书

使用 acme.sh 自动申请 Let's Encrypt 免费证书:

curl https://get.acme.sh | sh -s email=admin@yourdomain.com
source ~/.bashrc

# 申请证书(需先确保 80 端口可访问)
acme.sh --issue -d cardqr.yourdomain.com --nginx

# 安装证书到 Nginx 目录
acme.sh --install-cert -d cardqr.yourdomain.com \
  --key-file ~/card-qr-platform/nginx/ssl/privkey.pem \
  --fullchain-file ~/card-qr-platform/nginx/ssl/fullchain.pem \
  --reloadcmd "docker exec card-qr-nginx nginx -s reload"

五、转卡码核心系统代码与初始化

5.1 核心数据表结构

创建一个初始化 SQL 脚本 ./mariadb/init/01-schema.sql

CREATE TABLE IF NOT EXISTS `cards` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `card_no` VARCHAR(32) NOT NULL UNIQUE COMMENT '卡密唯一编号',
  `card_pwd` VARCHAR(64) NOT NULL COMMENT '卡密密码',
  `amount` DECIMAL(10,2) NOT NULL DEFAULT 0.00 COMMENT '面值',
  `status` TINYINT NOT NULL DEFAULT 0 COMMENT '0=未售 1=已售 2=已核销 3=冻结',
  `order_id` VARCHAR(64) DEFAULT NULL COMMENT '关联订单号',
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  `sold_at` DATETIME DEFAULT NULL,
  `redeemed_at` DATETIME DEFAULT NULL,
  INDEX idx_status (`status`),
  INDEX idx_order_id (`order_id`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

CREATE TABLE IF NOT EXISTS `orders` (
  `id` BIGINT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
  `order_no` VARCHAR(64) NOT NULL UNIQUE COMMENT '商户订单号',
  `card_id` BIGINT UNSIGNED DEFAULT NULL,
  `amount` DECIMAL(10,2) NOT NULL,
  `pay_type` VARCHAR(16) NOT NULL COMMENT 'alipay/wechat',
  `pay_status` TINYINT DEFAULT 0 COMMENT '0=待支付 1=已支付 2=已发货 3=已完成',
  `channel` VARCHAR(32) DEFAULT NULL COMMENT '支付通道标识',
  `notify_url` VARCHAR(512) DEFAULT NULL,
  `paid_at` DATETIME DEFAULT NULL,
  `created_at` DATETIME DEFAULT CURRENT_TIMESTAMP,
  INDEX idx_order_no (`order_no`),
  INDEX idx_pay_status (`pay_status`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;

5.2 自动发货核心逻辑

以下是一个简化版的自动发货 PHP 消费者脚本 ./app/worker/consume.php

<?php
// consume.php — Redis 队列消费者,监听已支付订单并自动发货

require_once __DIR__ . '/../vendor/autoload.php';

use Predis\Client as RedisClient;
use PDO;

$redis = new RedisClient([
    'scheme' => 'tcp',
    'host'   => getenv('REDIS_HOST') ?: 'redis',
    'port'   => 6379,
]);

$pdo = new PDO(
    sprintf('mysql:host=%s;dbname=%s;charset=utf8mb4',
        getenv('DB_HOST') ?: 'mariadb',
        getenv('DB_NAME') ?: 'card_qr'
    ),
    getenv('DB_USER') ?: 'card_qr_user',
    getenv('DB_PASS') ?: 'CardQrPass456!',
    [PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION]
);

echo "[Worker] Card QR auto-delivery consumer started\n";

while (true) {
    // 从 Redis 阻塞队列获取待发货订单
    $payload = $redis->brpop('queue:deliver', 5);

    if (!$payload) {
        continue;
    }

    $orderNo = $payload[1];
    echo "[Worker] Processing order: {$orderNo}\n";

    try {
        $pdo->beginTransaction();

        // 查询订单
        $stmt = $pdo->prepare(
            "SELECT id, amount FROM orders WHERE order_no = ? AND pay_status = 1 FOR UPDATE"
        );
        $stmt->execute([$orderNo]);
        $order = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$order) {
            $pdo->rollBack();
            continue;
        }

        // 分配一张未售卡密(原子操作)
        $stmt = $pdo->prepare(
            "SELECT id, card_no, card_pwd FROM cards
             WHERE status = 0 ORDER BY id ASC LIMIT 1 FOR UPDATE"
        );
        $stmt->execute();
        $card = $stmt->fetch(PDO::FETCH_ASSOC);

        if (!$card) {
            // 库存不足,标记订单异常
            $pdo->exec("UPDATE orders SET pay_status = 4 WHERE order_no = '{$orderNo}'");
            $pdo->commit();

            // 发送告警通知
            $redis->lpush('queue:alert', json_encode([
                'type' => 'out_of_stock',
                'order_no' => $orderNo,
                'time' => date('Y-m-d H:i:s')
            ]));
            echo "[Worker] WARN: Out of stock for order {$orderNo}\n";
            continue;
        }

        // 更新卡密状态为已售
        $pdo->exec("UPDATE cards SET status = 1, order_id = '{$orderNo}', sold_at = NOW()
                     WHERE id = {$card['id']}");

        // 更新订单为已发货
        $pdo->exec("UPDATE orders SET card_id = {$card['id']}, pay_status = 2
                     WHERE order_no = '{$orderNo}'");

        $pdo->commit();

        echo "[Worker] OK: Order {$orderNo} → Card {$card['card_no']}\n";

        // 可选:触发回调通知
        $redis->lpush('queue:notify', json_encode([
            'order_no' => $orderNo,
            'card_no'  => $card['card_no'],
            'card_pwd' => $card['card_pwd']
        ]));

    } catch (Exception $e) {
        $pdo->rollBack();
        echo "[Worker] ERROR: " . $e->getMessage() . "\n";
        // 重新入队重试
        $redis->lpush('queue:deliver', $orderNo);
        sleep(1);
    }
}
🔐 生产环境中,卡密的 card_pwd 字段应使用 AES-256 加密存储,密钥独立于数据库密码之外管理。

六、一键启动与验证

6.1 启动所有服务

cd ~/card-qr-platform

# 启动全部容器
docker compose up -d

# 查看运行状态
docker compose ps

# 查看实时日志
docker compose logs -f

6.2 健康检查

# 测试 Nginx 是否响应
curl -I https://cardqr.yourdomain.com

# 测试 PHP-FPM 是否正常工作
curl https://cardqr.yourdomain.com/health.php

# 检查数据库连接
docker exec card-qr-app php -r "
try {
    new PDO('mysql:host=mariadb;dbname=card_qr','card_qr_user','CardQrPass456!');
    echo 'DB OK';
} catch(Exception \$e) {
    echo 'DB FAIL: '.\$e->getMessage();
}"

6.3 模拟测试一笔订单

# 插入一张测试卡密
docker exec -i card-qr-db mysql -uroot -pMyStrongRootPass!2026 card_qr << 'SQL'
INSERT INTO cards (card_no, card_pwd, amount)
VALUES ('TEST-' || UUID_SHORT(), 'PASSWORD123', 99.00);
SQL

# 模拟创建一笔订单并触发发货
docker exec card-qr-app php -r "
\$orderNo = 'TEST-' . date('YmdHis');
echo \"Order: {\$orderNo}\n\";
// 推送至发货队列
echo \"Push to queue...\n\";
"

七、生产环境运维锦囊

7.1 数据库定时备份

通过 cron 定时执行备份:

cat > ~/card-qr-platform/scripts/backup.sh << 'SCRIPT'
#!/bin/bash
BACKUP_DIR="/data/backups/card-qr"
DATE=$(date +%Y%m%d_%H%M%S)
mkdir -p "$BACKUP_DIR"

docker exec card-qr-db mysqldump \
  -uroot -p"${DB_ROOT_PASS:-MyStrongRootPass!2026}" \
  --all-databases --single-transaction --routines --triggers \
  | gzip > "$BACKUP_DIR/card_qr_$DATE.sql.gz"

# 保留最近 30 天
find "$BACKUP_DIR" -name "*.sql.gz" -mtime +30 -delete
echo "Backup completed: card_qr_$DATE.sql.gz"
SCRIPT
chmod +x ~/card-qr-platform/scripts/backup.sh

# 添加到 crontab(每天凌晨 3 点)
(crontab -l 2>/dev/null; echo "0 3 * * * /home/deploy/card-qr-platform/scripts/backup.sh >> /var/log/card-qr-backup.log 2>&1") | crontab -

7.2 监控与告警

# 容器健康检查脚本
cat > ~/card-qr-platform/scripts/healthcheck.sh << 'SCRIPT'
#!/bin/bash
SERVICES="card-qr-nginx card-qr-app card-qr-db card-qr-redis card-qr-worker"
for svc in $SERVICES; do
    STATUS=$(docker inspect -f '{{.State.Status}}' "$svc" 2>/dev/null)
    if [ "$STATUS" != "running" ]; then
        echo "ALERT: $svc is $STATUS"
        # 发送通知(飞书/企业微信 webhook)
        curl -s -X POST "https://open.feishu.cn/open-apis/bot/v2/hook/YOUR_WEBHOOK" \
          -H "Content-Type: application/json" \
          -d "{\"msg_type\":\"text\",\"content\":{\"text\":\"⚠️ $svc 状态异常: $STATUS\"}}"
    fi
done
SCRIPT
chmod +x ~/card-qr-platform/scripts/healthcheck.sh

# 每 5 分钟执行一次
(crontab -l 2>/dev/null; echo "*/5 * * * * /home/deploy/card-qr-platform/scripts/healthcheck.sh") | crontab -

7.3 Docker 资源限制与日志轮转

docker-compose.yml 中为每个服务设置资源上限:

services:
  app:
    # ... 原有配置 ...
    deploy:
      resources:
        limits:
          cpus: '1.0'
          memory: 512M
        reservations:
          cpus: '0.5'
          memory: 256M

  mariadb:
    deploy:
      resources:
        limits:
          cpus: '2.0'
          memory: 2G

同时配置系统级日志轮转:

sudo tee /etc/logrotate.d/docker-containers << 'EOF'
/var/lib/docker/containers/*/*.log {
    rotate 7
    daily
    compress
    missingok
    delaycompress
    copytruncate
}
EOF

八、与源码商城产品的完美集成

搭建好的转卡码平台可以与 源码商城 的产品无缝对接。我们的 转卡码系统 V3 已经内置了 Docker Compose 部署模板,支持一键启动并预置了以下功能:

  • 多通道支付集成(支付宝 Native + 微信 Native)
  • 智能入口池防封 机制
  • 代理分润体系 自动结算
  • 异步对账与异常告警
  • 管理后台(订单管理、卡密导入、数据统计)

总结

本文完整演示了如何在一台 Linux 服务器上,通过 Docker Compose 一键部署高可用的转卡码平台。容器化方案带来的核心收益:

  • 环境隔离 — 每个服务独立容器,互不干扰
  • 快速部署docker compose up -d 一行命令启动全部服务
  • 水平扩展 — 只需增加容器副本即可应对流量增长
  • 易于迁移 — 整个项目目录打包即可迁移到新服务器

如果你正在寻找一个开箱即用的转卡码系统,欢迎访问 源码商城 了解我们的专业解决方案。