Appearance
七、生产环境部署 (Deployment)
本章提供完整的从零开始部署 TodoApp 后端服务的操作手册,适用于 Ubuntu 24.04 VPS 环境。
7.1 服务器要求
| 项目 | 最低要求 | 推荐配置 |
|---|---|---|
| 操作系统 | Ubuntu 22.04 LTS | Ubuntu 24.04 LTS |
| CPU | 1 核 | 1 核 |
| 内存 | 512 MB | 1 GB |
| 磁盘 | 10 GB | 20 GB |
| 网络 | IPv4 公网地址 | IPv4 + IPv6 双栈 |
| 域名 | 必须(申请 SSL 证书需要) | 已解析的二级域名 |
7.2 目录结构
~/todo_backend/
├── bin/
│ └── server.dart ← 后端入口
├── lib/ ← 后端源码
├── Dockerfile
├── docker-compose.yml
├── pubspec.yaml
├── .env ← 敏感配置(不进仓库)
├── .env.example ← 配置模板(进仓库)
└── logs/ ← 日志文件目录(不进仓库)
└── server_YYYY-MM-DD.log7.3 环境变量配置清单
在 ~/todo_backend/ 目录下创建 .env 文件,参考以下模板填写:
env
# ── 服务器 ────────────────────────────────────────────────
PORT=8080
HOST=::
# ── JWT 认证 ──────────────────────────────────────────────
JWT_SECRET=<用 openssl rand -hex 32 生成>
ACCESS_TOKEN_EXPIRE_HOURS=1
REFRESH_TOKEN_EXPIRE_DAYS=30
# ── PostgreSQL ────────────────────────────────────────────
DB_NAME=todo_db
DB_USER=todo_user
DB_PASSWORD=<强密码>
# ── 邮件服务(QQ 邮箱 SMTP)───────────────────────────────
SMTP_USER=<你的 QQ 邮箱,如 12345678@qq.com>
SMTP_PASS=<QQ 邮箱 SMTP 授权码,非登录密码>
# ── 日志查看接口认证 ──────────────────────────────────────
LOG_USER=<管理员用户名>
LOG_PASS=<强密码>
LOG_SECRET=<用 openssl rand -hex 32 生成>生成随机密钥:
bash
openssl rand -hex 327.4 安装依赖环境
安装 Docker 和 Docker Compose
bash
# 更新包索引
sudo apt update
# 安装 Docker
curl -fsSL https://get.docker.com | sh
sudo usermod -aG docker $USER
newgrp docker
# 验证安装
docker --version
docker compose version安装 Nginx
bash
sudo apt install -y nginx
sudo systemctl enable nginx
sudo systemctl start nginx安装 Certbot(Let's Encrypt 证书)
bash
sudo apt install -y certbot python3-certbot-nginx7.5 Docker Compose 部署
克隆代码
bash
git clone <your-repo-url> ~/todo_backend
cd ~/todo_backend创建日志目录
bash
mkdir -p ~/todo_backend/logs构建并启动服务
bash
docker compose up -d --build查看运行状态
bash
# 查看容器状态
docker compose ps
# 实时查看后端日志
docker compose logs -f api
# 查看数据库日志
docker compose logs -f db正常启动后应看到:
todo_api | 2026-05-20 10:00:00.123 [INFO ] [Logger] ServerLogger 初始化完成
todo_api | 2026-05-20 10:00:00.456 [INFO ] [DB ] 数据库初始化完成
todo_api | 2026-05-20 10:00:00.789 [INFO ] [Server] 启动成功,监听 ::80807.6 Nginx 配置
创建配置文件
bash
sudo vim /etc/nginx/sites-available/todo-api写入以下内容(将 api.todo.wangpudev.com 替换为你的域名):
nginx
# HTTP → HTTPS 重定向
server {
listen 80;
listen [::]:80;
server_name api.todo.wangpudev.com;
location /.well-known/acme-challenge/ {
root /var/www/certbot;
}
location / {
return 301 https://$host$request_uri;
}
}
# HTTPS 主配置
server {
listen 443 ssl;
listen [::]:443 ssl;
server_name api.todo.wangpudev.com;
ssl_certificate /etc/letsencrypt/live/api.todo.wangpudev.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/api.todo.wangpudev.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384;
ssl_prefer_server_ciphers off;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 1d;
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Frame-Options DENY;
add_header X-Content-Type-Options nosniff;
# 普通 API 请求
location / {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
proxy_connect_timeout 60s;
proxy_send_timeout 60s;
proxy_read_timeout 60s;
}
# SSE 长连接(关闭缓冲,保持长连接)
location /events/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
proxy_set_header Connection '';
proxy_buffering off;
proxy_cache off;
proxy_read_timeout 86400s;
proxy_send_timeout 86400s;
gzip off;
}
# 日志查看接口
location /logs/ {
proxy_pass http://127.0.0.1:8080;
proxy_http_version 1.1;
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;
}
}启用配置
bash
sudo ln -s /etc/nginx/sites-available/todo-api /etc/nginx/sites-enabled/
sudo nginx -t
sudo systemctl reload nginx7.7 配置 QQ 邮箱 SMTP 邮件服务
TodoApp 的密码重置功能通过 QQ 邮箱的 SMTP 服务发送验证码邮件。相比第三方邮件 API,直接使用 QQ 邮箱 SMTP 无需注册额外服务、无需绑定域名,个人项目零成本即可上手。后端通过 mailer 包连接 QQ SMTP 服务器(smtp.qq.com:465,SSL)发信。
开启 SMTP 服务并获取授权码
- 登录 QQ 邮箱网页版 → 设置 → 账号(新版界面为 设置 → 账户)
- 找到「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务」,开启 IMAP/SMTP 服务
- 按提示用手机发送短信完成验证,QQ 邮箱随后生成一串 授权码(16 位字母)
- 复制这串授权码,它就是
.env里的SMTP_PASS——注意不是你的 QQ 登录密码
填入 .env
env
SMTP_USER=12345678@qq.com # 你的 QQ 邮箱地址(发件账号)
SMTP_PASS=xxxxxxxxxxxxxxxx # 上一步获取的 16 位授权码SMTP 服务器地址(
smtp.qq.com)与端口(465,SSL)已在后端Config中内置默认值,一般无需在.env中额外配置;如需覆盖,可自行加入SMTP_HOST/SMTP_PORT。
发件地址与邮件内容
发件人固定使用 SMTP_USER 本身,显示名为「待办应用」。
注意:QQ SMTP 要求
from地址与登录账号完全一致,因此发件地址不可自定义,否则服务器会直接拒信。
邮件主题、正文(纯文本 + HTML 双版本)在后端 lib/utils/email_util.dart 中定义,验证码 5 分钟内有效。如需自定义邮件样式,编辑该文件的 _buildEmailText / _buildEmailHtml 方法。
验证邮件功能
部署完成后,在应用的「忘记密码」页面输入你的注册邮箱,点击发送验证码,确认能正常收到邮件。
如果收不到,检查以下几点:
bash
# 查看后端日志中的邮件发送记录
docker compose logs api | grep -i email[Email] 邮件发送失败 code=... msg=...:多为授权码填错或未开启 SMTP 服务,重新核对SMTP_PASS后重启容器- QQ 对发信频率有风控,短时间大量发送可能被临时限制,稍后再试
- 邮件进了垃圾箱:个人邮箱直发的常见现象,将发件地址加入收件方白名单即可
7.8 申请 SSL 证书
确保域名已解析到 VPS 的公网 IP,然后执行:
bash
sudo certbot --nginx -d api.todo.wangpudev.com按提示操作,选择强制重定向 HTTP 到 HTTPS。证书申请成功后 Certbot 会自动修改 Nginx 配置。
验证自动续期:
bash
sudo certbot renew --dry-run7.9 客户端构建
Windows 桌面端
powershell
flutter build windows --release产物位于 build\windows\x64\runner\Release\,将整个 Release 文件夹打包分发即可。
Android APK
针对 arm64 架构单独打包,体积更小(约为全架构包的 1/3):
powershell
flutter build apk --release --target-platform android-arm64产物位于 build\app\outputs\flutter-apk\app-arm64-v8a-release.apk。
注意:Release 包需要在
AndroidManifest.xml中声明INTERNET权限,否则网络请求全部失败。Debug 模式会自动注入此权限,Release 不会。
7.10 日常运维命令速查
后端服务管理
bash
# 查看所有容器状态
docker compose ps
# 重启后端服务
docker compose restart api
# 更新代码后重新构建并启动
docker compose down
docker compose build
docker compose up -d
# 查看实时日志(Ctrl+C 退出)
docker compose logs -f api
# 查看最近 100 行日志
docker compose logs --tail=100 api数据库管理
bash
# 进入数据库容器
docker exec -it todo_db psql -U todo_user -d todo_db
# 查看所有表
\dt
# 退出
\q日志管理
bash
# 查看今天的日志文件
ls ~/todo_backend/logs/
# 实时监控日志文件
tail -f ~/todo_backend/logs/server_$(date +%Y-%m-%d).log
# 搜索 ERROR 级别日志
grep "ERROR" ~/todo_backend/logs/server_$(date +%Y-%m-%d).log
# 通过接口查看日志(需要替换 TOKEN)
curl -u 'LOG_USER:LOG_PASS' \
'https://api.todo.wangpudev.com/logs/?level=error&limit=50'Nginx 管理
bash
# 检查配置语法
sudo nginx -t
# 重载配置(不中断服务)
sudo systemctl reload nginx
# 查看 Nginx 错误日志
sudo tail -f /var/log/nginx/error.logSSL 证书管理
bash
# 查看证书有效期
sudo certbot certificates
# 手动续期
sudo certbot renew7.11 Web 运维入口:监控后台与日志查看
后端内置两个浏览器可直接打开的 Web 控制台,均由后端服务渲染,经上文 Nginx 的 location / 反向代理透传,无需额外的 Nginx 配置:
| 入口 | 地址 | 用途 |
|---|---|---|
| 运维监控后台 | https://api.todo.wangpudev.com/dashboard/ | 在线设备、注册用户与设备总表、VPS 内存负载 |
| 服务端日志查看 | https://api.todo.wangpudev.com/logs/ | 按级别/关键字查看服务端结构化日志 |
认证
两个入口都用 Basic Auth:首次访问弹出浏览器认证框,账号密码即 .env 中的 LOG_USER / LOG_PASS;认证成功后各自种一个 7 天有效的签名 Cookie(用 LOG_SECRET 签名),期间免重复输入。两者 session 相互独立,且都带失败限速(同一 IP 连续失败 5 次锁定 15 分钟)。
隐私说明:监控后台绝不展示任何待办内容(明文或密文),显示的 IP 均经
maskIp()脱敏(IPv4 仅保留前两段)。详见 第十三章:运维监控后台与设备管理。
排障
- 打不开 / 一直弹认证框:确认
.env已正确设置LOG_USER/LOG_PASS/LOG_SECRET并重启容器 - 被锁定返回 429:等待锁定时间结束(最长 15 分钟)后重试