目录
- 前言
- 一、认清「分享」
- 二、知识库分享的业务类型全景
- 三、分享业务的核心模型(重点)
-
- [1、分享实体模型(Share Model)](#1、分享实体模型(Share Model))
- [2、分享访问模型(Access Model)](#2、分享访问模型(Access Model))
- 四、前端在分享业务中的真实职责
- 五、「分享」在前端的全流程总览(企业级)
-
- 1、分享入口(权限驱动)
- [2、获取 / 创建 Share 实体](#2、获取 / 创建 Share 实体)
- [3、分享配置(权限 / 期限 / 密码)](#3、分享配置(权限 / 期限 / 密码))
- 4、分享面板(控制台,而不是弹窗)
- [5、访问分享页面(独立 Share App)](#5、访问分享页面(独立 Share App))
- [6、分享访问鉴权(密码 / Token)](#6、分享访问鉴权(密码 / Token))
- 7、权限驱动渲染(核心)
- 8、风控与交互限制(前端层)
- 9、访问统计展示
- 六、企业级分享前端实战示例
-
- 1、项目结构
- 2、SharePage(完整)
- [3、useShare(核心 Hook)](#3、useShare(核心 Hook))
- 七、外部分享的安全设计(重点)
-
- [1、Token 设计原则](#1、Token 设计原则)
- [2、密码访问(Share Password)](#2、密码访问(Share Password))
- 3、防止"链接滥用"
- 八、分享统计与审计(企业级)
- [九、知识库分享 vs 普通链接分享(对比)](#九、知识库分享 vs 普通链接分享(对比))
前言
「分享」在前端的本质不是"生成链接",而是用一整套「权限驱动 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} />
六、企业级分享前端实战示例
前端实现的三大"坑":
- 只在前端做权限控制(致命错误)
- 前端只能"裁剪 UI",不能"决定权限"
- 分享页面复用主系统页面
- 外部分享页面 应该是独立壳
- 忽略"撤销分享"的即时性
- 撤销必须立即生效,不能靠缓存
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 普通链接分享(对比)
| 维度 | 普通链接 | 知识库分享 |
|---|---|---|
| 是否可撤销 | ❌ | ✅ |
| 权限 | 无 | 精细 |
| 有效期 | 无 | 有 |
| 审计 | 无 | 有 |
| 风险控制 | 无 | 强 |