网页上线 + 飞书应用部署指南 SOP https://ai.njwl.ai/deploy/ · 人 / AI 通用 · 维护者 ben22

本文档回答一个问题:如何把一个网页应用部署到我们的服务器,再接入飞书应用。照着步骤做即可,末尾附模板库、完整实例、检查清单和踩坑记录。

一页速览

  • 部署位置:香港服务器 43.129.74.108(any-g.cn,免备案),域名 + HTTPS + 反代 + 容器都在这一台。
  • 访问链路:浏览器/飞书 → https://子域名/路径 → 香港机 Nginx(443) → 127.0.0.1:端口 → Docker 容器。
  • 三步上线:① DNS 解析 → ② Docker 起容器 → ③ Nginx + HTTPS。
  • 接飞书:飞书企业自建应用 + 多维表格当数据库 + 主页/OAuth 回调 + 发布。

服务器清单

项目香港服务器(主力,用这台部署)大陆服务器(公网中枢,勿动)
公网 IP43.129.74.108111.229.185.231
系统Ubuntu 24.04Ubuntu 24.04
Web宝塔面板 + NginxNginx(frps/OpenClaw 等)
SSH 别名ssh ubuntu@any-g.cn(密钥免密 ~/.ssh/id_ed25519ssh cloud(端口 2222)
用途官网 + 各业务应用 + 本文档公网中枢,只读,勿动
备案香港机免 ICP 备案,域名可直接绑定
重要:新项目一律部署在香港机。大陆机是中枢,除非明确要求,否则不把新应用放上去。

网页上线到服务器(香港机直部署)

第 0 步:准备应用

应用需要自带三个东西(缺一不可):

  • Dockerfile —— 描述怎么构建镜像(Node 应用参考 FROM node:24-alpine + CMD ["node","src/server.js"])。
  • compose.yaml —— 端口映射只绑 127.0.0.1,数据挂载持久化目录。
  • .env —— 密钥和配置,只放服务器,不进 Git、不下发浏览器

第 1 步:DNS 解析

在腾讯云 DNSPod 给目标域名加一条 A 记录:主机记录 → 43.129.74.108

Resolve-DnsName 子域名.njwl.ai -Type A   # 生效后应返回 43.129.74.108
DNS 未生效就急着申请 HTTPS 证书会失败(报 NXDOMAIN / no valid A records),先等解析全球生效。

第 2 步:上传并启动容器

scp compose.yaml ubuntu@any-g.cn:~/项目名/
scp .env ubuntu@any-g.cn:~/项目名/.env
scp -r 项目目录 ubuntu@any-g.cn:~/项目名/
ssh ubuntu@any-g.cn 'cd ~/项目名 && docker compose up -d --build && docker compose ps'

本地冒烟测试(还没配域名时先确认容器活着):

ssh ubuntu@any-g.cn "curl -s http://127.0.0.1:18831/ | head"

第 3 步:Nginx 站点 + HTTPS

scp 项目名.conf ubuntu@any-g.cn:/tmp/
ssh ubuntu@any-g.cn 'sudo cp /tmp/项目名.conf /www/server/panel/vhost/nginx/ && \
  sudo nginx -t && sudo /www/server/nginx/sbin/nginx -s reload'

HTTPS 证书二选一(飞书可信域名要求 HTTPS):

  • 宝塔:网站 → SSL → Let's Encrypt → 勾选域名 → 申请。
  • certbotssh ubuntu@any-g.cn 'sudo certbot --nginx -d 域名'

第 4 步:验证

  • https://域名/ 能打开首页。
  • 登录 → 查询 → 写入 → 附件(如需)全链路正常。
  • 证书在 Chrome / Safari / 飞书内置浏览器都受信任。

上线到飞书应用(点哪儿 · 逐步版)

全程浏览器操作,需企业管理员账号。下面示例用 lab.njwl.ai/shidu,换成你自己的域名/路径即可。

第 1 步:登录飞书开放平台

  1. 浏览器打开 https://open.feishu.cn/
  2. 点右上角「登录
  3. 用企业管理员账号扫码 / 登录

第 2 步:创建「企业自建应用」

  1. 登录后点顶部「开发者后台」(或直接访问 open.feishu.cn/app
  2. 左侧菜单点「企业自建应用
  3. 点右上角「创建企业自建应用」按钮
  4. 填「应用名称」(如「实验室湿度屏」)、上传图标、写应用描述
  5. 点「创建

第 3 步:拿到 App ID 和 App Secret

  1. 创建后自动进入「应用详情页」
  2. 左侧点「凭证与基础信息
  3. 复制「App ID
  4. 「App Secret」点右侧「查看」→ 复制
  5. 这两个值填进服务器 .envFEISHU_APP_ID / FEISHU_APP_SECRET

第 4 步:添加「网页应用」能力 + 配主页

  1. 左侧点「应用能力
  2. 找到「网页」分类,点「添加」→「网页应用
  3. 「桌面端主页」填 https://lab.njwl.ai/shidu/
  4. 「移动端主页」填同一个地址
  5. 点「保存

第 5 步:开通权限

  1. 左侧点「权限管理
  2. 在搜索框输入「多维表格
  3. 逐个点「开通权限」,至少开这三个:
    • 获取多维表格元信息(读 App Token / 表结构)
    • 读取多维表格记录
    • 新增、修改、删除多维表格记录
  4. 搜索框改输「用户」→ 开通「获取用户身份信息」(飞书免登录识别操作人用)
  5. 如需推送通知:搜索框改输「消息」→ 开通「发送消息」
权限名随版本可能略有差异,认准「多维表格」「用户身份」「消息」这几个关键词,按需开通即可。开多了没关系,开少了接口会报「无权限」。

第 6 步:安全设置(可信域名 + 重定向 URL)

  1. 左侧点「安全设置
  2. 找到「重定向 URL」→ 点「添加」→ 填 https://lab.njwl.ai/shidu/auth/callback
  3. 找到「H5 可信域名」(有的版本叫「网页可信域名」)→ 点「添加」→ 填 lab.njwl.ai
  4. 点「保存

第 7 步:把应用加为多维表格协作者

  1. 打开目标飞书「多维表格」
  2. 右上角点「分享
  3. 点「添加协作者
  4. 搜索并选中刚创建的应用名(如「实验室湿度屏」)
  5. 点「确定
这步不做,接口会一直报「无权限」——即使第 5 步权限管理已经开了。

第 8 步:创建版本并发布

  1. 回到开放平台「应用详情页」
  2. 左侧点「版本管理与发布
  3. 点「创建版本
  4. 填版本号(如 1.0.0)和更新说明
  5. 点「保存」→ 点「申请发布
  6. 设置「可用范围」(勾选能看到的部门 / 人员)

第 9 步:管理员审核(若企业开了审核)

  1. 管理员打开飞书「管理后台」(客户端左侧「工作台」→ 更多,或 feishu.cn/admin
  2. 「工作台」→「应用管理」→「待审核
  3. 点「通过
若企业未开启发布审核,第 8 步提交后直接生效,跳过本步。

第 10 步:验证

  1. 打开飞书客户端左侧「工作台
  2. 找到应用图标(如「实验室湿度屏」)点进去
  3. 应打开 https://lab.njwl.ai/shidu/,且能免登录识别当前用户

环境变量对照填

DATA_MODE=feishu
FEISHU_APP_ID=第 3 步的 App ID
FEISHU_APP_SECRET=第 3 步的 App Secret
FEISHU_BITABLE_APP_TOKEN=多维表格 URL 里 /base/ 后面那串(App Token)
FEISHU_MASTER_TABLE_ID=主档表 URL 里 table= 后面那串(tbl 开头)
FEISHU_HISTORY_TABLE_ID=操作记录表 URL 里 table= 后面那串
PUBLIC_BASE_URL=https://lab.njwl.ai
APP_BASE_PATH=/shidu
FEISHU_LOGIN_ENABLED=true

App Token / Table ID 看多维表格地址栏:https://xxx.feishu.cn/base/AppToken?table=tblXXX&view=vewXXX

改了权限或安全设置后,必须回到「版本管理与发布」重新创建版本并发布,否则改动不生效。

怎么绑定飞书登录验证(OAuth 免登录)

让用户用飞书账号登录,应用就知道「是谁在操作」。飞书 OAuth 2.0:在飞书工作台点应用自带登录态,浏览器直接打开会跳授权页点一下「同意」。

飞书登录要改 三处:① 飞书开放平台(开权限 + 配重定向 URL,浏览器点);② 服务器 .env(App ID / Secret + 登录开关,SSH 改完要重启容器);③ 应用代码里的 login / callback 接口(通常已写好,只需确认路径对得上)。

第 1 步:开通用户身份权限

  1. 开放平台「应用详情页」→ 左侧「权限管理
  2. 搜索「用户」→ 开通「获取用户基本信息」(contact:user.base:readonly
  3. 改完权限到「版本管理与发布」重新发布(权限改动要重发版本才生效)

第 2 步:配置重定向 URL

  1. 左侧「安全设置」→「重定向 URL」→「添加
  2. https://lab.njwl.ai/shidu/auth/callback

第 3 步:登录流程(代码要做的四件事)

  1. 跳授权页:用户没登录时,跳到 https://accounts.feishu.cn/open-apis/authen/v1/authorize?app_id=…&redirect_uri=…&state=随机串
  2. 拿 code:用户在授权页点「同意」,飞书带 code 跳回 redirect_uri?code=xxx
  3. 换 token:后端拿 code 调 POST /open-apis/authen/v2/oauth/tokenuser_access_token
  4. 拿用户:用 token 调 GET /open-apis/authen/v1/user_info,得到 open_id / 名字 / 头像,写入 session

代码骨架(Node / Express)

// ① 登录入口:跳飞书授权页
app.get('/shidu/auth/login', (req, res) => {
  const url = 'https://accounts.feishu.cn/open-apis/authen/v1/authorize'
    + '?app_id=' + process.env.FEISHU_APP_ID
    + '&redirect_uri=' + encodeURIComponent(base + '/shidu/auth/callback')
    + '&state=' + crypto.randomUUID();
  res.redirect(url);
});

// ② 回调:code 换 token → 拿用户信息 → 建登录态
app.get('/shidu/auth/callback', async (req, res) => {
  const code = req.query.code;
  const t = await (await fetch('https://open.feishu.cn/open-apis/authen/v2/oauth/token', {
    method: 'POST', headers: { 'Content-Type': 'application/json' },
    body: JSON.stringify({ grant_type: 'authorization_code',
      client_id: process.env.FEISHU_APP_ID, client_secret: process.env.FEISHU_APP_SECRET,
      code, redirect_uri: base + '/shidu/auth/callback' })
  })).json();
  const u = await (await fetch('https://open.feishu.cn/open-apis/authen/v1/user_info', {
    headers: { Authorization: 'Bearer ' + t.data.access_token }
  })).json();
  req.session.user = u.data;   // u.data.open_id / name / avatar_url
  res.redirect('/shidu/');
});

第 4 步:服务器端改 .env(关键,SSH 操作)

  1. SSH 登录香港机:ssh ubuntu@any-g.cn
  2. 进入应用目录并编辑 .envcd ~/shidu-app && nano .env
  3. 填这 5 项(App ID / Secret 就是第 3 步在开放平台拿到的):
FEISHU_APP_ID=cli_xxxxxxxxxxxx        # 开放平台「凭证与基础信息」里的 App ID
FEISHU_APP_SECRET=xxxxxxxxxxxxxxxx    # App Secret
FEISHU_LOGIN_ENABLED=true             # 开启飞书登录鉴权
PUBLIC_BASE_URL=https://lab.njwl.ai    # 登录回调用到的域名
APP_BASE_PATH=/shidu                   # 应用子路径
  1. 保存后重启容器让配置生效:docker compose up -d(或 docker compose restart
回调地址是由 .env 拼出来的PUBLIC_BASE_URL + APP_BASE_PATH + /auth/callback = https://lab.njwl.ai/shidu/auth/callback。这个地址必须和第 2 步在飞书「安全设置 → 重定向 URL」里填的一字不差,否则登录回调 404。state 用随机串防 CSRF;改完 .env 必须重启容器才生效;user_access_token 只在后端用,别下发浏览器。

怎么增加机器人提醒

两种方式:群 Webhook 机器人(最简单,往群里发);应用机器人(能私聊指定人,复杂一点)。一般「提醒」用群 Webhook 就够了。

方式 A:群自定义机器人 Webhook(推荐,最简单)

  1. 打开要接收提醒的飞书「群聊」
  2. 点群名 →「设置」→「群机器人」→「添加机器人」→ 选「自定义机器人
  3. 给机器人起名(如「实验室湿度提醒」)
  4. 复制「Webhook 地址」(形如 https://open.feishu.cn/open-apis/bot/v2/hook/xxxx
  5. (可选)安全设置里勾「签名校验」或「关键词」,防止别人乱发
  6. 点「完成

拿到 Webhook 地址后,往它 POST 一段 JSON 就能发提醒:

curl -X POST 'https://open.feishu.cn/open-apis/bot/v2/hook/xxxx' \
  -H 'Content-Type: application/json' \
  -d '{"msg_type":"text","content":{"text":"⚠️ 湿度超标:A区 72%"}}'

塞进应用代码里(Node 示例):

await fetch(process.env.FEISHU_BOT_WEBHOOK, {
  method: 'POST',
  headers: { 'Content-Type': 'application/json' },
  body: JSON.stringify({ msg_type: 'text', content: { text: '任务完成提醒' } })
});

把 Webhook 地址放到服务器 .envFEISHU_BOT_WEBHOOK=https://open.feishu.cn/open-apis/bot/v2/hook/xxxx),不要写进 Git。常用 msg_typetext(纯文本)、post(富文本)、interactive(卡片)。

方式 B:应用机器人(私聊员工 / 接收消息)

  1. 回到开放平台「应用详情页」→ 左侧「应用能力」→「机器人」→ 点「添加
  2. 左侧「权限管理」→ 搜索「消息」→ 开通这两个权限:
    • 获取与发送单聊、群组消息:开通后机器人可向用户发单聊消息,或向机器人所在群聊发群消息
    • 获取用户在群组中@机器人的消息:开通后可接收用户在群聊中 @机器人的消息
  3. 左侧「事件与回调」→「添加事件」→「消息与群组」→ 添加「接收消息」事件,机器人才能收到用户发来的单聊消息、以及群聊中 @机器人的消息
  4. 代码里先拿 tenant_access_token,再调 POST /open-apis/im/v1/messagesreceive_id 填目标用户的 open_id
  5. 用户的 open_id 通过飞书免登录(/open-apis/authen/v1/user_info)拿到
方式 B 适合「给某个具体员工私聊提醒」;方式 A 适合「发生事情往一个群里喊一嗓子」。多数提醒场景用 A 就够。

配置模板库

Nginx(香港机直部署 + 子路径,带 HTTPS)

# 域名.conf
server {
    listen 80;
    server_name 域名;
    location ^~ /.well-known/acme-challenge/ { root /var/www/certbot; default_type "text/plain"; }
    location / { return 301 https://$host$request_uri; }
}
server {
    listen 443 ssl;
    http2 on;
    server_name 域名;
    ssl_certificate /etc/letsencrypt/live/域名/fullchain.pem;
    ssl_certificate_key /etc/letsencrypt/live/域名/privkey.pem;
    ssl_protocols TLSv1.2 TLSv1.3;

    location = / { return 301 /路径/; }          # 根路径跳到应用子路径(若用根路径则删这行)
    location /路径/ {
        client_max_body_size 25m;
        proxy_pass http://127.0.0.1:端口;         # 不带尾斜杠,路径原样透传
        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 https;
        proxy_read_timeout 60s;
    }
    access_log /www/wwwlogs/域名.log;
    error_log /www/wwwlogs/域名.error.log;
}

compose.yaml

services:
  app:
    build: .
    container_name: 容器名
    restart: unless-stopped
    env_file: .env
    ports:
      - "127.0.0.1:端口:3000"      # 只绑本机回环
    volumes:
      - ./data:/app/data

.env

PORT=3000
PUBLIC_BASE_URL=https://域名
APP_BASE_PATH=/路径
DATA_MODE=mock                      # mock=本地演示 / feishu=正式
FEISHU_APP_ID=
FEISHU_APP_SECRET=
FEISHU_BITABLE_APP_TOKEN=
FEISHU_MASTER_TABLE_ID=
FEISHU_HISTORY_TABLE_ID=
FEISHU_LOGIN_ENABLED=true

完整实例

实例 1 · lab.njwl.ai/shidu(实验室湿度 LED 屏)香港机直部署

域名 / 路径lab.njwl.ai / /shidu
容器端口127.0.0.1:18831
主页 / 回调https://lab.njwl.ai/shidu/ · …/shidu/auth/callback

实例 2 · diary.chenben.ai(思源笔记)香港机直部署 · 根路径

无子路径的典型:location / 直接 proxy_pass http://127.0.0.1:6806APP_BASE_PATH 留空。

实例 3 · cell.njwl.ai(电芯录入,旧两段式,仅参考)

历史模式:香港机 proxy_pass http://111.229.185.231 反代到大陆机容器。新项目不再这样,统一香港机直部署。

上线检查清单

  • ✅ 子域名 A 记录已解析到 43.129.74.108 并全球生效
  • ✅ 容器启动、docker compose ps 健康、日志无报错
  • ✅ Nginx 站点启用且 nginx -t 通过
  • ✅ 应用 .env 已填(App ID/Secret、Token、表 ID)
  • ✅ HTTPS 证书受 Chrome/Safari/飞书内置浏览器信任
  • ✅ 飞书应用已建、已开通网页应用、已发布
  • ✅ 主页 = https://域名/路径/,OAuth 回调 = …/auth/callback
  • ✅ 飞书应用已加为多维表格协作者
  • ✅ 密钥只存在服务器 .env,未进 Git、未下发浏览器
  • ✅ 定时通知只跑一份(单容器单实例)
  • ✅ 备份 / 日志轮转 / 证书续期已配置

踩坑记录

  • 飞书接口偶发 Data not ready, please try again later → 指数退避重试。
  • 并发建字段重名 → 字段初始化加互斥锁;多节点写需分布式锁。
  • 附件上传失败 → 先查 Nginx client_max_body_size,别让前端把 HTML 错误页当 JSON。
  • 全量读飞书记录慢 → 缓存 + 写入后精准失效,短周期缓存别设太短。
  • Nginx 代理 404 → 检查 proxy_pass 尾斜杠(带尾斜杠会剥前缀,导致路径错位)。
  • 静态站目录 404 → 静态站点用 root + index 即可,别用 try_files $uri $uri/ =404(目录请求会直接落到 404)。
  • HTTPS 申请失败 → 99% 是域名 A 记录没加或没生效,先 Resolve-DnsName
  • 统计口径 → 「实际工艺时间」和「提交时间」别混用,前后端口径统一。