知识库分享业务

目录

前言

「分享」在前端的本质不是"生成链接",而是用一整套「权限驱动 UI + 独立访问流程」,把一次"受控授权"安全、可撤销、可审计地跑通。

一、认清「分享」

分享 ≠ 复制一个链接

「分享」的本质:

  • "分享"是一次"受控授权的内容访问能力下放"

拆开看它同时包含:

维度 含义
内容 某个知识实体(文档 / 目录 / 多文档集合)
身份 谁在访问(登录用户 / 匿名用户 / 外部成员)
权限 能做什么(看 / 评论 / 编辑 / 下载)
范围 分享到哪里(组织内 / 组织外)
时间 有效期、是否可撤销
风险 泄露、滥用、越权、审计

分享不是 UI 功能,是权限系统的一种"特殊出口"

二、知识库分享的业务类型全景

1、按"分享对象"分类

(1)、内部分享(组织内)

  • 同公司 / 同项目成员
  • 通常 依赖原有账号体系
  • 权限粒度精细

特点:

  • 不需要 token
  • 直接基于 userId / role / group

(2)、外部分享(组织外)

  • 发给客户、合作方、朋友
  • 通常 基于链接 + token
  • 可匿名访问

特点:

  • 核心是 Share Link
  • 风险最高
  • 需要完整风控设计

2、按"分享内容"分类

类型 说明 风险
单文档 最常见
文档目录 一组知识
搜索结果 动态内容
快照版本 固定版本
实时版本 内容变化同步

企业级系统 推荐"快照分享" + "可选实时"

3、按"权限能力"分类(非常关键)

权限 含义
read 只读
comment 评论
edit 编辑
copy 复制内容
export 导出
download 下载附件

分享不是"有/没有",而是权限集合

三、分享业务的核心模型(重点)

1、分享实体模型(Share Model)

这是整个系统的"灵魂"。

typescript 复制代码
Share {
  shareId: string
  resourceType: 'doc' | 'folder'
  resourceId: string

  permission: {
    read: boolean
    comment: boolean
    edit: boolean
  }

  scope: 'internal' | 'external'
  token?: string

  expiresAt?: number
  passwordHash?: string

  createdBy: userId
  revoked: boolean
}

📌 前端一定要理解:

  • 分享不是资源本身的属性,而是一条独立的授权记录

2、分享访问模型(Access Model)

访问分享时的真实流程:

typescript 复制代码
URL
 ↓
shareId / token
 ↓
校验 share 是否有效
 ↓
解析权限
 ↓
加载资源(按权限裁剪)
 ↓
前端渲染

四、前端在分享业务中的真实职责

前端在分享业务中的真实职责可不是"展示链接"那么简单。

前端不是"安全的最终防线",但它是:

  • 第一层权限体验控制器

前端需要做到:

  • 权限感知
  • 权限裁剪
  • 权限引导
  • 权限兜底

五、「分享」在前端的全流程总览(企业级)

typescript 复制代码
① 权限校验 → 是否可分享
② 获取 / 创建 Share 实体
③ 分享配置(权限 / 有效期 / 密码)
④ 分享面板(链接 / 二维码 / 撤销)
⑤ 外部访问路由(独立 Share App)
⑥ 分享访问鉴权(token / 密码)
⑦ 权限驱动渲染(只读 / 编辑)
⑧ 风控与交互限制
⑨ 访问统计展示

下面进行逐步的拆解剖析。

1、分享入口(权限驱动)

业务目标:

  • 只有"允许分享的人"才能看到分享入口

前端原则:

  • ❌ 不要"点了再提示没权限"
  • ✅ 权限决定 UI 是否出现

示例:

typescript 复制代码
export function ShareEntry({ canShare, onOpen }) {
  if (!canShare) return null
  return <Button onClick={onOpen}>分享</Button>
}

要点:

  • canShare 来自 后端权限接口
  • 前端只是"展示权限结果"

2、获取 / 创建 Share 实体

前端要做什么?

  • 选择权限
  • 选择有效期
  • 是否设置密码
  • 是否允许编辑
  • 是否允许下载

关键认知:

  • Share 是独立实体,不是文档的字段

流程:

typescript 复制代码
点击分享
 → GET /share/by-resource
 → 若不存在
 → POST /share/create

示例:

typescript 复制代码
async function openShare(resourceId: string) {
  let share = await api.getShareByResource(resourceId)
  if (!share) {
    share = await api.createShare(resourceId)
  }
  return share
}

