飞书自动化同步:Django 后台一键同步文章到飞书云文档

技术开发 · 工具开发 难度:⭐⭐⭐ 关键词:lark-cli, OAuth, user_access_token, 飞书云文档

故事背景

之前我一直在做"自动同步 Django 文章到飞书云文档"的功能。最痛的点:文档所有者是 App,我自己不能编辑、删除,要"申请权限"才能动。

花了两天研究 OAuth + lark-cli,终于搞定

  • ✅ 文档所有者 = 我(user 身份)
  • ✅ 可以编辑、删除、分享
  • Django 后台一键同步
  • ✅ token 自动续期

这篇文章复盘整个过程。

一、为什么会卡住?

1.1 两种身份的差别

飞书开放平台有两种调用 API 的方式

模式 谁能操作 文档所有者
应用身份(tenant_access_token) App 自己 App
用户身份(user_access_token) 模拟

默认用应用身份,所以文档属于 App,你不是所有者

1.2 你不能编辑/删除的原因

飞书文档的"权限模型"是: - 所有者(owner):可以编辑、删除、转让 - 协作者:只有"申请权限"才能编辑

App 创建的文档,所有者是 App 这个实体,你只是协作者。所以你不能直接编辑

二、OAuth 用户授权流程

要让 App "借用你的身份",走 OAuth 授权

2.1 OAuth 2.0 授权码流程

1. App 引导你到飞书授权页
2. 你登录飞书 → 点"同意授权"
3. 飞书回调 App 带 code
4. App 用 code 换 access_token
5. access_token 有效期 2 小时
6. refresh_token 用于自动续期

2.2 实现方式(手动版)

import requests

# 步骤 1:浏览器跳转飞书授权页
auth_url = (
    f"https://open.feishu.cn/open-apis/authen/v1/index"
    f"?app_id={APP_ID}"
    f"&redirect_uri=http://localhost:8000/callback"
    f"&scope=docs:document:create docs:document:write drive:drive offline_access"
)
# 用户在浏览器登录 → 同意 → 跳回带 code=XXX

# 步骤 2:用 code 换 token
resp = requests.post(
    "https://open.feishu.cn/open-apis/authen/v1/access_token",
    json={
        "grant_type": "authorization_code",
        "code": "用户复制的code",
        "app_id": APP_ID,
        "app_secret": APP_SECRET,
    }
)
token = resp.json()["data"]
# access_token: u-XXX(用户身份 token)
# refresh_token: ur-XXX(续期用)

坑点:手动写 OAuth 坑很多——字段名错了、scope 没勾全、回调 URL 没配……我光 debug 就花了 1 天

三、推荐方案:飞书官方 lark-cli

飞书 2026 年出了一个官方 CLI —— lark-cli所有 OAuth 流程它都帮你搞定

3.1 安装

# 需要 Node.js(18+)
npm install -g @larksuite/cli

3.2 一键绑定 Hermes

lark-cli config bind --source hermes --app-id cli_xxx --identity user-default

输出:

⚠️ 你正在从应用身份切换到用户身份
⚠️ 请勿将此机器人分享给他人... 
配置成功!lark-cli 已可在 Hermes 中使用。

3.3 OAuth 授权(Device Flow)

lark-cli auth login --recommend

自动给你一个验证链接 + 二维码:

在浏览器中打开以下链接进行认证:
  https://accounts.feishu.cn/oauth/v1/device/verify?...
  user_code: PFFU-YLXN

手机飞书扫码 → 确认授权 → token 自动保存。

3.4 创建文档(一行命令)

lark-cli docs +create \
  --doc-format markdown \
  --content - \
  --title "我的文章" \
  --as user

- 表示从 stdin 读 Markdown。lark-cli 自动处理: - Markdown 转飞书块 - 应用样式(标题/代码/列表/表格/引用/分割线/图片) - 保存到你的个人云空间

3.5 完整能力

📚 文档:创建/读取/编辑/搜索
📁 云空间:上传/下载/移动/删除/搜索
📊 多维表格:管理/记录/视图/表单/仪表盘
📅 日历:查日程/约会议/查忙闲/推荐时间
📧 邮件:搜索/读取/起草/发送/转发/归档
💬 消息:搜索消息/群聊/发消息/回复话题
📝 任务:创建/更新/拆分子任务
🎬 妙记:搜索妙记/下载音视频/获取总结待办
📎 审批:查询审批实例/处理审批任务

四、集成到 Django 后台

4.1 模型加字段

# blog/models.py
class Article(models.Model):
    ...
    feishu_url = models.URLField('飞书云文档', max_length=500, blank=True)

迁移:

python manage.py makemigrations blog
python manage.py migrate

