Skip to content

七、生产环境部署 (Deployment)

本章提供完整的从零开始部署 TodoApp 后端服务的操作手册,适用于 Ubuntu 24.04 VPS 环境。


7.1 服务器要求

项目最低要求推荐配置
操作系统Ubuntu 22.04 LTSUbuntu 24.04 LTS
CPU1 核1 核
内存512 MB1 GB
磁盘10 GB20 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.log

7.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 32

7.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-nginx

7.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] 启动成功,监听 ::8080

7.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 nginx

7.7 配置 QQ 邮箱 SMTP 邮件服务

TodoApp 的密码重置功能通过 QQ 邮箱的 SMTP 服务发送验证码邮件。相比第三方邮件 API,直接使用 QQ 邮箱 SMTP 无需注册额外服务、无需绑定域名,个人项目零成本即可上手。后端通过 mailer 包连接 QQ SMTP 服务器(smtp.qq.com:465,SSL)发信。

开启 SMTP 服务并获取授权码

  1. 登录 QQ 邮箱网页版设置账号(新版界面为 设置账户
  2. 找到「POP3/IMAP/SMTP/Exchange/CardDAV/CalDAV 服务」,开启 IMAP/SMTP 服务
  3. 按提示用手机发送短信完成验证,QQ 邮箱随后生成一串 授权码(16 位字母)
  4. 复制这串授权码,它就是 .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-run

7.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.log

SSL 证书管理

bash
# 查看证书有效期
sudo certbot certificates

# 手动续期
sudo certbot renew

7.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 分钟)后重试