目录
- 前言
- 一、认清「分享」
- 二、知识库分享的业务类型全景
- 1、按“分享对象”分类
- (1)、内部分享(组织内)
- (2)、外部分享(组织外)
- 2、按“分享内容”分类
- 3、按“权限能力”分类(非常关键)
- 三、分享业务的核心模型(重点)
- 1、分享实体模型(Share Model)
- 2、分享访问模型(Access Model)
- 四、前端在分享业务中的真实职责
- 五、「分享」在前端的全流程总览(企业级)
- 1、分享入口(权限驱动)
- 2、获取 / 创建 Share 实体
- 3、分享配置(权限 / 期限 / 密码)
- 4、分享面板(控制台,而不是弹窗)
- (1)、一般的分享面板
- (2)、带「二维码」的分享面板
- ①、认识二维码分享
- ②、企业级细节(真正拉开水平的点)
- ③、实战示例
- 5、访问分享页面(独立 Share App)
- 6、分享访问鉴权(密码 / Token)
- 7、权限驱动渲染(核心)
- 8、风控与交互限制(前端层)
- 9、访问统计展示
- 六、企业级分享前端实战示例
- 1、项目结构
- 2、SharePage(完整)
- 3、useShare(核心 Hook)
- 七、外部分享的安全设计(重点)
- 1、Token 设计原则
- 2、密码访问(Share Password)
- 3、防止“链接滥用”
- 八、分享统计与审计(企业级)
- 九、知识库分享 vs 普通链接分享(对比)
前言
「分享」在前端的本质不是“生成链接”,而是用一整套「权限驱动 UI + 独立访问流程」,把一次“受控授权”安全、可撤销、可审计地跑通。
一、认清「分享」
分享 ≠ 复制一个链接
「分享」的本质:
- “分享”是一次“受控授权的内容访问能力下放”
拆开看它同时包含:
| 维度 | 含义 |
|---|---|
| 内容 | 某个知识实体(文档 / 目录 / 多文档集合) |
| 身份 | 谁在访问(登录用户 / 匿名用户 / 外部成员) |
| 权限 | 能做什么(看 / 评论 / 编辑 / 下载) |
| 范围 | 分享到哪里(组织内 / 组织外) |
| 时间 | 有效期、是否可撤销 |
| 风险 | 泄露、滥用、越权、审计 |
分享不是 UI 功能,是权限系统的一种“特殊出口”
二、知识库分享的业务类型全景
1、按“分享对象”分类
(1)、内部分享(组织内)
- 同公司 / 同项目成员
- 通常 依赖原有账号体系
- 权限粒度精细
特点:
- 不需要 token
- 直接基于 userId / role / group
(2)、外部分享(组织外)
- 发给客户、合作方、朋友
- 通常 基于链接 + token
- 可匿名访问
特点:
- 核心是 Share Link
- 风险最高
- 需要完整风控设计
2、按“分享内容”分类
| 类型 | 说明 | 风险 |
|---|---|---|
| 单文档 | 最常见 | 低 |
| 文档目录 | 一组知识 | 中 |
| 搜索结果 | 动态内容 | 高 |
| 快照版本 | 固定版本 | 低 |
| 实时版本 | 内容变化同步 | 中 |
企业级系统 推荐“快照分享” + “可选实时”
3、按“权限能力”分类(非常关键)
| 权限 | 含义 |
|---|---|
| read | 只读 |
| comment | 评论 |
| edit | 编辑 |
| copy | 复制内容 |
| export | 导出 |
| download | 下载附件 |
分享不是“有/没有”,而是权限集合
三、分享业务的核心模型(重点)
1、分享实体模型(Share Model)
这是整个系统的“灵魂”。
Share{shareId:stringresourceType:'doc'|'folder'resourceId:stringpermission:{read:booleancomment:booleanedit:boolean}scope:'internal'|'external'token?:stringexpiresAt?:numberpasswordHash?:stringcreatedBy:userId revoked:boolean}📌 前端一定要理解:
- 分享不是资源本身的属性,而是一条独立的授权记录
2、分享访问模型(Access Model)
访问分享时的真实流程:
URL↓ shareId/token ↓ 校验 share 是否有效 ↓ 解析权限 ↓ 加载资源(按权限裁剪) ↓ 前端渲染四、前端在分享业务中的真实职责
前端在分享业务中的真实职责可不是“展示链接”那么简单。
前端不是“安全的最终防线”,但它是:
- 第一层权限体验控制器
前端需要做到:
- 权限感知
- 权限裁剪
- 权限引导
- 权限兜底
五、「分享」在前端的全流程总览(企业级)
① 权限校验 → 是否可分享 ② 获取/创建 Share 实体 ③ 分享配置(权限/有效期/密码) ④ 分享面板(链接/二维码/撤销) ⑤ 外部访问路由(独立 Share App) ⑥ 分享访问鉴权(token/密码) ⑦ 权限驱动渲染(只读/编辑) ⑧ 风控与交互限制 ⑨ 访问统计展示下面进行逐步的拆解剖析。
1、分享入口(权限驱动)
业务目标:
- 只有“允许分享的人”才能看到分享入口
前端原则:
- ❌ 不要“点了再提示没权限”
- ✅ 权限决定 UI 是否出现
示例:
exportfunctionShareEntry({canShare,onOpen}){if(!canShare)returnnullreturn<Button onClick={onOpen}>分享</Button>}要点:
- canShare 来自 后端权限接口
- 前端只是“展示权限结果”
2、获取 / 创建 Share 实体
前端要做什么?
- 选择权限
- 选择有效期
- 是否设置密码
- 是否允许编辑
- 是否允许下载
关键认知:
- Share 是独立实体,不是文档的字段
流程:
点击分享 →GET/share/by-resource → 若不存在 →POST/share/create示例:
asyncfunctionopenShare(resourceId:string){letshare=awaitapi.getShareByResource(resourceId)if(!share){share=awaitapi.createShare(resourceId)}returnshare}要点:
- 永远不要在前端生成 token
- 所有安全逻辑必须在后端
- shareId 是系统级授权凭证
3、分享配置(权限 / 期限 / 密码)
分享配置是“授权边界”
常见配置:
| 项 | 说明 |
|---|---|
| read | 是否可查看 |
| edit | 是否可编辑 |
| expiresAt | 过期时间 |
| password | 访问密码 |
实战示例(配置表单):
functionShareConfig({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)、一般的分享面板
一般的分享面板比较简单。
典型示例:
functionSharePanel({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 状态
是否允许“下载二维码图片”?
企业级建议:可选
<QRCodeCanvasref={qrRef}/><ButtononClick={downloadQR}>下载二维码</Button>⚠️ 但要注意:
- 二维码下载 = 扩大传播能力
- 金融 / 内部系统往往禁用
③、实战示例
技术选型(前端)
- React + TypeScript
- UI:Ant Design(你用别的也一样)
- 二维码:qrcode.react
安装 qrcode.react:
npm install qrcode.react组件实现:
import{QRCodeCanvas}from'qrcode.react'import{Button,Input,Divider,Tooltip}from'antd'interfaceSharePanelProps{share:{id:stringlink:stringpermission:{edit:boolean}}}exportfunctionSharePanel({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>)}样式(简化示例):
.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 | 已过期 |
正确路由设计:
/share/:shareId❌ 禁止:
- 不复用后台 layout
- 不加载主系统权限体系
页面状态划分:
if(revoked)→ 已撤销if(expired)→ 已过期if(needPwd)→ 密码页else→ 内容页6、分享访问鉴权(密码 / Token)
正确的密码访问模型:
输入密码 →POST/share/verify → 返回短期 accessToken → sessionStorage 保存实战示例:
asyncfunctionverifySharePassword(shareId,password){consttoken=awaitapi.verifyPassword(shareId,password)sessionStorage.setItem('shareToken',token)}📌 为什么是 sessionStorage?
- 关闭标签页即失效
- 防止长期滥用
7、权限驱动渲染(核心)
❗️所有 UI 必须权限驱动
包括:
- 按钮
- 快捷键
- 右键菜单
- 导出能力
示例:
functionShareContent({permission}){if(!permission.read)return<NoPermission/>returnpermission.edit?<Editor/>:<ReadonlyViewer/>}8、风控与交互限制(前端层)
前端能做的(但不是安全保证):
| 行为 | 手段 |
|---|---|
| 复制 | user-select: none |
| 下载 | 不渲染按钮 |
| 打印 | @media print { display: none } |
.readonly{user-select:none;}❗️ 重要认知:
- 前端只能“提高作恶成本”,不能“绝对防护”
9、访问统计展示
前端仅展示,不做统计:
<Statistic title="访问次数"value={share.viewCount}/><Statistic title="最近访问"value={share.lastVisitAt}/>六、企业级分享前端实战示例
前端实现的三大“坑”:
- 只在前端做权限控制(致命错误)
- 前端只能“裁剪 UI”,不能“决定权限”
- 分享页面复用主系统页面
- 外部分享页面 应该是独立壳
- 忽略“撤销分享”的即时性
- 撤销必须立即生效,不能靠缓存
1、项目结构
share/├── ShareEntry.tsx ├── SharePanel.tsx ├── SharePage.tsx ├── PasswordGate.tsx ├── ShareContent.tsx ├── useShare.ts └── share.api.ts2、SharePage(完整)
exportfunctionSharePage(){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)
exportfunctionuseShare(){const{shareId}=useParams()consttoken=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)
前端流程:
输入密码 → 后端校验 → 返回短期 access token → 前端缓存(sessionStorage)❌ 禁止:
- 前端 hash 密码
- 本地长期存储 token
3、防止“链接滥用”
前端配合点:
- 单设备限制提示
- 异常访问提示
- CAPTCHA 触发
八、分享统计与审计(企业级)
前端能做什么?
- PV / UV
- 来源(referrer)
- 地域(粗粒度)
- 最近访问列表
展示示例:
- “该分享被访问 37 次”
- “最近访问:2 分钟前”
九、知识库分享 vs 普通链接分享(对比)
| 维度 | 普通链接 | 知识库分享 |
|---|---|---|
| 是否可撤销 | ❌ | ✅ |
| 权限 | 无 | 精细 |
| 有效期 | 无 | 有 |
| 审计 | 无 | 有 |
| 风险控制 | 无 | 强 |