适用版本:v1.1.0(2026-08-15)· 部署包:
mai-2-dingxin-v1.1.0.tar.gz适用:你有一台装了宝塔面板的 Linux 服务器,想用宝塔的「Node.js 版本管理器」直接跑本系统(不使用 Docker)。
底层 = 起一个 Next.js 进程(单副本)+ 宝塔 Nginx 反代 + Let's Encrypt SSL。
数据库 = PostgreSQL(集中式,应用通过DATABASE_URL连接)。本手册第 0 步先让你准备好一个 Postgres 实例。
⚠️ 单副本硬约束:应用的「读缓存 + 登录限流 + 写锁」在进程内存,多实例并发会读旧数据/丢更新,故实例数保持 1(禁多开 / 负载均衡)。
0. 先准备两样东西
部署包:
mai-2-dingxin-v1.1.0.tar.gz(见第 1 步;不含node_modules/、.next/、.env)一个 PostgreSQL 实例(三选一):
宝塔「数据库」里建一个 PostgreSQL 库(填库名、用户名、强口令);
服务器用 Docker 起一个
postgres:18-alpine并挂卷(数据基线由 PG 18 生成,低于 18 无法恢复);云厂商托管 RDS(推荐限制来源 IP,只允许本机访问 5432)。 拿到连接串,形如:
postgresql://用户名:强口令@地址:5432/库名应用首次启动会自动建表,并用
.env的ADMIN_USER/ADMIN_PASS种子化初始管理员(建号后即失效)。站点内容可在后台自行搭建;若部署包含_db_backup/数据基线,也可用pg_restore -Fc --clean --if-exists -d 库名 _db_backup/mai_2_dingxin_20260815.dump直接恢复已有内容。
1. 准备部署包(推荐用现成包,也可自行打包)
推荐:直接使用官方部署包 mai-2-dingxin-v1.1.0.tar.gz(已含源码 / 文档 / data/uploads / _db_backup 数据基线),跳到第 2 步上传即可。
自行打包(仅当你改了源码、需要从源码重打时):
tar --exclude=node_modules --exclude=.next --exclude=.git \
--exclude=.env --exclude=backups --exclude=.workbuddy \
-czf mai-2-dingxin-v1.1.0.tar.gz -C "项目根目录" .2. 上传解压到服务器
宝塔 → 文件 → 进
/www/wwwroot/→ 新建mai-2-dingxin上传
mai-2-dingxin-v1.1.0.tar.gz→ 右键解压到当前目录
目录应含:src/ public/ .env.example package.json package-lock.json(不含 node_modules/.next,下面会装会编)
运行时会自动生成
data/uploads(图片)与data/backups(备份),需保证该目录可写(见第 7 步)。
3. 生成 .env(安全关键,Next.js 会自动读取)
复制
.env.example→ 重命名.env编辑
.env,改成真实值(注意加了HOSTNAME=127.0.0.1,让 3000 只监听本机):
ADMIN_JWT_SECRET=在此填 openssl rand -base64 48 的输出
SECRET_MASTER_KEY=再生成一个随机串填这里(库内 LLM API Key 信封加密主密钥,与备份分开保管)
ADMIN_PASS=在此填强密码
ADMIN_USER=admin
PORT=3000
NODE_ENV=production
HOSTNAME=127.0.0.1
# ===== PostgreSQL 连接串(必填,指向第 0 步准备的实例)=====
DATABASE_URL=postgresql://用户名:强口令@地址:5432/库名
# 可选:站点正式域名(canonical/sitemap/og 绝对地址用)
# 留空则改用后台「基础信息 → 网站域名」字段(推荐,实时生效);两者都不设会回退 localhost
NEXT_PUBLIC_SITE_URL=
# 可选:前台边缘缓存调优(详见 .env.example「前台边缘缓存」段)
# REVALIDATE_SECONDS=3600
# REVALIDATE_STALE=86400
# 想让「后台保存即整站刷新」:DNS 走 Cloudflare 并设 Cache Rule Cache Everything,
# 再填下面三项(最省事);或在宝塔 Nginx 里加 proxy_cache(参考 deploy/nginx-cache.conf)。
# CDN_PROVIDER=cloudflare
# CLOUDFLARE_ZONE_ID=
# CLOUDFLARE_API_TOKEN=不强填
ADMIN_JWT_SECRET会用代码弱默认值,后台 token 可被伪造 —— 生产必须填。
不强填SECRET_MASTER_KEY会退化为复用ADMIN_JWT_SECRET或开发兜底值,库内 API Key 等同弱加密并告警 —— 生产必须填。.env是唯一必要来源:next start和备份脚本backup-db.mjs都只读这个.env文件。宝塔「项目环境变量」只是可选的双保险——但备份脚本不读宝塔 UI 的环境变量,所以DATABASE_URL必须写进.env,否则备份会静默失效(详见「备份」一节)。
🌐 站点域名(SEO 关键):本系统 canonical / sitemap.xml / og:image 的绝对地址都依赖站点域名。两种方式二选一:
推荐(实时、零停机):部署后登录后台 → 「网站管理 → 基础信息」填**「网站域名」**并保存,立即生效,无需重启。
备选(环境变量):在下方
.env填NEXT_PUBLIC_SITE_URL=https://你的域名.com,然后重启 Node 项目即可(仅服务端读取,不需重新 build);此变量优先级高于后台字段。两者都不设会回退
http://localhost:3000,生产环境 SEO 链接全部错误,务必避免。
4. 装 Node 22(宝塔软件商店)
宝塔 → 软件商店 → 搜索安装 Node.js 版本管理器(新版同时内含 PM2)
打开它 → 版本管理 → 安装/确认 Node v22(与
package.json的engines.node>=22对应)。若服务器已自带(常见路径
/www/server/nodejs/v22.23.1/),直接用它、第 6 步选此版本即可,无需重复安装。安装后记得在「版本」里确认 22 已就绪。
5. 装依赖 + 构建(宝塔「终端」或 SSH,进入项目目录)
# 宝塔终端默认 PATH 无 Node 22,先加上(否则 npm: command not found)
export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
node -v # 确认 v22.23.1
cd /www/wwwroot/mai-2-dingxin
npm ci # ⚠️ 必须完整安装(不要加 --omit=dev)
npm run build # 看到 Compiled successfully 即成功宝塔不会自动安装/编译,这两步必须手动跑。
6. 添加 Node 项目并启动
宝塔 → Node.js 版本管理器 → 项目 → 添加项目:
项目名称:
mai-2-dingxin项目目录:选
/www/wwwroot/mai-2-dingxin启动命令:
npm run start(即next start)端口:
3000运行 Node 版本:选 v22
运行用户:默认
www(与 Nginx 一致即可)实例数保持 1(不要开多开 / 负载均衡,原因见顶部「单副本硬约束」)
域名(可选但推荐):填入你的域名,宝塔会自动建好反代站点,第 8 步可直接在它上面挂 SSL
环境变量(关键!):
ADMIN_JWT_SECRET=强随机串、SECRET_MASTER_KEY=另一串强随机串、ADMIN_USER=admin、ADMIN_PASS=强密码、NODE_ENV=production、PORT=3000、DATABASE_URL=第 0 步的 PostgreSQL 连接串点「提交」后点「启动」,看日志确认输出
Ready、无报错(全新库会自动建表 + 种子化管理员)。若日志卡在「端口占用」:
lsof -i:3000查占用进程,kill掉或改PORT与反代端口。
若日志报连不上数据库:检查DATABASE_URL是否正确、Postgres 实例是否可达、5432 是否对应用服务器开放。
备选启动方式:launcher 托管模式
默认 npm run start(直跑 next start)最稳。若要用项目自带的 scripts/launcher.mjs 启动器(内置看门狗 + 受控重启),按下面改两处:
启动命令改为:
node scripts/launcher.mjs环境变量额外加:
SUPERVISED=1
SUPERVISED=1 让 launcher 进入托管模式——放弃自身二级监管、直跑 next,把重启/存活完全交给宝塔,停止项目时端口干净释放(不留孤儿进程占 3000)。务必加上,否则 launcher 走裸机模式(detached + 3001 控制端口),被宝塔停服时 next 可能逃逸成孤儿占端口。
7. 赋权 data 目录(必做,否则上传写不进)
Node 进程(默认 www 用户)要对 data/ 可写(存上传图片与备份):
chown -R www:www /www/wwwroot/mai-2-dingxin/data
chmod -R 755 /www/wwwroot/mai-2-dingxin/data若启动/访问仍报
EACCES写库错误:在进程日志里看实际运行用户,把data改为该用户即可。
赋权后重启 Node 项目(宝塔里点「重启」)。
8. 域名 + HTTPS 反代
若第 6 步已填域名(推荐):宝塔已自动生成反代站点。
宝塔 → 网站 → 找到该域名站点 → SSL → Let's Encrypt → 勾选域名 → 申请并部署 → 开启「强制 HTTPS」。
若第 6 步没填域名(手动反代):
宝塔 → 网站 → 添加站点(填域名,FTP/数据库不创建)
该站点 → 反向代理 → 目标 URL:
http://127.0.0.1:3000→ 提交同上步骤开 SSL + 强制 HTTPS。
因为第 3 步设了
HOSTNAME=127.0.0.1,3000 只在本机,公网只会走 80/443。宝塔「安全」里只放行 22/80/443,不要对外暴露 3000,也不要对外暴露 PostgreSQL 的 5432。
9. 验证与收尾
前台
https://域名/、健康检查https://域名/api/health(返回 200)SEO 产出检查:
https://域名/robots.txt返回 200 且含Sitemap:;https://域名/sitemap.xml返回 200 且<loc>里的域名正确(不是 localhost)设置站点域名(canonical/sitemap/og 才能用正式域名):登录后台 → 「网站管理 → 基础信息」填**「网站域名」**并保存,立即生效、无需重启(详见第 3 步说明)
后台
https://域名/admin用第 3 步.env的ADMIN_USER/ADMIN_PASS登录(全新库直接生效,不是"原实例密码"),立即改强密码上线前确认:单实例、3000 仅本地、5432 不暴露、已赋权、已备份、站点域名已设
9.5 重置管理员密码(忘了密码 / 想改用 .env 的 ADMIN_PASS)
库里已有用户时,.env 的 ADMIN_PASS 不生效。要强制用 .env 新密码,须清空库里的用户、让服务重启时重新种子:
# 1) 宝塔 Node.js 管理器里「停止」项目
# 2) 服务器执行(需 psql 客户端,连接第 0 步的库):
psql "$DATABASE_URL" -c "DELETE FROM data WHERE key='users.json';"
# 3) 回到宝塔「启动」项目 → 日志会打印全新库首次启动... 用 .env 的 ADMIN_USER/ADMIN_PASS 登录登录后立刻在后台改强密码(
.env的ADMIN_PASS只是建号用,之后不再参与校验)。
若没有 psql 客户端:在宝塔「数据库」管理界面直接执行上面那条DELETE FROM data WHERE key='users.json';亦可。
排错(只对真会踩的)
现象 | 原因 / 解决 |
|---|---|
首页 500 / 改内容刷新没了 | 全新 PG 库只种子化管理员、不自动生成站点内容;先登录后台搭建内容。另确认 |
启动报连不上数据库 / |
|
启动报 |
|
启动报 | 没跑 |
访问 502 | Node 项目没启动或端口不对(第 6 步 |
改了源码没生效 | 必须重新 |
登录被限流 429 | 登录 15 分钟内最多 10 次/IP,超限等 15 分钟或重启清空(单实例有效) |
备份(必做,否则数据无保障)
cd /www/wwwroot/mai-2-dingxin
node scripts/backup-db.mjs # pg_dump 导出库 + 打包 uploads → data/backups/两个前提(缺一都会导致备份静默失效,务必核对):
DATABASE_URL必须写在项目根.env文件里——备份脚本只读.env/.env.local文件和 shell 环境变量,不读宝塔面板 UI 里填的环境变量。若只在宝塔「Node 项目」环境变量里填了DATABASE_URL、没写进.env,脚本会输出SKIP: .../data/site.db not found (亦未配置 DATABASE_URL),等于没备份。pg_dump必须能被找到——安装postgresql-client,或设PG_BIN_DIR=/usr/lib/postgresql/<版本>/bin。找不到时脚本输出WARN: L1 pg_dump 不可用。
定时备份(crontab,每天凌晨 4 点):
crontab -e
# 0 4 * * * cd /www/wwwroot/mai-2-dingxin && /www/server/nodejs/v22.23.1/bin/node scripts/backup-db.mjs >> /var/log/site-backup.log 2>&1cron 的环境常不含
node,建议用宝塔 Node 的完整路径(/www/server/nodejs/<版本>/bin/node,第 4 步确认过的版本);若你已把 node 放进系统 PATH 也可直接用node。
验证备份成功:手动跑一次,日志应出现 L1 OK、L2 OK、BACKUP DONE;出现 ERROR: 数据库备份失败... 或 WARN: L1 pg_dump 不可用 就是上面两个前提没满足(脚本会以非 0 退出),按上文修正后重跑。
10. 生产环境代码 / 模板更新流程(手动)
适用场景:你改了代码/模板(
src/templates/**、src/lib/theme.ts、primitives.tsx、新增模板、或其它src/改动)后推上生产。
不适用:后台改内容(产品/新闻/案例/设置)实时生效,不需要走这里,也不用 build。只有"代码/模板改动"才需要 build + 重启。
更新前必读(3 条红线)
模板是编译进
.next/的 TSX 组件:改源码必须重新npm run build;光重启不 build 无效(见排错表)。绝对不碰
data/uploads与data/backups:生产图片与备份在这两处,覆盖 = 丢数据。上传/解压时务必排除data/。单副本(重启约 2–5 秒中断):挑低峰期操作;若担心 build 期间有用户正在后台改内容,可先在宝塔「停止」项目再操作。原因见顶部「单副本硬约束」。
详细步骤
步骤 0 — 备份(最先做,防翻车)
export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
cd /www/wwwroot/mai-2-dingxin
node scripts/backup-db.mjs && echo "✓ 数据库备份成功" # pg_dump 导出 → data/backups(失败会打印 ERROR 并中断)
cp -r .next .next.bak.$(date +%F-%H%M) && echo "✓ 产物备份完成" # 备份当前 .next(回滚用)⚠ 务必看到「✓ 数据库备份成功」再往下走:
backup-db.mjs失败时会打印ERROR: ...并以非 0 退出,此时&& echo不会执行(看不到"成功"字样)。若没看到成功回显,说明数据库没备上——先按「备份」一节修好前提(.env里配DATABASE_URL+pg_dump可用)再继续,否则"防翻车"无从谈起。
步骤 1 — 把新代码传上服务器
方式 A(整体同步,推荐):本地按第 1 步重新打包(已排除
node_modules/.next/.git/.env/data),上传到服务器临时目录解压,再用rsync/宝塔文件管理器把其中src/、public/、package*.json、*.config.*、.env.example等移进项目目录,跳过data/和.env。bash # 例:若解压在 /tmp/new,仅同步代码、排除数据与环境 rsync -a --exclude='data' --exclude='.env' --exclude='node_modules' --exclude='.next' \ /tmp/new/ /www/wwwroot/mai-2-dingxin/方式 B(只改少量文件):宝塔「文件」管理器直接覆盖
src/下对应文件即可,更快。
步骤 2 — 让 Node 22 进 PATH(宝塔终端每次新开都要)
export PATH=/www/server/nodejs/v22.23.1/bin:$PATH
node -v # 确认 v22.23.1
cd /www/wwwroot/mai-2-dingxin步骤 3 — 依赖变化才重装(否则跳过)
仅当改了 package.json / package-lock.json(如新模板加了依赖)才需要:
npm ci # 完整安装,不要加 --omit=dev
复制没改依赖直接下一步,省时间。
步骤 4 — 重新构建(核心,不可省)
npm run build # 看到 Compiled successfully 即成功构建失败常见原因:Cannot find module → 回到步骤 3 装依赖;Node 版本不对 → 确认 v22;磁盘满 → df -h 查。
步骤 5 — 数据迁移(仅 v1.0.x 升级到 v1.1.0 需要)
本版有两处数据变化:messages 表新增 type/meta 字段(重启后自动补全,无需手动);团队成员从 about.json 拆为独立 team.json,需跑一次迁移脚本(否则前台「核心团队」区块为空):
node scripts/migrate-team.mjs # 幂等:team.json 已有成员则跳过,可反复跑步骤 6 — 重启 Node 进程加载新产物
宝塔 → Node.js 版本管理器 → 项目
mai-2-dingxin→ 「重启」或命令行(看你起的方式):
pm2 restart mai-2-dingxin
看日志出现Ready、无报错即成功。
步骤 7 — 验证
前台
https://域名/正常、/api/health返回 200后台
https://域名/admin登录正常(HTTPS!否则 Secure cookie 被弃,见第 8 步)确认你改过的模板/页面确实生效(清浏览器缓存再看)
失败回滚
代码/构建翻车(新代码有 bug、build 失败):恢复旧产物即可,数据不动。
bash cd /www/wwwroot/mai-2-dingxin rm -rf .next mv .next.bak.<步骤0的时间戳> .next # 宝塔里「重启」项目数据库需要回滚(误删/误改内容):
bash # 备份脚本产出的是纯文本 SQL(pgdump.sql),用 psql 恢复: psql "$DATABASE_URL" -f data/backups/volume/data-<ts>/pgdump.sql # 或 L1 快照:psql "$DATABASE_URL" -f data/backups/snapshots/pgdump.sql.<ts> # 恢复后重启项目,让进程内缓存重新加载误覆盖 data(极端):从备份恢复
data/uploads即可(库不在 data/ 里)。
一句话总结
备份 → 传代码(跳过 data/)→ PATH → [npm ci] → npm run build → [node scripts/migrate-team.mjs 数据迁移] → 重启 → 验证;翻车用 .next.bak 回滚代码,数据用 backup-db.mjs 的 pg_dump 备份回滚。
升级流程(极简版,老手回顾用)
宝塔「终端」进项目目录:
[npm ci] && npm run build(仅 v1.0.x → v1.1.0)跑一次数据迁移:
node scripts/migrate-team.mjs宝塔 Node.js 管理器里重启
mai-2-dingxin项目后台改内容实时生效,不用重建;改代码/模板才需上面两步。
升级前务必先
node scripts/backup-db.mjs打快照,并确认输出L1 OK/BACKUP DONE(若SKIP说明.env缺DATABASE_URL,先补上,见「备份」一节)。
