部署文档
本文档对应 Word 交付文档《MCS信息安全传输控制软件 部署文档》(文件编号 IIE-MCS-ISTCS-DEP,项目编号 IIE-MCS-ISTCS)的全部章节内容,可直接誊录入正式文档。
项目:风云四号02批气象卫星工程C星地面系统 / MCS信息安全传输控制软件 承担部门:第二研究室 / 中国科学院信息工程研究所
部署实施快速导航
- 首次实施:按章节顺序执行,重点 三、后台 Python 服务部署 → 四、前台页面部署 → 五、Nginx → 八、部署验证;
- 已有环境更新/回滚:直接参阅 九、回滚与更新策略;
- 部署完成后交维:日常操作与巡检见 用户手册;
- 第三方业务系统对接本环境:业务后端必须与本系统共享 Redis,见 2.4 业务系统并设要求 与 业务系统接入指南。
一、文档概述
1.1 文档目的
本文档描述 MCS 信息安全传输控制软件(以下简称"本软件")在国家卫星气象中心 DMZ 区 MCS 服务器上的完整部署流程,包括部署架构、软硬件与网络环境准备、后台 Python 服务部署、前台页面部署、反向代理与 HTTPS 配置、进程守护、日志监控、部署验证、回滚与更新策略以及常见故障排查方法,为现场实施与运维人员提供可直接操作的标准化部署依据。
1.2 适用范围
本文档适用于以下软件组件的部署与运维:
- 后台服务:qxj-backend-admin,基于 Django 4.2 + DRF 的认证与管理后端,提供 NSP-SM(SM2withSM3 签名)Token 签发、SM2 四步握手、SM4-GCM 加密帧传输、设备/用户/角色/围栏/时间规则管理等接口,接口前缀
/api/v3/; - 前台管理端:qxj-frontend-admin,基于 Vue 3 + Vite 的管理端 Web(含 PC 与移动端两套 UI);
- 配套组件:MySQL 8(业务数据库)、Redis 5+(缓存 / Token 黑名单 / 验证码 / SM2 会话)、Nginx(反向代理与 HTTPS 终结)、Gunicorn(WSGI 服务);
- 鸿蒙 APP 分发站(apps.nsp.ac.cn,纯静态站点,Nginx 托管,部署方式在附录中简要说明)。
部署目标环境为国家卫星气象中心 DMZ 区 MCS 服务器(Linux),兼顾主站与灾备站的双站点部署模式。
1.3 读者对象
- 现场部署实施工程师;
- 系统运维与安全管理员;
- 软件测试与验收人员;
- 项目管理与配置管理人员。
1.4 术语与缩写
| 术语/缩写 | 说明 |
|---|---|
| MCS | 卫星地面系统监测控制分系统(Monitor and Control Subsystem) |
| DRF | Django REST framework,Django 的 REST 接口开发框架 |
| NSP-SM | 本软件自定义国密 Token 机制,采用 SM2withSM3 非对称签名 |
| SM2 / SM3 / SM4 | 国家商用密码算法:椭圆曲线公钥算法 / 密码杂凑算法 / 分组对称算法 |
| SM4-GCM | SM4 算法的 GCM 工作模式,用于业务数据加密帧传输 |
| WSGI | Python Web 服务器网关接口,本软件经 Gunicorn 以 WSGI 方式运行 |
| SPA | 单页应用(Single Page Application),前端 history 路由需回退至 index.html |
| DMZ | 隔离区(Demilitarized Zone),对外提供服务的缓冲区网络 |
| venv | Python 虚拟环境,用于隔离项目依赖 |
| pnpm | 前端包管理器(Node.js 生态) |
二、部署架构与环境准备
2.1 整体部署架构图
本软件部署于国家卫星气象中心 DMZ 区 MCS 服务器。Pad 终端(鸿蒙安全浏览器 / 专用设备)通过互联网经边界防火墙访问 DMZ 区 Nginx 统一入口(HTTPS),Nginx 将 /api/ 请求反向代理至本机 Gunicorn(Django 应用),静态页面由 Nginx 直接托管(或采用前后端一体化方式由 Gunicorn/WhiteNoise 提供)。Gunicorn 访问 MySQL(业务数据)与 Redis(缓存/黑名单/会话)。主站与灾备站结构相同,数据库做主从同步。
2.2 软硬件环境要求
服务器硬件建议(单机,主/备同配置):
| 项目 | 最低配置 | 建议配置 |
|---|---|---|
| CPU | 4 核 | 8 核及以上 |
| 内存 | 8 GB | 16 GB |
| 磁盘 | 100 GB(系统+应用+日志) | 200 GB SSD,日志分区独立 |
| 网络 | 千兆网卡,DMZ 区固定 IP | 同左 |
软件环境清单:
| 软件 | 版本要求 | 用途 |
|---|---|---|
| 操作系统 | CentOS 7 / 银河麒麟 V10 / Ubuntu 20.04+ 等 Linux | 运行环境 |
| Python | 3.8 及以上(建议 3.11) | 后台服务运行时 |
| Django / DRF | Django 4.2 / djangorestframework 3.15(随 requirements.txt 锁定) | Web 框架 |
| Gunicorn | 23.0.0(随 requirements.txt) | 生产 WSGI 服务器 |
| MySQL | 8.0(生产建议;SQLite 仅联调使用) | 业务数据库 |
| Redis | 5.0 及以上 | 缓存 / Token 黑名单 / 验证码 / SM2 会话 |
| Nginx | 1.18 及以上 | 反向代理、HTTPS 终结、静态托管 |
| Node.js | ≥ 20.19(仅构建前端时需要) | 前端构建 |
| pnpm | 9.x / 10.x(仅构建前端时需要) | 前端包管理 |
| Supervisor 或 systemd | 系统自带 | 进程守护 |
2.3 网络与端口规划
| 端口 | 协议 | 绑定地址 | 用途 | 对外开放 |
|---|---|---|---|---|
| 443 | TCP | 0.0.0.0 | Nginx HTTPS 统一入口(Pad/浏览器访问) | 是(边界防火墙放行) |
| 80 | TCP | 0.0.0.0 | HTTP 强制跳转 HTTPS | 是 |
| 8000 | TCP | 127.0.0.1 | Gunicorn WSGI 服务(仅本机,经 Nginx 反代) | 否 |
| 3306 | TCP | 127.0.0.1 / 内网 | MySQL 数据库 | 否(主从同步走内网) |
| 6379 | TCP | 127.0.0.1 | Redis | 否 |
| 22 | TCP | 内网管理地址 | SSH 运维 | 仅运维网段 |
说明:开发/联调环境后端使用 4607 端口(甲方联调端口);正式生产统一为 Nginx 443 入口 + 后端 127.0.0.1:8000。若采用"gunicorn 直接 TLS"的轻量部署方式(无 Nginx),则后端端口可直接对外,详见 5.5 节说明。
2.4 业务系统并设与 Redis 共享要求
本系统交付后,MCS 业务后台及第三方业务系统需与本系统并设对接。部署架构必须满足以下硬性要求,否则业务侧无法完成 token 校验与会话密钥读取。
1)业务后端必须能访问同一 Redis 实例。 SM2 密钥协商成功后,会话密钥(SM4)由认证服务器写入 Redis;业务后端只持有服务器 SM2 公钥,通过后端 Python SDK(qxj-backend-sdk)从 Redis 只读查询会话密钥并验签 token。因此部署时须:
- 将业务后端所在主机加入 Redis 访问白名单/安全组(生产 Redis 仅监听内网,禁止暴露公网);
- 向业务方提供
REDIS_URL(建议为其创建独立 Redis 账号,ACL 仅授予只读与指定键空间权限); - 告知键前缀口径:缓存键统一前缀
qxj:1:,会话密钥物理键为qxj:1:sm2:device_session_key:{device_id}。
2)业务方网络可达性。 业务前端(含 Pad WebView 页面)能访问 Nginx 统一入口;业务后端与 MCS 服务器间网络延迟建议 ≤ 50 ms,无 HTTPS 中间人替换(证书链完整)。
3)对接资产交付。 向业务方交付:服务器 SM2 公钥、前端 JS SDK、后端 Python SDK(wheel)、教学 Demo 及 业务系统接入指南。业务方只需开发自有前后端,不重复实现认证与密码逻辑。
4)多业务方隔离。 多个业务系统并设时,其页面域名须逐台加入 APP 服务器配置的 JSBridge 白名单(不在白名单的页面调用 Bridge 一律返回 code=-60);Redis 侧可通过 ACL 限制各业务方仅可读取会话键,禁止互访业务缓存。
三、后台 Python 服务部署
3.1 代码获取与更新
方式一:Git 克隆(推荐)
# 部署目录约定:/opt/qxj
mkdir -p /opt/qxj && cd /opt/qxj
git clone <仓库地址>/qxj-backend-admin.git
cd qxj-backend-admin
# 切到发布标签(交付基线),例如:
git checkout v1.0.0
# 或切到 release 分支
git checkout release2
3
4
5
6
7
8
方式二:压缩包上传(内网无 Git 服务时)
# 将 qxj-backend-admin-v1.0.0.tar.gz 上传至 /opt/qxj 后:
cd /opt/qxj
tar -zxf qxj-backend-admin-v1.0.0.tar.gz
mv qxj-backend-admin-v1.0.0 qxj-backend-admin2
3
4
分支与版本标签说明:main/master 为开发主干;release 为待发布分支;每次交付打 vX.Y.Z 标签作为部署基线。生产部署一律检出标签,不跟踪主干。
3.2 Python 环境准备
以 Python 3.11 + venv 为例(CentOS 7 需先安装编译工具与 Python 3.11;麒麟/Ubuntu 用对应包管理器):
# CentOS 7(通过 SCL 或源码编译安装 python3.11;麒麟可直接 yum/dnf 安装)
sudo yum install -y gcc openssl-devel bzip2-devel libffi-devel zlib-devel
# 麒麟 V10 / Ubuntu:
# sudo dnf install -y python3.11 python3.11-devel gcc 或
# sudo apt install -y python3.11 python3.11-venv python3.11-dev gcc
cd /opt/qxj/qxj-backend-admin
python3.11 -m venv venv
source venv/bin/activate
python -V # 确认 Python 3.11.x
pip install --upgrade pip2
3
4
5
6
7
8
9
10
11
3.3 依赖包安装
source /opt/qxj/qxj-backend-admin/venv/bin/activate
cd /opt/qxj/qxj-backend-admin
pip install -r requirements.txt2
3
requirements.txt 主要锁定依赖:Django 4.2.25、djangorestframework 3.15.2、django-filter 24.3、drf-spectacular 0.29.0、django-cors-headers 4.4.0、django-redis 5.4.0、python-dotenv、whitenoise 6.7.0、gunicorn 23.0.0、阿里云验证码/短信 SDK,以及本地 wheel ./vendor/qxj_backend_sdk-0.1.0-py3-none-any.whl(国密 SDK,必须保留 vendor 目录)。
离线安装(内网无外网时):项目自带 vendor/ 离线 wheel 目录;如需完整离线依赖,可提前在外网同版本机器执行 pip download -r requirements.txt -d wheels/,将 wheels/ 拷贝至服务器后:
pip install --no-index --find-links=./wheels -r requirements.txt使用 MySQL 时需额外安装驱动(requirements 默认 SQLite,MySQL 为可选项):
# 系统依赖:CentOS: yum install mysql-devel;Ubuntu: apt install libmysqlclient-dev(或 libpq-dev 之于 PostgreSQL)
pip install mysqlclient2
常见系统依赖:gcc(编译 wheel)、openssl-devel、mysql-devel(仅 MySQL 驱动需要)。
3.4 配置文件准备
环境变量采用两级加载(config/settings/base.py):.env(公共)+ .env.production(生产专属,同名变量覆盖 .env)。生产进程(gunicorn,wsgi.py 默认 production 配置)启动时自动加载。
cd /opt/qxj/qxj-backend-admin
cp .env.example .env
cp .env.production.example .env.production2
3
.env 关键项(示例,生产必须替换为强随机值,缺失或占位值时 production 配置拒绝启动):
# Django 框架密钥:python -c "import secrets; print(secrets.token_urlsafe(64))"
SECRET_KEY=请替换为64位以上随机字符串
# 数据库:生产 MySQL(DB_ENGINE=mysql 并安装 mysqlclient)
DB_ENGINE=mysql
DB_NAME=qxj_mcs
DB_USER=qxj
DB_PASSWORD=数据库强密码
DB_HOST=127.0.0.1
DB_PORT=3306
# Redis:缓存 + Token 黑名单 + 验证码 + SM2 会话
REDIS_URL=redis://:Redis强密码@127.0.0.1:6379/0
# SM2 私钥主密钥(64位hex):python -c "import secrets; print(secrets.token_hex(32))"
# ⚠️ 初始化服务器密钥后不可更改,否则数据库中加密存储的 SM2 私钥无法解密
QXJ_KEY_ENC_KEY=请替换为64位hex
# 限流(按需调整)
THROTTLE_ANON_RATE=60/min
THROTTLE_USER_RATE=600/min
THROTTLE_SMS_RATE=5/min
# 甲方内网短信网关(生产必须配置,否则验证码无法发送)
SMS_URL=http://10.0.66.235:8082/smsinterface/submit/msg
SMS_USERNAME=提交账户
SMS_PASSWORD=接口密码
SMS_TYPE=1
SMS_SIGNATURE=国家气象局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
.env.production 关键项:
# 允许访问的主机/IP(逗号分隔),漏配返回 400 Bad Request (DisallowedHost)
ALLOWED_HOSTS=localhost,127.0.0.1,mcs-qxj.nsmc.example.cn,10.x.x.x
# CORS 来源(生产强制非空;同源部署时至少填一个占位来源)
CORS_ALLOWED_ORIGINS=https://mcs-qxj.nsmc.example.cn
# Nginx 单层反代:信任 X-Forwarded-Proto
TRUSTED_PROXY_COUNT=1
SECURE_PROXY_SSL_HEADER_HTTP=X-Forwarded-Proto
SECURE_PROXY_SSL_HEADER_VALUE=https
# 登录方式:生产推荐仅专用设备登录
ALLOW_JSON_LOGIN=false
ALLOW_DEVICE_LOGIN=true
VERIFY_INNER_HMAC=true2
3
4
5
6
7
8
9
10
11
12
13
14
15
Redis 键空间约定(部署与联调必须对齐)
以下口径以当前工程代码为准,部署文档与业务方对接时不得自行改写:
- 键前缀为代码内固定值,不是环境变量。
config/settings/base.py中CACHES.default固定KEY_PREFIX='qxj'、VERSION=1,后端实际拼接的键前缀为qxj:1:;.env/.env.production中没有也不应添加这两项配置。Redis 连接地址唯一通过REDIS_URL配置。 - 序列化全局禁用 pickle。 缓存使用 django-redis 的
JSONSerializer(安全要求 DJ-11),业务方不得通过写入 pickle 对象触发反序列化风险。 - 关键物理键一览:
| 用途 | 物理键 | 写入方 |
|---|---|---|
| SM2 握手会话密钥 | qxj:1:sm2:device_session_key:{device_id} | 认证服务器(握手成功时) |
| Token 黑名单 | token:blacklist:{jti} | 登出/强制下线,TTL 为剩余有效期(最小 60s) |
| 会话撤销标记 | token:session_revoked:{sid} | 管理员强制会话失效 |
| Django 通用缓存 | qxj:1:<缓存名> | 后端框架(默认 TTL 300s) |
- 用户级 token 批量失效不依赖 Redis,依靠用户表
token_version(token 中tvclaim)实现;教学 Demo(mock-demo)使用独立前缀qxj:mock:与 db=1,与生产键空间隔离。
国密证书/密钥文件:服务器 SM2 密钥对由初始化命令生成并加密入库(见 3.5);HTTPS TLS 证书(server.crt / server.key 或 CA 签发的 fullchain 证书与私钥)统一放置于 /opt/qxj/qxj-backend-admin/certs/,权限设为 600,并在 Nginx 配置(5.5 节)或 gunicorn 启动参数(3.7 节)中指向。
3.5 数据库初始化
创建数据库与用户(MySQL 8):
-- 以 root 登录 mysql 后执行
CREATE DATABASE qxj_mcs DEFAULT CHARACTER SET utf8mb4 COLLATE utf8mb4_general_ci;
CREATE USER 'qxj'@'localhost' IDENTIFIED BY '数据库强密码';
GRANT ALL PRIVILEGES ON qxj_mcs.* TO 'qxj'@'localhost';
FLUSH PRIVILEGES;2
3
4
5
执行迁移与初始化:
source /opt/qxj/qxj-backend-admin/venv/bin/activate
cd /opt/qxj/qxj-backend-admin
# 建表(生产配置)
python manage.py migrate --settings=config.settings.production
# 一键初始化:5 个角色、超管 admin/nsp、测试账号、4 个 GCJ-02 围栏、10 条时间规则,
# 并生成服务器 SM2 密钥对(私钥经 QXJ_KEY_ENC_KEY 加密入库)
python tools/setup.py init
# 如需手工创建管理员:
python manage.py createsuperuser --settings=config.settings.production
# 交付基线默认超管:admin / Nsp123456!(首次登录后必须修改)2
3
4
5
6
7
8
9
10
11
12
13
tools/setup.py 还提供 migrate / status / qrcode / gen-cert 等子命令:status 检查初始化状态,gen-cert <IP> 生成含指定 IP SAN 的自签 TLS 证书(联调用)。
3.6 静态文件收集
Django Admin 后台(/admin/django/)样式与 DRF 页面静态资源需要收集:
python manage.py collectstatic --noinput --settings=config.settings.production产物默认收集到项目 staticfiles/ 目录,生产由 WhiteNoise 或 Nginx 提供。纯 API + 一体化前端部署也必须执行本步骤。
3.7 启动后台服务
开发测试方式(仅限调试,不得用于生产):
python manage.py runserver 0.0.0.0:4607生产方式(Gunicorn,经 Nginx 反代,绑定回环地址):
cd /opt/qxj/qxj-backend-admin
source venv/bin/activate
mkdir -p logs
DJANGO_SETTINGS_MODULE=config.settings.production gunicorn config.wsgi:application \
--bind 127.0.0.1:8000 \
--workers 4 \
--timeout 120 \
--pid logs/gunicorn.pid \
--access-logfile logs/gunicorn-access.log \
--error-logfile logs/gunicorn-error.log \
--daemon2
3
4
5
6
7
8
9
10
11
12
生产方式(Gunicorn 直接 TLS,无 Nginx 的轻量部署):
DJANGO_SETTINGS_MODULE=config.settings.production gunicorn config.wsgi:application \
--bind 0.0.0.0:4607 \
--certfile certs/server.crt \
--keyfile certs/server.key \
--workers 4 \
--timeout 120 \
--pid logs/gunicorn.pid \
--access-logfile logs/gunicorn-access.log \
--error-logfile logs/gunicorn-error.log \
--daemon2
3
4
5
6
7
8
9
10
参数说明:
| 参数 | 说明 |
|---|---|
--bind 127.0.0.1:8000 | 监听地址。经 Nginx 反代时绑回环地址;直接对外(TLS 模式)绑 0.0.0.0 |
--workers 4 | worker 进程数,建议 CPU核数×2+1,内网轻量部署 2~4 个 |
--timeout 120 | 请求超时秒数(默认 30s 偏短,握手/大报文接口需调大) |
--pid | 主进程 PID 文件,便于停止与平滑重启(kill -HUP) |
--certfile / --keyfile | TLS 证书与私钥(gunicorn 直接 TLS 模式使用;Nginx 反代模式不需要) |
--access-logfile / --error-logfile | 访问/错误日志路径;业务日志另在 logs/app.log、logs/error.log |
停止 / 平滑重启:
kill "$(cat logs/gunicorn.pid)" # 停止
kill -HUP "$(cat logs/gunicorn.pid)" # 平滑重启(逐个重启 worker)2
四、前台页面部署
4.1 前端代码获取
cd /opt/qxj
git clone <仓库地址>/qxj-frontend-admin.git
cd qxj-frontend-admin
git checkout v1.0.0 # 与后端同基线的发布标签2
3
4
若采用"构建产物离线交付"方式,直接获取 dist 压缩包并上传服务器解压即可,跳过 4.2 节。
4.2 构建与编译
要求 Node.js ≥ 20.19 与 pnpm(包管理器必须用 pnpm,不要用 npm/yarn 直接装依赖,避免锁文件不一致):
# Node 安装(以 nvm 为例)
curl -o- https://raw.githubusercontent.com/nvm-sh/nvm/v0.40.3/install.sh | bash
source ~/.bashrc
nvm install 20 && nvm use 20
# pnpm 安装
corepack enable && corepack prepare pnpm@latest --activate
# 或:npm i -g pnpm
cd /opt/qxj/qxj-frontend-admin
pnpm install2
3
4
5
6
7
8
9
10
11
两种构建命令的区别:
pnpm build # 普通构建,产物输出到本项目 dist/,用于 Nginx 独立托管
pnpm build:backend # 一体化构建:vue-tsc 类型检查 + vite build,
# 产物直接输出到 ../qxj-backend-admin/frontend_dist(--emptyOutDir 先清空),
# 由后端 Django/WhiteNoise 直接提供,无需单独前端静态服务器2
3
4
内网无外网时,可在外网同版本 Node 机器执行
pnpm deploy或拷贝整个node_modules/ 使用 pnpm 离线 store 后构建。
4.3 静态资源部署
方式 A:Nginx 托管 dist(推荐用于正式生产)
cd /opt/qxj/qxj-frontend-admin
pnpm build
sudo mkdir -p /var/www/qxj
sudo cp -r dist /var/www/qxj/dist-v1.0.0
sudo ln -sfn /var/www/qxj/dist-v1.0.0 /var/www/qxj/dist # 软链接,便于版本切换2
3
4
5
方式 B:与后端一体化部署(frontend_dist)
在前端项目执行 pnpm build:backend,产物写入 qxj-backend-admin/frontend_dist/(该目录不入库)。后端已通过 WhiteNoise 提供该目录静态资源,并为 Vue createWebHistory 路由配置 index.html 回退;访问后端端口根路径即打开管理端,/api/v3/ 接口按原路由工作,前端 API 使用同源 /api/v3/,无需配置主机地址。一体化方式可与 Nginx 共存(Nginx 继续反代并缓存静态资源)。
方式 C:鸿蒙 APP 分发站(apps.nsp.ac.cn)
纯静态站点,npm run build 后将产物上传至宝塔站点目录,Nginx 托管 + try_files ... /index.html 兜底即可,不涉及后端服务。
4.4 前端路由配置(SPA try_files)
前端使用 Vue Router history 模式,Nginx 必须配置回退,否则刷新非根路径 404:
location / {
root /var/www/qxj/dist;
index index.html;
try_files $uri $uri/ /index.html; # SPA history 路由兜底
}2
3
4
5
4.5 前端环境变量配置
前端环境变量文件:.env(公共)、.env.production(pnpm build 时加载)。生产构建前确认:
VITE_BASE_URL = /
VITE_API_URL = / # 同源部署:请求走 /api/v3/ 由 Nginx 反代
VITE_DROP_CONSOLE = true
VITE_ENABLE_MOBILE_UI = true
VITE_USE_JSBRIDGE = true # 鸿蒙安全浏览器 JSBridge 登录模式
VITE_ALLOW_JSON_LOGIN = false # 与后端 ALLOW_JSON_LOGIN 保持一致
VITE_REQUIRE_DEDICATED_DEVICE = true
VITE_APP_TITLE = 风云四号C星管理系统2
3
4
5
6
7
8
若前端独立域名部署(跨源),VITE_API_URL 填后端完整地址(如 https://mcs-qxj-api.nsmc.example.cn/),并在后端 .env.production 的 CORS_ALLOWED_ORIGINS 中加入该前端来源。
五、反向代理与负载均衡(Nginx 示例)
5.1 Nginx 安装与基本配置
# CentOS 7
sudo yum install -y epel-release && sudo yum install -y nginx
# 麒麟 V10
sudo dnf install -y nginx
# Ubuntu
sudo apt install -y nginx
sudo systemctl enable --now nginx
nginx -t # 配置语法检查2
3
4
5
6
7
8
9
5.2 代理 Python 后台
upstream qxj_backend {
server 127.0.0.1:8000;
keepalive 32;
}
# 在 server 块内:
location /api/ {
proxy_pass http://qxj_backend;
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; # 供 Django 判断 HTTPS(配合 TRUSTED_PROXY_COUNT=1)
proxy_read_timeout 120s;
}2
3
4
5
6
7
8
9
10
11
12
13
14
WebSocket 备注:本软件 v3 基线业务接口均为 HTTP 短连接,无 WebSocket 依赖;若后续版本引入,需追加:
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection "upgrade";2
3
5.3 托管前台静态文件
location / {
root /var/www/qxj/dist; # 指向 pnpm build 产物(或软链接)
index index.html;
try_files $uri $uri/ /index.html;
}
# Django 后台 / collectstatic 静态资源(一体化部署时可选)
location /static/ {
alias /opt/qxj/qxj-backend-admin/staticfiles/;
expires 7d;
}2
3
4
5
6
7
8
9
10
11
5.4 统一访问入口(同一域名/端口)
前后端通过同一域名(443)对外:/ 走前端静态,/api/ 走 Gunicorn,浏览器无跨域问题(后端 CORS 仅需配置占位来源)。Pad 与浏览器统一入口:https://mcs-qxj.nsmc.example.cn/。
5.5 SSL 证书配置(HTTPS)
本项目调试与生产均要求 HTTPS(Pad 摄像头等能力要求安全上下文;生产配置强制 SSL 重定向 / HSTS / 安全 Cookie)。
server {
listen 80;
server_name mcs-qxj.nsmc.example.cn;
return 301 https://$host$request_uri; # HTTP 强制跳转 HTTPS
}
server {
listen 443 ssl;
server_name mcs-qxj.nsmc.example.cn;
# CA 签发证书(fullchain 合并文件);内网自签证书用法相同
ssl_certificate /opt/qxj/qxj-backend-admin/certs/server.crt;
ssl_certificate_key /opt/qxj/qxj-backend-admin/certs/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
# ... location 配置(见 5.2 / 5.3)
}2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
证书获取途径:① 甲方/单位 CA 签发的正式证书(推荐);② 内网自签证书(python -m tools.setup gen-cert <服务器IP> 生成,SAN 须包含访问用 IP/域名,Pad 首次访问需信任)。
六、进程守护与自动重启
6.1 使用 Supervisor
# CentOS: yum install supervisor;Ubuntu: apt install supervisor
sudo systemctl enable --now supervisord2
/etc/supervisord.d/qxj-backend.ini:
[program:qxj-backend]
command=/opt/qxj/qxj-backend-admin/venv/bin/gunicorn config.wsgi:application --bind 127.0.0.1:8000 --workers 4 --timeout 120
directory=/opt/qxj/qxj-backend-admin
environment=DJANGO_SETTINGS_MODULE="config.settings.production"
user=nginx
autostart=true
autorestart=true
startretries=3
redirect_stderr=true
stdout_logfile=/opt/qxj/qxj-backend-admin/logs/gunicorn-supervisor.log
stdout_logfile_maxbytes=50MB
stdout_logfile_backups=52
3
4
5
6
7
8
9
10
11
12
生效与管理:
sudo supervisorctl reread && sudo supervisorctl update
sudo supervisorctl start qxj-backend
sudo supervisorctl status qxj-backend
# 更新代码后:
sudo supervisorctl restart qxj-backend2
3
4
5
6.2 使用 Systemd(推荐,Linux 通用)
/etc/systemd/system/qxj-backend.service:
[Unit]
Description=QXJ MCS Backend (Django + Gunicorn)
After=network.target mysql.service redis.service
[Service]
Type=simple
User=nginx
Group=nginx
WorkingDirectory=/opt/qxj/qxj-backend-admin
Environment=DJANGO_SETTINGS_MODULE=config.settings.production
ExecStart=/opt/qxj/qxj-backend-admin/venv/bin/gunicorn config.wsgi:application \
--bind 127.0.0.1:8000 --workers 4 --timeout 120 \
--pid /opt/qxj/qxj-backend-admin/logs/gunicorn.pid \
--access-logfile /opt/qxj/qxj-backend-admin/logs/gunicorn-access.log \
--error-logfile /opt/qxj/qxj-backend-admin/logs/gunicorn-error.log
ExecReload=/bin/kill -HUP $MAINPID
Restart=always
RestartSec=5
[Install]
WantedBy=multi-user.target2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
sudo systemctl daemon-reload
sudo systemctl enable --now qxj-backend # 开机自启并立即启动
sudo systemctl status qxj-backend
sudo journalctl -u qxj-backend -f # 查看服务日志2
3
4
6.3 健康检查与探针
# 路由总览探针(经 Nginx):返回 2xx/3xx/4xx 均表示应用层存活
curl -sk -o /dev/null -w '%{http_code}\n' https://127.0.0.1/api/v3/
# 直连后端探针
curl -s -o /dev/null -w '%{http_code}\n' http://127.0.0.1:8000/api/v3/
# 可配置 cron 每分钟探测并异常时重启:
# * * * * * curl -s -o /dev/null -w '%{http_code}' http://127.0.0.1:8000/api/v3/ | grep -qE '^[234]' || systemctl restart qxj-backend2
3
4
5
6
7
8
七、日志与监控
7.1 后台日志输出与切割(logrotate)
日志文件(均位于 /opt/qxj/qxj-backend-admin/logs/):
gunicorn-access.log/gunicorn-error.log:Gunicorn 访问/错误日志;app.log/error.log:Django 业务日志与异常日志(production 配置落盘)。
/etc/logrotate.d/qxj-backend:
/opt/qxj/qxj-backend-admin/logs/*.log {
daily
rotate 30
compress
delaycompress
missingok
notifempty
copytruncate
dateext
}2
3
4
5
6
7
8
9
10
使用
copytruncate时无需通知进程重新打开日志文件;也可改用postrotate+kill -USR1 $(cat .../gunicorn.pid)方式。
7.2 Nginx 访问与错误日志
- 访问日志:
/var/log/nginx/access.log(含客户端 IP、URI、状态码、耗时,可用于审计); - 错误日志:
/var/log/nginx/error.log(反代 502/504 首先查这里)。
tail -f /var/log/nginx/access.log | grep ' /api/v3/'
tail -100 /var/log/nginx/error.log2
Nginx 自带 logrotate 策略(/etc/logrotate.d/nginx),一般无需修改。
7.3 简单的监控建议(进程、磁盘、内存)
# 进程与端口
ps -ef | grep -E 'gunicorn|nginx' | grep -v grep
ss -lntup | grep -E ':443|:8000|:3306|:6379'
# 磁盘(重点关注 /opt、/var/log、MySQL 数据目录)
df -h
# 内存与负载
free -h
uptime
# Redis 存活
redis-cli -a 'Redis强密码' ping # 期望 PONG
# MySQL 存活与连接数
mysqladmin -u qxj -p status2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
建议接入单位统一监控平台(Zabbix/Prometheus):监控 443 端口 HTTPS 探活、gunicorn worker 数、磁盘使用率 ≥ 80% 告警、logs/error.log 关键字告警。
八、部署验证与测试
8.1 后台接口测试(curl / Postman)
# 1) 路由总览(探活)
curl -sk -o /dev/null -w '%{http_code}\n' https://mcs-qxj.nsmc.example.cn/api/v3/
# 期望:200 或 401/403(应用层响应,非 000 连接失败)
# 2) HSTS 响应头(HTTPS 生效验证)
curl -skI https://mcs-qxj.nsmc.example.cn/ | grep -i strict-transport
# 期望:strict-transport-security: max-age=31536000; ...
# 3) JSON 登录接口(联调账号,仅当 ALLOW_JSON_LOGIN=true 时可用)
curl -sk -X POST https://mcs-qxj.nsmc.example.cn/api/v3/user/login/ \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"Nsp123456!"}'
# 期望:返回 token 字段(NSP-SM Token)
# 4) 带 token 访问管理接口
curl -sk -H "Authorization: Bearer <token>" \
https://mcs-qxj.nsmc.example.cn/api/v3/admin/users/
# 5) SM2 握手接口(Pad/专用设备通道,需国密 SDK 构造报文)
# POST /api/v3/keymgr/sm2/build_resp/ 、/api/v3/keymgr/sm2/build_token/2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
8.2 前台页面访问测试
- 浏览器访问
https://mcs-qxj.nsmc.example.cn/,应打开"风云四号C星管理系统"登录页; - 登录后进入首页/仪表盘;逐页打开用户、角色、设备、设备公钥、围栏、时间规则、安全日志、服务器密钥页面,数据加载正常;
- 任意子路由(如
/user)按 F5 刷新,页面正常(try_files 生效); - 移动端 UI:以窄屏(<768px)或 Pad 访问
/m/*路由,布局正确。
8.3 典型业务功能冒烟测试
| # | 冒烟项 | 验证方法 | 通过标准 |
|---|---|---|---|
| 1 | 用户登录 | 管理端输入账号密码登录 | 登录成功并跳转首页 |
| 2 | 设备注册 | 设备管理页新增设备(eqp_unique_identifier)并录入设备 SM2 公钥 | 设备状态流转正常,公钥 active 唯一 |
| 3 | 密钥协商 | Pad 触发 SM2 四步握手(/keymgr/sm2/build_resp/ → /build_token/) | 握手成功,签发 NSP-SM Token |
| 4 | 加密传输 | Pad 以 SM4-GCM 加密帧调用业务接口 | 服务端正常解密、业务返回正确,安全日志有记录 |
| 5 | 访问控制 | 越出围栏/时间规则时段登录 | 被拒绝并产生安全日志 |
| 6 | 退出登录 | 管理端退出 | Token 入黑名单,再次访问 401 |
九、回滚与更新策略
9.1 代码更新流程(拉取新代码 → 重启服务)
# 后端
cd /opt/qxj/qxj-backend-admin
git fetch --tags && git checkout v1.1.0 # 切到新标签
source venv/bin/activate
pip install -r requirements.txt # 依赖有变更时
python manage.py migrate --settings=config.settings.production
python manage.py collectstatic --noinput --settings=config.settings.production
sudo systemctl reload qxj-backend # 或 supervisorctl restart qxj-backend / kill -HUP $(cat logs/gunicorn.pid)
# 前端
cd /opt/qxj/qxj-frontend-admin
git fetch --tags && git checkout v1.1.0
pnpm install
pnpm build
sudo ln -sfn /var/www/qxj/dist-v1.1.0 /var/www/qxj/dist
# 一体化部署则:pnpm build:backend 后重载后端即可2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
9.2 数据库迁移的回滚方案
- 升级前备份:
mysqldump -u root -p qxj_mcs > qxj_mcs_$(date +%F).sql; - 迁移回退:Django 迁移可按 app 回退到指定版本,例如
python manage.py migrate users 0003 --settings=config.settings.production(0003 为升级前版本号,用python manage.py showmigrations查询); - 结构性破坏变更(删字段/改类型)不建议直接回退迁移,应恢复升级前数据库备份 + 回退代码标签;
- 回退完成后重启服务并复跑冒烟测试(8.3)。
9.3 前端版本快速切换方式(软链接)
前端每次构建发布到带版本号的独立目录,dist 为指向当前版本的软链接:
/var/www/qxj/
├── dist-v1.0.0/
├── dist-v1.1.0/
└── dist -> dist-v1.1.0 # 当前版本
# 回滚(秒级,无需重启 Nginx):
sudo ln -sfn /var/www/qxj/dist-v1.0.0 /var/www/qxj/dist2
3
4
5
6
7
一体化部署模式下,frontend_dist/ 由构建覆盖;回滚需重新执行旧标签的 pnpm build:backend 并 systemctl reload qxj-backend。
十、常见问题与故障排查(FAQ)
10.1 Python 依赖安装失败
| 现象 | 排查与解决 |
|---|---|
pip install 超时/失败(内网无外网) | 使用离线 wheel:pip install --no-index --find-links=./wheels -r requirements.txt;vendor 本地包 qxj_backend_sdk 必须在项目根目录执行安装 |
编译报错缺 Python.h / gcc | 安装系统依赖:CentOS yum install gcc python3-devel openssl-devel;Ubuntu apt install gcc python3-dev libffi-dev |
mysqlclient 编译失败 | CentOS yum install mysql-devel;Ubuntu apt install libmysqlclient-dev;或改用 pip install pymysql 并在 config/__init__.py 中 pymysql.install_as_MySQLdb() |
10.2 端口被占用
ss -lntup | grep -E ':8000|:443'
# 找到占用进程后:确认是否旧进程未退出
pkill -f "gunicorn config.wsgi"
# 或修改 --bind 端口 / Nginx listen 端口后重启2
3
4
10.3 静态文件 404
# Django Admin 样式丢失 / /static/ 404:DEBUG=False 后 Django 不再提供静态文件
python manage.py collectstatic --noinput --settings=config.settings.production
# 检查 Nginx /static/ 的 alias 路径是否指向 staticfiles/,路径末尾斜杠一致
# 前端资源 404:确认 dist 软链接指向、Nginx root 路径与文件权限(nginx 用户可读)
ls -l /var/www/qxj/dist
sudo -u nginx test -r /var/www/qxj/dist/index.html && echo OK2
3
4
5
6
10.4 数据库连接拒绝
# 确认 MySQL 运行与监听
systemctl status mysqld
ss -lnt | grep 3306
# 用应用账号测试连通性
mysql -u qxj -p -h 127.0.0.1 qxj_mcs -e 'select 1'
# 核对 .env 中 DB_HOST/DB_PORT/DB_USER/DB_PASSWORD;授权主机('qxj'@'localhost' 与 'qxj'@'%' 区别)
# Django 报错详情见 logs/error.log2
3
4
5
6
7
10.5 跨域问题(CORS)
- 浏览器控制台报
CORS header 'Access-Control-Allow-Origin' missing:确认后端.env.production的CORS_ALLOWED_ORIGINS包含前端来源(含协议与端口,如https://mcs-qxj.nsmc.example.cn),修改后重启后端; - 优先采用 5.4 节同源部署(Nginx 统一入口)避免跨域;生产配置启动时强制要求
CORS_ALLOWED_ORIGINS非空,无跨域需求也至少填一个占位来源。
10.6 前端刷新页面 404(路由问题)
- 原因:Vue Router history 模式下,刷新子路由时 Nginx 找不到对应物理文件;
- 解决:确认
location /中配置了try_files $uri $uri/ /index.html;(见 4.4 / 11.2);一体化部署模式后端已内置 index.html 回退,无需处理。
其他高频问题:400 Bad Request (DisallowedHost) → 访问用 IP/域名加入 ALLOWED_HOSTS 后重启;握手/登录 500 且日志有 redis 连接错误 → redis-cli -a '密码' ping 检查 Redis;改证书后浏览器报证书错误 → 清理浏览器站点数据/HSTS 缓存。
十一、附录
11.1 完整环境变量示例
.env(公共,生产参考值,密钥均须现场重新生成):
SECRET_KEY=替换为-token_urlsafe(64)-生成的随机串
DB_ENGINE=mysql
DB_NAME=qxj_mcs
DB_USER=qxj
DB_PASSWORD=替换为数据库强密码
DB_HOST=127.0.0.1
DB_PORT=3306
REDIS_URL=redis://:替换为Redis强密码@127.0.0.1:6379/0
QXJ_KEY_ENC_KEY=替换为-token_hex(32)-生成的64位hex
THROTTLE_ANON_RATE=60/min
THROTTLE_USER_RATE=600/min
THROTTLE_SMS_RATE=5/min
SMS_URL=http://10.0.66.235:8082/smsinterface/submit/msg
SMS_USERNAME=替换为网关账户
SMS_PASSWORD=替换为网关密码
SMS_TYPE=1
SMS_SIGNATURE=国家气象局
SMS_CODE_EXPIRE_SECONDS=60
SMS_PHONE_INTERVAL=60
ENABLE_DJANGO_ADMIN=true
EMAIL_ENABLED=false
CAPTCHA_ENABLED=false2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
.env.production(生产专属):
ALLOWED_HOSTS=localhost,127.0.0.1,mcs-qxj.nsmc.example.cn
CORS_ALLOWED_ORIGINS=https://mcs-qxj.nsmc.example.cn
TRUSTED_PROXY_COUNT=1
SECURE_PROXY_SSL_HEADER_HTTP=X-Forwarded-Proto
SECURE_PROXY_SSL_HEADER_VALUE=https
ALLOW_JSON_LOGIN=false
ALLOW_DEVICE_LOGIN=true
VERIFY_INNER_HMAC=true
LOGIN_LOCKOUT_SECONDS=900
ALLOW_LOGIN_WITHOUT_PHONE=true2
3
4
5
6
7
8
9
10
11.2 Nginx 完整配置文件样例
/etc/nginx/conf.d/qxj.conf:
upstream qxj_backend {
server 127.0.0.1:8000;
keepalive 32;
}
server {
listen 80;
server_name mcs-qxj.nsmc.example.cn;
return 301 https://$host$request_uri;
}
server {
listen 443 ssl;
server_name mcs-qxj.nsmc.example.cn;
ssl_certificate /opt/qxj/qxj-backend-admin/certs/server.crt;
ssl_certificate_key /opt/qxj/qxj-backend-admin/certs/server.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
ssl_session_cache shared:SSL:10m;
ssl_session_timeout 10m;
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
client_max_body_size 20m;
gzip on;
gzip_types text/css application/javascript application/json image/svg+xml;
gzip_min_length 1k;
access_log /var/log/nginx/qxj-access.log;
error_log /var/log/nginx/qxj-error.log;
# 后端 API 反代
location /api/ {
proxy_pass http://qxj_backend;
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_read_timeout 120s;
}
# Django 后台静态资源(collectstatic 产物)
location /static/ {
alias /opt/qxj/qxj-backend-admin/staticfiles/;
expires 7d;
}
# 前端 SPA
location / {
root /var/www/qxj/dist;
index index.html;
try_files $uri $uri/ /index.html;
expires 1h;
}
}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
11.3 Supervisor / Systemd 服务文件样例
Supervisor(/etc/supervisord.d/qxj-backend.ini)与 Systemd(/etc/systemd/system/qxj-backend.service)完整样例见 6.1、6.2 节,可直接誊录使用。两者二选一即可,推荐 Systemd。
11.4 快速部署检查清单(Checklist)
| # | 检查项 | 命令/方法 | 通过标准 |
|---|---|---|---|
| 1 | 操作系统与依赖软件就位 | python3.11 -V、nginx -v、mysql --version、redis-cli --version | 版本满足 2.2 节 |
| 2 | 代码检出发布标签 | git describe --tags | 与交付基线一致 |
| 3 | venv 与依赖安装完成 | pip check | 无 broken 依赖 |
| 4 | .env / .env.production 已配置 | 逐项核对 3.4 节 | 密钥为强随机值、非占位 |
| 5 | TLS 证书就位 | ls -l certs/ | 证书私钥权限 600,SAN 含访问地址 |
| 6 | 数据库初始化完成 | python tools/setup.py status | 迁移与初始数据完整 |
| 7 | collectstatic 已执行 | ls staticfiles/ | 目录非空 |
| 8 | Gunicorn 由 systemd/supervisor 托管 | systemctl status qxj-backend | active (running) |
| 9 | Nginx 配置正确并启动 | nginx -t && systemctl status nginx | 语法 OK、running |
| 10 | HTTPS 探活通过 | curl -sk -o /dev/null -w '%{http_code}' https://<域名>/api/v3/ | 返回应用层状态码 |
| 11 | 前端页面可访问 | 浏览器访问 https://<域名>/ | 登录页正常 |
| 12 | 冒烟测试通过 | 按 8.3 清单执行 | 6 项全部通过 |
| 13 | 日志落盘与切割配置 | ls logs/、logrotate -d /etc/logrotate.d/qxj-backend | 日志正常、切割规则生效 |
| 14 | 开机自启配置 | systemctl is-enabled qxj-backend nginx mysqld redis | 全部 enabled |
| 15 | 初始密码已修改 | 管理端修改 admin 默认密码 | 默认口令已废弃 |
附:鸿蒙 APP 分发站(apps.nsp.ac.cn)部署说明:纯静态站点(Vue 3 + Vite 构建),在构建机执行 npm install && npm run build,将 dist 产物上传至阿里云服(8.140.216.214)宝塔站点目录(apps.nsp.ac.cn 的 443 站点),Nginx 配置 root 指向产物目录并加 try_files $uri $uri/ /index.html; 兜底即可,发布更新仅替换静态文件。