从零搭建”一物一码”合格证查询系统——云服务器部署实战
系列文章 5/5 | 适合读者:技术开发者 | 风格:手把手教程,命令可直接复制执行
前四篇文章我们完成了数据模型设计、后端 API 开发、前端双端构建以及二维码签名与防伪机制。现在,是时候把整套系统搬上云端,让它真正跑起来了。本篇将以一台全新的 Ubuntu 云服务器为起点,带你走完从环境初始化到 HTTPS 上线的完整流程。
1. 服务器环境准备
以 Ubuntu 22.04 / 24.04 LTS 为例,阿里云、腾讯云、AWS 均适用。
1.1 系统更新与基础软件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| sudo apt update && sudo apt upgrade -y
sudo apt install python3 python3-venv python3-pip nginx git -y
curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - sudo apt install nodejs -y
python3 --version node --version nginx -v
|
1.2 创建部署用户
不要用 root 跑应用。创建一个专用的 deploy 用户:
1 2 3 4 5 6 7 8 9 10
| sudo adduser deploy sudo usermod -aG sudo deploy
su - deploy mkdir -p ~/.ssh && chmod 700 ~/.ssh
echo "ssh-ed25519 AAAA...你的公钥..." >> ~/.ssh/authorized_keys chmod 600 ~/.ssh/authorized_keys
|
1.3 防火墙配置
只开放必要端口,最小化攻击面:
1 2 3 4 5 6 7
| sudo ufw default deny incoming sudo ufw default allow outgoing sudo ufw allow 22/tcp sudo ufw allow 80/tcp sudo ufw allow 443/tcp sudo ufw enable sudo ufw status
|
提示:如果你使用云厂商的安全组,记得在控制台同步放行这三个端口。
2. 后端部署
2.1 拉取代码与虚拟环境
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16
| sudo mkdir -p /var/www/backend sudo chown deploy:deploy /var/www/backend
cd /var/www/backend git clone https://github.com/yourorg/cert-query-system.git .
python3 -m venv venv source venv/bin/activate
pip install --upgrade pip pip install -r requirements.txt pip install gunicorn
|
2.2 生产环境配置
创建 .env.production 文件,绝对不要提交到 Git:
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| cat > .env.production << 'EOF' APP_ENV=production DEBUG=False SECRET_KEY=your-super-secret-random-string-at-least-50-chars ALLOWED_HOSTS=yourdomain.com BASE_URL=https://yourdomain.com
SIGNING_ACTIVE_KID=prod-key-001 SIGNING_ACTIVE_PRIVATE_KEY_PEM=-----BEGIN PRIVATE KEY-----\nMC4CAQ...替换为真实密钥...\n-----END PRIVATE KEY-----
EOF
|
安全提示:SECRET_KEY 可用 python3 -c "import secrets; print(secrets.token_urlsafe(64))" 生成。
2.3 数据库初始化与静态文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| source /var/www/backend/venv/bin/activate
python manage.py migrate
python manage.py generate_signing_key
python manage.py collectstatic --noinput
python manage.py createsuperuser
|
2.4 Gunicorn 配置
先手动测试一下 Gunicorn 是否能正常启动:
1 2 3 4 5 6
| gunicorn config.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 3 \ --timeout 120 \ --access-logfile /var/log/gunicorn/access.log \ --error-logfile /var/log/gunicorn/error.log
|
workers 数量公式:CPU 核心数 x 2 + 1。2 核服务器设 5 个 worker 即可。
确认无误后,用 systemd 托管进程,实现开机自启和崩溃自动重启。
2.5 Systemd 服务文件
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23
| sudo tee /etc/systemd/system/gunicorn.service << 'EOF' [Unit] Description=Gunicorn - Cert Query System After=network.target
[Service] User=deploy Group=deploy WorkingDirectory=/var/www/backend Environment="PATH=/var/www/backend/venv/bin" EnvironmentFile=/var/www/backend/.env.production ExecStart=/var/www/backend/venv/bin/gunicorn config.wsgi:application \ --bind 127.0.0.1:8000 \ --workers 3 \ --timeout 120 \ --access-logfile /var/log/gunicorn/access.log \ --error-logfile /var/log/gunicorn/error.log Restart=always RestartSec=3
[Install] WantedBy=multi-user.target EOF
|
1 2 3 4 5 6 7 8 9 10 11
| sudo mkdir -p /var/log/gunicorn sudo chown deploy:deploy /var/log/gunicorn
sudo systemctl daemon-reload sudo systemctl start gunicorn sudo systemctl enable gunicorn
sudo systemctl status gunicorn
|
3. 前端构建与部署
项目包含两个前端应用,分别构建后部署到 Nginx 的静态文件目录。
3.1 管理后台(Admin Panel)
1 2 3 4 5 6 7 8 9 10 11
| cd /var/www/backend/frontend/admin
npm install npm run build
sudo mkdir -p /var/www/admin sudo cp -r dist/* /var/www/admin/ sudo chown -R deploy:deploy /var/www/admin
|
3.2 公众查询端(Cert View)
1 2 3 4 5 6 7 8 9 10 11
| cd /var/www/backend/frontend/cert-view
npm install npm run build
sudo mkdir -p /var/www/cert-view sudo cp -r dist/* /var/www/cert-view/ sudo chown -R deploy:deploy /var/www/cert-view
|
构建建议:如果服务器内存不足 2GB,可以在本地构建后 scp 上传 dist/ 目录,避免 OOM。
4. Nginx 配置
这是整套系统的核心路由层。一个 Nginx 同时服务两个 SPA、代理 API、托管静态资源。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77
| sudo tee /etc/nginx/sites-available/cert-query << 'NGINX'
server { listen 80; server_name yourdomain.com; return 301 https://$host$request_uri; }
server { listen 443 ssl http2; server_name yourdomain.com;
ssl_certificate /etc/letsencrypt/live/yourdomain.com/fullchain.pem; ssl_certificate_key /etc/letsencrypt/live/yourdomain.com/privkey.pem; ssl_protocols TLSv1.2 TLSv1.3; ssl_ciphers HIGH:!aNULL:!MD5; ssl_prefer_server_ciphers on;
gzip on; gzip_types text/plain text/css application/json application/javascript text/xml application/xml image/svg+xml; gzip_min_length 1024; gzip_vary on;
add_header X-Frame-Options "SAMEORIGIN" always; add_header X-Content-Type-Options "nosniff" always; add_header X-XSS-Protection "1; mode=block" always; add_header Referrer-Policy "strict-origin-when-cross-origin" always; add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
root /var/www/cert-view; index index.html;
location / { try_files $uri $uri/ /index.html; }
location /manage/ { alias /var/www/admin/; try_files $uri $uri/ /manage/index.html; }
location /api/ { proxy_pass http://127.0.0.1:8000; 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;
client_max_body_size 10M; }
location /static/ { alias /var/www/backend/staticfiles/; expires 30d; add_header Cache-Control "public, immutable"; }
location /media/ { alias /var/www/backend/media/; expires 7d; } } NGINX
|
1 2 3 4 5 6 7 8 9
| sudo ln -sf /etc/nginx/sites-available/cert-query /etc/nginx/sites-enabled/ sudo rm -f /etc/nginx/sites-enabled/default
sudo nginx -t
sudo systemctl reload nginx
|
5. SSL 证书配置
使用 Let’s Encrypt 免费证书,certbot 会自动修改 Nginx 配置:
1 2 3 4 5 6 7 8
| sudo apt install certbot python3-certbot-nginx -y
sudo certbot --nginx -d yourdomain.com
sudo certbot renew --dry-run
|
注意:申请前请确保域名 DNS 已解析到服务器 IP,且 80 端口可访问。国内服务器需完成 ICP 备案。
Certbot 会自动在 crontab 或 systemd timer 中添加续期任务,证书到期前 30 天会自动续期,无需手动操心。
6. 数据库升级(SQLite –> MySQL / PostgreSQL)
开发阶段用 SQLite 快速迭代没问题,但生产环境强烈建议升级:
| 维度 |
SQLite |
MySQL / PostgreSQL |
| 并发写入 |
单写锁,多用户时卡顿 |
行级锁,高并发无压力 |
| 备份恢复 |
复制文件,简单但粗暴 |
mysqldump / pg_dump,支持增量备份 |
| 监控 |
无内置工具 |
慢查询日志、性能监控完善 |
| 部分唯一索引 |
原生支持 |
MySQL 需要变通方案(见下文) |
6.1 迁移步骤
1 2 3 4 5 6 7
| python manage.py dumpdata --natural-foreign --natural-primary -o backup.json
pip install mysqlclient
|
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
| DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'cert_db', 'USER': 'cert_user', 'PASSWORD': 'strong-password-here', 'HOST': '127.0.0.1', 'PORT': '3306', 'OPTIONS': { 'charset': 'utf8mb4', 'init_command': "SET sql_mode='STRICT_TRANS_TABLES'", }, } }
|
1 2 3 4 5
| python manage.py migrate
python manage.py loaddata backup.json
|
6.2 MySQL 部分唯一索引注意事项
我们的模型中使用了 UniqueConstraint 带 condition 参数(部分唯一索引),这在 SQLite 和 PostgreSQL 中原生支持,但 MySQL 不支持部分唯一索引。解决方案有两种:
- 应用层校验:在
Model.clean() 或 Serializer 中手动检查唯一性约束
- 改用 PostgreSQL:如果业务依赖此特性较多,PostgreSQL 是更好的选择
7. 运维与监控
系统上线只是开始,稳定运行才是目标。
7.1 日志管理
项目的 settings.py 已配置了 RotatingFileHandler,日志文件按大小自动轮转,无需额外配置 logrotate。关注以下日志路径:
1 2 3 4 5
| /var/log/gunicorn/access.log # Gunicorn 访问日志 /var/log/gunicorn/error.log # Gunicorn 错误日志 /var/www/backend/logs/app.log # Django 应用日志 /var/log/nginx/access.log # Nginx 访问日志 /var/log/nginx/error.log # Nginx 错误日志
|
7.2 数据库定时备份
1 2
| # MySQL 每日备份,保留最近 30 天 0 3 * * * mysqldump -u cert_user -p'password' cert_db | gzip > /var/backups/db/cert_db_$(date +\%Y\%m\%d).sql.gz && find /var/backups/db/ -name "*.sql.gz" -mtime +30 -delete
|
1 2 3
| sudo mkdir -p /var/backups/db sudo chown deploy:deploy /var/backups/db
|
7.3 证书到期监控
系统内置的 Notification 模块可以用来监控 SSL 证书和签名密钥的到期时间。建议添加一个 health check 端点:
1 2 3 4 5 6 7 8 9 10
| from django.http import JsonResponse from datetime import datetime
def health_check(request): return JsonResponse({ 'status': 'ok', 'timestamp': datetime.now().isoformat(), 'version': '1.0.0', })
|
1 2 3 4
| urlpatterns += [ path('api/health/', health_check, name='health-check'), ]
|
配合外部监控服务(如 UptimeRobot)定期请求此端点,服务异常时自动告警。
7.4 Gunicorn Worker 调优
修改 systemd 服务文件中的 --workers 参数后:
1 2
| sudo systemctl daemon-reload sudo systemctl restart gunicorn
|
8. 常见问题与排错
静态文件返回 404
1 2 3 4 5 6 7
| python manage.py shell -c "from django.conf import settings; print(settings.STATIC_ROOT)"
python manage.py collectstatic --noinput
|
CORS 跨域错误
生产环境要明确指定允许的域名,不能用通配符 *:
1 2 3 4 5
| CORS_ALLOWED_ORIGINS = [ "https://yourdomain.com", ]
|
微信 OAuth 登录不可用
微信网页授权需要满足以下条件,缺一不可:
- 域名已完成 ICP 备案
- 微信公众平台已完成 域名验证(JS 安全域名、授权回调域名)
- 服务器必须支持 HTTPS
- 公众号需为 已认证的服务号(订阅号没有网页授权接口)
Gunicorn 请求超时
PDF 合格证生成接口可能耗时较长,需要适当增加超时:
1 2 3 4 5 6
| --timeout 120
proxy_read_timeout 120s; proxy_connect_timeout 10s;
|
部署后服务无法启动
1 2 3 4
| sudo systemctl status gunicorn sudo journalctl -u gunicorn --no-pager -n 50 tail -100 /var/log/gunicorn/error.log
|
系列文章回顾
至此,”一物一码”合格证查询系统的全部技术栈已经讲完。让我们回顾一下五篇文章的脉络:
| 篇目 |
主题 |
核心内容 |
| 第一篇 |
数据模型设计 |
产品、批次、合格证、二维码的多层数据关系;Django Model 设计与数据库约束 |
| 第二篇 |
后端 API 开发 |
Django REST Framework 接口设计;批量生成合格证;防伪签名与验签流程 |
| 第三篇 |
前端双端开发 |
管理后台(Admin Panel)+ 公众查询端(Cert View);Vue/React SPA 架构 |
| 第四篇 |
二维码与防伪机制 |
Ed25519 数字签名;JWS 格式编码;密钥轮换策略;签名验证全流程 |
| 第五篇 |
云服务器部署实战 |
Ubuntu + Nginx + Gunicorn 部署;SSL 配置;数据库升级;运维监控 |
从数据模型到线上运行,这套系统覆盖了一个典型 Web 应用的完整生命周期。其中最有价值的设计决策包括:
- Ed25519 签名防伪:让每张合格证都具有密码学级别的不可伪造性
- 双前端架构:管理端和公众端分离,各自独立部署和迭代
- 密钥轮换机制:保证系统长期运行的安全性
希望这个系列能帮助你理解”一物一码”场景的技术全貌。现在,去把你的合格证系统部署上线吧!