注:在原有增删改查,导出分享功能上强化分享功能,为分享功能添加了有效期。
在日常使用笔记系统时,我们经常需要分享笔记给他人,但永久有效的分享链接存在安全隐患。本文将介绍如何在原有笔记系统基础上,新增分享链接的有效期限制功能,包括后端逻辑和前端交互的实现。
功能背景:
原有笔记系统已支持生成分享链接(分为 "只读" 和 "可编辑" 权限),但链接永久有效。新增需求如下:
- 支持设置分享链接的有效期(1 分钟、3 天、7 天、30 天、永久)
- 过期链接自动失效,无法访问
- 自动清理过期的分享链接,减少冗余数据
后端实现(Python + Flask)
1. 数据模型修改
首先在分享链接模型中添加过期时间字段,修改ShareLink类:
点击查看代码
# 原有代码
class ShareLink(db.Model):__tablename__ = 'share_link'id = db.Column(db.Integer, primary_key=True)note_id = db.Column(db.Integer, db.ForeignKey('note.id', ondelete="CASCADE"), nullable=False)token = db.Column(db.String(36), unique=True, nullable=False) # uuid4permission = db.Column(db.String(10), nullable=False) # 'view' / 'edit'created_at = db.Column(db.DateTime, default=datetime.now)note = db.relationship('Note', backref='share_links')# 新增后代码
class ShareLink(db.Model):__tablename__ = 'share_link'id = db.Column(db.Integer, primary_key=True)note_id = db.Column(db.Integer, db.ForeignKey('note.id', ondelete="CASCADE"), nullable=False)token = db.Column(db.String(36), unique=True, nullable=False) # uuid4permission = db.Column(db.String(10), nullable=False) # 'view' / 'edit'created_at = db.Column(db.DateTime, default=datetime.now)expire_at = db.Column(db.DateTime) # 新增:过期时间,None表示永久有效note = db.relationship('Note', backref='share_links')
关键修改:
- 添加expire_at字段存储过期时间,None表示永久有效。
2. 生成分享链接 API 优化
修改创建分享链接的接口,支持接收有效期参数并计算过期时间:
点击查看代码
# 原有代码
@app.route('/api/share', methods=['POST'])
def create_share():data = request.jsonnote_id = data.get('note_id')permission = data.get('permission', 'view')if permission not in ('view', 'edit'):return jsonify({'error': '无效的权限类型'}), 400note = Note.query.get_or_404(note_id)token = generate_token()link = ShareLink(note_id=note.id, token=token, permission=permission)db.session.add(link)db.session.commit()share_url = url_for('share_page', token=token, _external=True)return jsonify({'share_url': share_url, 'permission': permission})# 新增后代码
@app.route('/api/share', methods=['POST'])
def create_share():data = request.jsonnote_id = data.get('note_id')permission = data.get('permission', 'view')expires_in = data.get('expires_in') # 数值(如1、3)expires_unit = data.get('expires_unit', 'days') # 单位(minutes/days)if permission not in ('view', 'edit'):return jsonify({'error': '无效的权限类型'}), 400note = Note.query.get_or_404(note_id)token = generate_token()expire_at = None# 计算过期时间(支持分钟和天)if expires_in is not None:if expires_unit == 'minutes':expire_at = datetime.now() + timedelta(minutes=int(expires_in))else:expire_at = datetime.now() + timedelta(days=int(expires_in))link = ShareLink(note_id=note.id,token=token,permission=permission,expire_at=expire_at # 保存过期时间)db.session.add(link)db.session.commit()share_url = url_for('share_page', token=token, _external=True)return jsonify({'share_url': share_url,'permission': permission,'expire_at': expire_at.isoformat() if expire_at else None # 返回过期时间})
3. 分享链接验证逻辑
在访问分享链接时,新增过期检查:
点击查看代码
# 原有代码
def get_share_by_token(token):link = ShareLink.query.filter_by(token=token).first()if not link:abort(404, description='分享链接不存在')return link# 新增后代码
def get_share_by_token(token):link = ShareLink.query.filter_by(token=token).first()if not link:abort(404, description='分享链接不存在')# 新增:检查是否过期if link.expire_at and link.expire_at < datetime.now():abort(403, description='分享链接已过期')return link
关键修改:
- 添加expire_at < datetime.now()的过期判断,过期则返回 403 错误。
4. 自动清理过期链接
使用定时任务定期清理过期链接,避免数据冗余:
点击查看代码
# 新增代码
def init_scheduler(app):scheduler.init_app(app)# 每天凌晨清理过期链接@scheduler.task('cron', hour=0)def clean_expired_links():with app.app_context():expired_links = ShareLink.query.filter(ShareLink.expire_at < datetime.now()).all()for link in expired_links:db.session.delete(link)db.session.commit()scheduler.start()
前端实现(HTML + JavaScript)
1. 分享弹窗添加有效期选择
在分享弹窗中新增有效期下拉框,让用户选择链接有效期:
点击查看代码
<!-- 原有代码 -->
<div id="share-modal" class="modal"><div class="modal-content"><span class="close" id="share-close">×</span><h3>生成分享链接</h3><select id="share-permission" style="width:100%;margin-top:8px;"><option value="view">只读(view)</option><option value="edit">可编辑(edit)</option></select><input type="text" id="share-link" readonly placeholder="生成的链接会显示在这里"><button class="confirm" id="generate-link-btn">生成链接</button><button class="cancel" id="copy-link-btn">复制链接</button></div>
</div><!-- 新增后代码 -->
<div id="share-modal" class="modal"><div class="modal-content"><span class="close" id="share-close">×</span><h3>生成分享链接</h3><select id="share-permission" style="width:100%;margin-top:8px;"><option value="view">只读(view)</option><option value="edit">可编辑(edit)</option></select><!-- 新增:有效期选择 --><select id="share-expiry" style="width:100%;margin-top:8px;"><option value="1m">1分钟(测试用)</option><option value="3">3天</option><option value="7" selected>7天</option><option value="30">30天</option><option value="">永久有效</option></select><input type="text" id="share-link" readonly placeholder="生成的链接会显示在这里"><div id="expiry-info" style="color:#666;font-size:12px;margin:8px 0;">链接有效期信息将在这里显示</div><button class="confirm" id="generate-link-btn">生成链接</button><button class="cancel" id="copy-link-btn">复制链接</button></div>
</div>
2. 生成链接时传递有效期参数
修改前端生成分享链接的逻辑,将有效期参数传递给后端:
点击查看代码
// 原有代码
async function generateShareLink() {if (!currentNoteId) return;const permission = sharePermissionSelect.value;try {const res = await fetch('/api/share', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({ note_id: currentNoteId, permission })});// ...后续逻辑} catch (e) {// ...错误处理}
}// 新增后代码
async function generateShareLink() {if (!currentNoteId) return;const permission = sharePermissionSelect.value;const expiryValue = shareExpirySelect.value; // 获取选中的有效期值(如"1m"、"3"等)// 解析有效期(支持分钟和天)let expiresIn = null;let unit = 'days'; // 默认单位:天if (expiryValue.includes('m')) {// 处理分钟(如"1m")expiresIn = parseInt(expiryValue.replace('m', ''));unit = 'minutes';} else if (expiryValue) {// 处理天(如"3")expiresIn = parseInt(expiryValue);}try {const res = await fetch('/api/share', {method: 'POST',headers: { 'Content-Type': 'application/json' },body: JSON.stringify({note_id: currentNoteId,permission,expires_in: expiresIn,expires_unit: unit // 传递单位(minutes/days)})});if (!res.ok) {const data = await res.json();throw new Error(data.error || '生成链接失败');}const data = await res.json();shareLinkInput.value = data.share_url;// 显示有效期信息let expiryText = '永久有效';if (data.expire_at) {const expireDate = new Date(data.expire_at);expiryText = `有效期至: ${expireDate.toLocaleString()}`;}document.getElementById('expiry-info').textContent = expiryText;showToast('链接生成成功');} catch (e) {console.error('生成分享链接失败:', e);showToast(e.message || '生成链接失败');}
}
-
解析share-expiry的值,区分分钟(含 "m")和天(纯数字)
-
向后端传递expires_in(数值)和expires_unit(单位)
-
接收后端返回的expire_at,格式化后显示在expiry-info区域
功能效果
-
用户点击 "分享" 按钮后,可选择权限和有效期
-
生成链接时,前端显示具体的过期时间(如 "有效期至:2024-05-20 12:00:00")
-
过期链接访问时,后端返回 403 错误("分享链接已过期")
-
系统每天自动清理过期链接,保持数据整洁
总结与扩展
通过上述修改,我们为笔记系统的分享功能添加了有效期限制,提升了安全性。后续可扩展的方向:
- 支持自定义有效期(如用户输入具体天数)
- 分享链接列表显示,支持手动提前失效
- 过期前提醒用户链接即将失效
该功能的核心是通过expire_at字段跟踪过期时间,结合前端选择和后端验证,实现完整的有效期管理流程。