要点:

  • 永远不要在前端生成 token
  • 所有安全逻辑必须在后端
  • shareId 是系统级授权凭证

3、分享配置(权限 / 期限 / 密码)

分享配置是"授权边界"

常见配置:

说明
read 是否可查看
edit 是否可编辑
expiresAt 过期时间
password 访问密码

实战示例(配置表单):

typescript 复制代码
function ShareConfig({ share, onChange }) {
  return (
    <>
      <Checkbox
        checked={share.permission.edit}
        onChange={e =>
          onChange({ edit: e.target.checked })
        }
      >
        允许编辑
      </Checkbox>

      <DatePicker
        value={share.expiresAt}
        onChange={date => onChange({ expiresAt: date })}
      />

      <Input.Password
        placeholder="访问密码(可选)"
        onChange={e => onChange({ password: e.target.value })}
      />
    </>
  )
}

❌ 严禁:

  • 前端做权限判断
  • 前端 hash 密码

4、分享面板(控制台,而不是弹窗)

分享面板 = 分享管理后台

必备能力:

  • 分享链接复制(PC / IM)
  • 二维码分享(移动端 / 跨设备)
  • 权限修改(实时生效)
  • 撤销分享(立即失效)
  • 访问统计入口

前端难点:

  • 权限修改 = 立即生效
  • 多人协作下的状态同步

要求:

  • 撤销必须 立刻失效
  • 不允许"缓存权限"

通常情况下可以分为两种分享的功能场景:

  • 一般的分享面板
  • 有二维码的分享面板

(1)、一般的分享面板

一般的分享面板比较简单。

典型示例:

typescript 复制代码
function SharePanel({ share }) {
  return (
    <>
      <Input value={share.link} readOnly />
      <Button onClick={() => copy(share.link)}>复制</Button>

      <Button danger onClick={() => api.revokeShare(share.id)}>
        撤销分享
      </Button>
    </>
  )
}

(2)、带「二维码」的分享面板

①、认识二维码分享

二维码 ≠ 额外功能

  • 二维码 = 分享链接的另一种表现形式

二维码分享的严格要求:

  • 二维码 只编码 share.link
  • 权限、有效期、撤销 完全继承 share
  • 撤销分享后,二维码 立即失效
  • 二维码本身 不携带任何权限信息
②、企业级细节(真正拉开水平的点)

二维码是否需要"重新生成"?

❌ 不需要

✅ 二维码永远只依赖 share.link

  • 权限变化 ≠ 链接变化
  • 权限变化 = 后端实时生效

撤销分享后的行为(非常关键)

行为 正确结果
点击链接 已撤销
扫描二维码 已撤销
旧页面刷新 已撤销

前端 不能缓存 share 状态

是否允许"下载二维码图片"?

企业级建议:可选

html 复制代码
<QRCodeCanvas ref={qrRef} />

<Button onClick={downloadQR}>
  下载二维码
</Button>

⚠️ 但要注意:

  • 二维码下载 = 扩大传播能力
  • 金融 / 内部系统往往禁用
③、实战示例

技术选型(前端)

  • React + TypeScript
  • UI:Ant Design(你用别的也一样)
  • 二维码:qrcode.react

安装 qrcode.react

typescript 复制代码
npm install qrcode.react

组件实现

typescript 复制代码
import { QRCodeCanvas } from 'qrcode.react'
import { Button, Input, Divider, Tooltip } from 'antd'

interface SharePanelProps {
  share: {
    id: string
    link: string
    permission: {
      edit: boolean
    }
  }
}

export function SharePanel({ share }: SharePanelProps) {
  return (
    <div className="share-panel">
      {/* 一、分享链接 */}
      <section>
        <div className="label">分享链接</div>
        <Input.Group compact>
          <Input style={{ width: 'calc(100% - 80px)' }} value={share.link} readOnly />
          <Button onClick={() => copyToClipboard(share.link)}>复制</Button>
        </Input.Group>
      </section>

      <Divider />

      {/* 二、二维码分享 */}
      <section>
        <div className="label">二维码分享</div>
        <div className="qr-wrapper">
          <QRCodeCanvas
            value={share.link}
            size={128}
            level="M"
            includeMargin
          />
          <div className="qr-tip">
            使用手机扫码访问
          </div>
        </div>
      </section>

      <Divider />

      {/* 三、分享操作 */}
      <section className="actions">
        <Tooltip title="撤销后,链接和二维码将立即失效">
          <Button danger onClick={() => api.revokeShare(share.id)}>
            撤销分享
          </Button>
        </Tooltip>
      </section>
    </div>
  )
}