4.2 Admin 加 action

# blog/admin.py
from django.contrib import admin, messages
from django.utils.html import format_html
import subprocess, json

@admin.register(Article)
class ArticleAdmin(admin.ModelAdmin):
    list_display = ['title', 'sync_status_display', ...]
    readonly_fields = [..., 'feishu_url']
    actions = ['sync_to_feishu']

    def sync_status_display(self, obj):
        if obj.feishu_url:
            return format_html(
                '<a href="{}" target="_blank">'
                '<i class="bi bi-cloud-check"></i> 已同步</a>',
                obj.feishu_url
            )
        return '未同步'
    sync_status_display.short_description = '飞书状态'

    def sync_to_feishu(self, request, queryset):
        success = 0
        for article in queryset.filter(status='published'):
            md = f"# {article.title}\n\n{article.markdown_content}"
            result = subprocess.run(
                ['lark-cli', 'docs', '+create',
                 '--doc-format', 'markdown',
                 '--content', '-',
                 '--title', article.title,
                 '--as', 'user'],
                input=md, capture_output=True, text=True
            )
            if result.returncode == 0:
                data = json.loads(result.stdout)
                url = data['data']['document']['url']
                article.feishu_url = url
                article.save(update_fields=['feishu_url'])
                success += 1
        self.message_user(request, f'✅ 同步成功 {success} 篇', level=messages.SUCCESS)
    sync_to_feishu.short_description = '同步到飞书云文档(以你身份)'

4.3 使用流程

  1. Django 后台 → 文章列表
  2. 勾选多篇文章
  3. 操作 → 选择 "同步到飞书云文档"
  4. 点击 "执行"
  5. 几秒后看到 ✅ 提示 + 飞书链接

全程在浏览器里完成,不需要 SSH 进服务器。

五、踩过的坑

5.1 OAuth 字段名

# ❌ OAuth2 标准(飞书不支持)
{"client_id": ..., "client_secret": ...}

# ✅ 飞书 OAuth
{"app_id": ..., "app_secret": ...}

5.2 Scope 必须勾全

常见必需 scope:

docs:document:create
docs:document:write
docs:document:read
drive:drive
drive:file
space:document:retrieve  ← 这个容易漏
offline_access  ← 必须有,否则没 refresh_token

5.3 用户身份权限 vs 应用身份权限

飞书后台有两套权限: - 应用身份权限:App 自己用 - 用户身份权限:App 模拟用户时用

OAuth 时必须两套都勾。

5.4 lark-cli 安装路径

npx @larksuite/cli@latest install 经常失败(权限问题)。

直接:

sudo npm install -g @larksuite/cli --no-audit --no-fund

六、一键脚本

最终精简版 feishu_sync.py

import subprocess, json, os, sys
os.environ.setdefault('DJANGO_SETTINGS_MODULE', 'config.settings')
import django; django.setup()
from blog.models import Article

def sync_article(slug):
    art = Article.objects.get(slug=slug)
    md = f"# {art.title}\n\n{art.markdown_content}"
    r = subprocess.run(
        ['lark-cli', 'docs', '+create',
         '--doc-format', 'markdown',
         '--content', '-',
         '--title', art.title,
         '--as', 'user'],
        input=md, capture_output=True, text=True
    )
    if r.returncode == 0:
        data = json.loads(r.stdout)
        url = data['data']['document']['url']
        print(f'✅ {url}')

48 行搞定一切,比之前 200+ 行少 75%。

📚 思考题

Q1: 为什么要区分"应用身份"和"用户身份"? A1: 安全考虑。应用身份只能操作 App 自己的数据;用户身份模拟你操作你的数据。这和 OAuth 2.0 的设计哲学一致——不要给 App 过多授权。

Q2: user_access_token 会过期,怎么办? A2: 2 小时过期,但lark-cli 自动用 refresh_token 续期,你不用管。refresh_token 一周有效,过期后再走一遍 OAuth。

Q3: 文档所有者是谁,影响什么? A3: 决定谁能编辑/删除/转让。应用身份文档:你能"申请权限"编辑;用户身份文档:你直接能操作。

🔜 下一篇预告

飞书 + AI:让 lark-cli 接管日程、消息、任务

你以为 lark-cli 只是写文档?太天真了。

  • "约明天上午 10 点跟张三开会,30 分钟"
  • "找出跟张三的所有未读消息"
  • "把今天的会议记录整理成任务,分配给团队"

全部 lark-cli 一行命令搞定。下一篇展示我用 lark-cli 实现"日常 AI 助手"的完整工作流。


📱 关注公众号「网英的日常」,第一时间收到 lark-cli 实战系列。 读完有疑问?在公众号留言,我会逐条回复