智联笔记项目——251022登录注册、后端管理及内容类型处理优化

登录注册、后端管理及内容类型处理优化
在笔记系统的迭代中,基于原有代码,新增了登录注册功能、后端管理模块,并解决了 Markdown 与富文本内容混淆的问题。
本文将详细介绍这些功能的实现思路与代码修改细节,适合记录系统迭代过程。

功能背景与核心目标
原有系统局限
此前的系统(基于 share (4)、index (4)、app (4))存在以下关键问题:

  1. 无用户认证机制,所有用户共用同一权限,存在数据安全隐患
  2. 缺乏后端管理能力,无法对笔记、用户进行批量管理
  3. Markdown 与富文本内容渲染逻辑混乱(如富文本含 HTML 标签被误解析为 Markdown,或 Markdown 语法未正确转换)

一、登录注册功能实现

1. 后端用户模型与认证逻辑

数据模型修改
原有app中无用户模型,本次新增User表及认证相关字段:

点击查看代码
新增代码(app.py)
from flask_login import UserMixin
from werkzeug.security import generate_password_hash, check_password_hash

# 原有app中仅包含Note、ShareLink模型,新增User模型
class User(UserMixin, db.Model):
    __tablename__ = 'user'
    id = db.Column(db.Integer, primary_key=True)
    username = db.Column(db.String(50), unique=True, nullable=False)  # 用户名
    password_hash = db.Column(db.String(128), nullable=False)  # 密码哈希
    email = db.Column(db.String(100), unique=True, nullable=False)
    role = db.Column(db.String(20), default='user')  # 角色:user/admin
    created_at = db.Column(db.DateTime, default=datetime.now)
    
    # 密码加密存储
    def set_password(self, password):
        self.password_hash = generate_password_hash(password)
    
    # 密码验证
    def check_password(self, password):
        return check_password_hash(self.password_hash, password)

# 关联用户与笔记(修改Note模型,app(4)中原Note无用户关联)
class Note(db.Model):
    __tablename__ = 'note'
    # ...原有字段(id、title、content、type等)
    user_id = db.Column(db.Integer, db.ForeignKey('user.id', ondelete="CASCADE"), nullable=False)  # 新增:关联用户
    user = db.relationship('User', backref='notes')  

新增:用户的笔记列表
认证接口实现
新增注册、登录、登出接口,基于 Flask-Login 管理用户会话:

点击查看代码
新增代码(app.py)
from flask_login import LoginManager, login_user, login_required, logout_user, current_user

# 初始化登录管理器
login_manager = LoginManager(app)
login_manager.login_view = 'login'

@login_manager.user_loader
def load_user(user_id):
    return User.query.get(int(user_id))

# 注册接口
@app.route('/api/register', methods=['POST'])
def register():
    data = request.json
    username = data.get('username')
    password = data.get('email')
    email = data.get('email')
    
    if User.query.filter_by(username=username).first():
        return jsonify({'error': '用户名已存在'}), 400
    
    user = User(username=username, email=email)
    user.set_password(password)
    db.session.add(user)
    db.session.commit()
    return jsonify({'message': '注册成功'}), 201

# 登录接口
@app.route('/api/login', methods=['POST'])
def login():
    data = request.json
    user = User.query.filter_by(username=data.get('username')).first()
    if not user or not user.check_password(data.get('password')):
        return jsonify({'error': '用户名或密码错误'}), 401
    
    login_user(user, remember=data.get('remember', False))  # 记住登录状态
    return jsonify({
        'message': '登录成功',
        'user': {
            'id': user.id,
            'username': user.username,
            'role': user.role
        }
    })

# 登出接口
@app.route('/api/logout', methods=['POST'])
@login_required
def logout():
    logout_user()
    return jsonify({'message': '登出成功'})

2. 前端登录注册页面

原有 index无用户界面,新增登录 / 注册弹窗及路由控制:

点击查看代码
<!-- 新增登录弹窗(index.html) -->
<div id="login-modal" class="modal">
    <div class="modal-content">
        <span class="close" data-modal="login-modal">&times;</span>
        <h3>用户登录</h3>
        <form id="login-form">
            <input type="text" name="username" placeholder="用户名" required>
            <input type="password" name="password" placeholder="密码" required>
            <label><input type="checkbox" name="remember"> 记住我</label>
            <button type="submit">登录</button>
            <p>还没有账号?<a href="#" data-toggle-modal="register-modal">注册</a></p>
        </form>
    </div>
</div>

<!-- 新增注册弹窗(index.html) -->
<div id="register-modal" class="modal">
    <div class="modal-content">
        <span class="close" data-modal="register-modal">&times;</span>
        <h3>用户注册</h3>
        <form id="register-form">
            <input type="text" name="username" placeholder="用户名" required>
            <input type="email" name="email" placeholder="邮箱" required>
            <input type="password" name="password" placeholder="密码" required>
            <button type="submit">注册</button>
        </form>
    </div>
</div>

前端交互逻辑(处理表单提交与会话管理):

点击查看代码
// 登录表单提交(index.html)
document.getElementById('login-form').addEventListener('submit', async (e) => {
    e.preventDefault();
    const formData = new FormData(e.target);
    const data = Object.fromEntries(formData.entries());
    
    try {
        const res = await fetch('/api/login', {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(data)
        });
        
        if (!res.ok) throw new Error('登录失败');
        const result = await res.json();
        
        // 保存用户信息到本地存储
        localStorage.setItem('user', JSON.stringify(result.user));
        // 刷新页面或跳转至首页
        window.location.reload();
    } catch (err) {
        alert(err.message);
    }
});

二、后端管理模块实现
1. 管理员权限控制
基于用户角色(role=admin)限制管理接口访问:

点击查看代码
# 新增代码(app.py)
from functools import wraps
from flask import abort

# 管理员权限装饰器
def admin_required(f):
    @wraps(f)
    @login_required
    def decorated_function(*args, **kwargs):
        if current_user.role != 'admin':
            abort(403, description='无管理员权限')
        return f(*args, **kwargs)
    return decorated_function

2. 管理接口实现(笔记 / 用户管理)

点击查看代码
# 管理员获取所有用户(app.py)
@app.route('/api/admin/users')
@admin_required
def get_all_users():
    users = User.query.all()
    return jsonify([{
        'id': u.id,
        'username': u.username,
        'email': u.email,
        'role': u.role,
        'created_at': u.created_at.isoformat()
    } for u in users])

# 管理员删除笔记(app.py)
@app.route('/api/admin/notes/<int:note_id>', methods=['DELETE'])
@admin_required
def delete_note_admin(note_id):
    note = Note.query.get_or_404(note_id)
    db.session.delete(note)
    db.session.commit()
    return jsonify({'message': '笔记已删除'})

3. 管理页面前端实现

新增admin.html页面,仅管理员可见:

点击查看代码
<!-- admin.html -->
<!DOCTYPE html>
<html>
<head>
    <title>系统管理</title>
    <!-- 复用index(4)中的基础样式 -->
    <link rel="stylesheet" href="/static/css/base.css">
</head>
<body>
    <div class="admin-container">
        <h1>系统管理</h1>
        <div class="tabs">
            <button class="tab-btn active" data-tab="users">用户管理</button>
            <button class="tab-btn" data-tab="notes">笔记管理</button>
        </div>
        
        <!-- 用户列表 -->
        <div class="tab-content" id="users-tab">
            <table id="users-table">
                <thead>
                    <tr><th>ID</th><th>用户名</th><th>邮箱</th><th>角色</th><th>操作</th></tr>
                </thead>
                <tbody></tbody>
            </table>
        </div>
        
        <!-- 笔记列表 -->
        <div class="tab-content" id="notes-tab" style="display:none;">
            <table id="notes-table">
                <thead>
                    <tr><th>ID</th><th>标题</th><th>作者</th><th>类型</th><th>操作</th></tr>
                </thead>
                <tbody></tbody>
            </table>
        </div>
    </div>

    <script>
        // 加载用户列表
        async function loadUsers() {
            const res = await fetch('/api/admin/users', {
                headers: { 'Authorization': `Bearer ${localStorage.getItem('token')}` }
            });
            const users = await res.json();
            // 渲染表格...
        }
        
        // 页面加载时执行
        document.addEventListener('DOMContentLoaded', () => {
            // 验证管理员权限(前端判断)
            const user = JSON.parse(localStorage.getItem('user'));
            if (!user || user.role !== 'admin') {
                window.location.href = '/';
            }
            loadUsers();
        });
    </script>
</body>
</html>

三、Markdown 与富文本混淆问题处理
1. 问题分析
原有 share (4) 和 app (4) 中,笔记内容渲染存在两个问题:

  • 富文本包含的 HTML 标签被当作普通文本显示
  • Markdown 语法(如# 标题)未转换为 HTML,直接显示原始字符

2. 解决方案实现

后端类型标识强化
在app原有Note.type字段基础上,增加创建 / 编辑时的类型校验:

点击查看代码
# 修改代码(app.py,基于原来代码的Note模型)
@app.route('/api/notes', methods=['POST'])
@login_required
def create_note():
    data = request.json
    note_type = data.get('type', 'richtext')
    
    # 校验类型合法性(仅允许richtext/markdown)
    if note_type not in ['richtext', 'markdown']:
        return jsonify({'error': '无效的笔记类型'}), 400
    
    note = Note(
        title=data.get('title'),
        content=data.get('content'),
        type=note_type,  # 明确存储类型
        user_id=current_user.id
    )
    db.session.add(note)
    db.session.commit()
    return jsonify({'id': note.id}), 201

前端渲染逻辑优化
在 share (4) 的分享详情页和笔记编辑页,根据类型切换渲染方式:

点击查看代码

<!-- 分享详情页渲染优化(share_detail.html,基于share(4)扩展) -->
<div class="note-content">
    {% if note.type == 'richtext' %}
        <!-- 富文本:保留HTML格式 -->
        {{ note.content|safe }}
    {% else %}
        <!-- Markdown:转换为HTML并高亮代码 -->
        {{ note.content|markdown }}
    {% endif %}
</div>

<!-- 编辑页编辑器切换(index.html) -->
<select id="note-type" onchange="switchEditor(this.value)">
    <option value="richtext">富文本</option>
    <option value="markdown">Markdown</option>
</select>

<div id="richtext-editor" style="display:block;">
    <!-- 富文本编辑器(如TinyMCE) -->
    <textarea id="richtext-content"></textarea>
</div>

<div id="markdown-editor" style="display:none;">
    <!-- Markdown编辑器(如SimpleMDE) -->
    <textarea id="markdown-content"></textarea>
</div>

<script>
    // 切换编辑器显示
    function switchEditor(type) {
        document.getElementById('richtext-editor').style.display = type === 'richtext' ? 'block' : 'none';
        document.getElementById('markdown-editor').style.display = type === 'markdown' ? 'block' : 'none';
    }
</script>

功能效果与总结
核心功能效果

  • 登录注册:用户可创建账号并登录,系统基于用户身份隔离数据
  • 后端管理:管理员可查看所有用户 / 笔记,执行删除等操作,提升系统可控性
  • 内容渲染:富文本保留格式、Markdown 正确转换,解决此前混淆问题
posted @ 2025-10-22 18:50  黄思宇  阅读(12)  评论(0)    收藏  举报