样式(简化示例)

css 复制代码
.share-panel {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.label {
  font-size: 13px;
  color: #666;
  margin-bottom: 4px;
}

.qr-wrapper {
  display: flex;
  align-items: center;
  gap: 16px;
}

.qr-tip {
  font-size: 12px;
  color: #999;
}

5、访问分享页面(独立 Share App)

页面类型:

页面 用户
share-view 外部访客
share-edit 内部成员
share-deny 权限不足
share-expired 已过期

正确路由设计:

typescript 复制代码
/share/:shareId

❌ 禁止:

  • 不复用后台 layout
  • 不加载主系统权限体系

页面状态划分:

typescript 复制代码
if (revoked)   → 已撤销
if (expired)  → 已过期
if (needPwd)  → 密码页
else          → 内容页

6、分享访问鉴权(密码 / Token)

正确的密码访问模型:

typescript 复制代码
输入密码
 → POST /share/verify
 → 返回短期 accessToken
 → sessionStorage 保存

实战示例:

typescript 复制代码
async function verifySharePassword(shareId, password) {
  const token = await api.verifyPassword(shareId, password)
  sessionStorage.setItem('shareToken', token)
}

📌 为什么是 sessionStorage?

  • 关闭标签页即失效
  • 防止长期滥用

7、权限驱动渲染(核心)

❗️所有 UI 必须权限驱动

包括:

  • 按钮
  • 快捷键
  • 右键菜单
  • 导出能力

示例:

typescript 复制代码
function ShareContent({ permission }) {
  if (!permission.read) return <NoPermission />

  return permission.edit
    ? <Editor />
    : <ReadonlyViewer />
}

8、风控与交互限制(前端层)

前端能做的(但不是安全保证):

行为 手段
复制 user-select: none
下载 不渲染按钮
打印 @media print { display: none }
typescript 复制代码
.readonly {
  user-select: none;
}

❗️ 重要认知:

  • 前端只能"提高作恶成本",不能"绝对防护"

9、访问统计展示

前端仅展示,不做统计:

typescript 复制代码
<Statistic title="访问次数" value={share.viewCount} />
<Statistic title="最近访问" value={share.lastVisitAt} />

六、企业级分享前端实战示例

前端实现的三大"坑":

  1. 只在前端做权限控制(致命错误)
    • 前端只能"裁剪 UI",不能"决定权限"
  2. 分享页面复用主系统页面
    • 外部分享页面 应该是独立壳
  3. 忽略"撤销分享"的即时性
    • 撤销必须立即生效,不能靠缓存

1、项目结构

typescript 复制代码
share/
 ├── ShareEntry.tsx
 ├── SharePanel.tsx
 ├── SharePage.tsx
 ├── PasswordGate.tsx
 ├── ShareContent.tsx
 ├── useShare.ts
 └── share.api.ts

2、SharePage(完整)

typescript 复制代码
export function SharePage() {
  const { share, permission, loading } = useShare()

  if (loading) return <Spin />

  if (share.revoked) return <Revoked />
  if (share.expired) return <Expired />
  if (share.needPassword) return <PasswordGate />

  return <ShareContent permission={permission} />
}

3、useShare(核心 Hook)

typescript 复制代码
export function useShare() {
  const { shareId } = useParams()
  const token = sessionStorage.getItem('shareToken')

  const { data } = useRequest(() =>
    api.getShareDetail(shareId, token)
  )

  return {
    share: data.share,
    permission: data.permission,
    loading: !data
  }
}

七、外部分享的安全设计(重点)

1、Token 设计原则

  • 长度足够(128bit+)
  • 不可猜测
  • 可撤销
  • 可过期

前端:

  • 只当字符串用
  • 不解析、不拼接、不计算

2、密码访问(Share Password)

前端流程:

typescript 复制代码
输入密码
 → 后端校验
 → 返回短期 access token
 → 前端缓存(sessionStorage)

❌ 禁止:

  • 前端 hash 密码
  • 本地长期存储 token

3、防止"链接滥用"

前端配合点:

  • 单设备限制提示
  • 异常访问提示
  • CAPTCHA 触发

八、分享统计与审计(企业级)

前端能做什么?

  • PV / UV
  • 来源(referrer)
  • 地域(粗粒度)
  • 最近访问列表

展示示例:

  • "该分享被访问 37 次"
  • "最近访问:2 分钟前"

九、知识库分享 vs 普通链接分享(对比)

维度 普通链接 知识库分享
是否可撤销
权限 精细
有效期
审计
风险控制