智联笔记项目——251021为分享功能添加有效期

注:在原有增删改查,导出分享功能上强化分享功能,为分享功能添加了有效期。

在日常使用笔记系统时,我们经常需要分享笔记给他人,但永久有效的分享链接存在安全隐患。本文将介绍如何在原有笔记系统基础上,新增分享链接的有效期限制功能,包括后端逻辑和前端交互的实现。

功能背景:
原有笔记系统已支持生成分享链接(分为 "只读" 和 "可编辑" 权限),但链接永久有效。新增需求如下:

  1. 支持设置分享链接的有效期(1 分钟、3 天、7 天、30 天、永久)
  2. 过期链接自动失效,无法访问
  3. 自动清理过期的分享链接,减少冗余数据

后端实现(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)  # uuid4
    permission = 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)  # uuid4
    permission = 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.json
    note_id = data.get('note_id')
    permission = data.get('permission', 'view')
    if permission not in ('view', 'edit'):
        return jsonify({'error': '无效的权限类型'}), 400
    note = 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.json
    note_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': '无效的权限类型'}), 400

    note = 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  # 返回过期时间
    })
关键修改: * 接收expires_in(有效期数值)和expires_unit(单位:minutes/days)参数 * 根据参数计算expire_at并存储 * 返回expire_at给前端,用于显示有效期信息

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">&times;</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">&times;</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>
关键修改: * 新增share-expiry下拉框,提供多种有效期选项 * 新增expiry-info区域,用于显示链接的过期时间

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字段跟踪过期时间,结合前端选择和后端验证,实现完整的有效期管理流程。
posted @ 2025-10-21 13:13  黄思宇  阅读(9)  评论(0)    收藏  举报