订餐后台管理系统 - day04退出登录与账号管理模块

发布于:2025-08-30 ⋅ 阅读:(21) ⋅ 点赞:(0)

退出登录模块

功能概述

退出登录功能是系统安全性的重要组成部分,它确保用户能够安全地结束会话,防止未授权访问。

代码实现与讲解

# 退出登录
@manage_bp.route('/logout')
def logout():
    # 重定向到登录页
    response = make_response(redirect(url_for('manage.login')))
    # 删除cookie
    response.delete_cookie(Config.AUTH_COOKIE_NAME)
    return response

代码详解:

  1. 路由定义

    • 使用@manage_bp.route('/logout')装饰器定义退出登录的路由

    • 这是一个GET请求路由,不需要处理表单数据

  2. 响应对象创建

    • make_response(redirect(url_for('manage.login')))创建一个重定向到登录页面的响应对象

    • url_for('manage.login')使用Flask的反向解析功能生成登录页面的URL

  3. Cookie删除

    • response.delete_cookie(Config.AUTH_COOKIE_NAME)删除认证Cookie

    • Config.AUTH_COOKIE_NAME是在配置文件中定义的Cookie名称("lgsp_food")

  4. 返回响应

    • 返回处理后的响应对象,浏览器会重定向到登录页面并删除认证Cookie

安全考虑:

  • 退出登录后立即删除认证Cookie,防止会话被滥用

  • 重定向到登录页面,提供清晰的用户反馈

账号管理模块

功能概述

账号管理模块负责系统中用户账号的CRUD操作(创建、读取、更新、删除),包括列表展示、搜索、分页和编辑功能。

用户列表与分页

路由配置与数据处理
@manage_bp.route('/account/list')
def account_list():
    # 默认分页起始从第一页开始
    page = int(request.args.get('page', 1))
​
    # 精简操作
    query = User.query
​
    # 默认状态
    status_name = int(request.args.get('status', '-1'))
​
    # 有值就取值
    if status_name > -1:
        query = query.filter(User.status == status_name)
​
    # 姓名或者手机号验证,默认为空
    mix_kw = request.args.get('mix_kw', '')
    if mix_kw:
        # 数据分页
        rule = or_(User.nickname.contains('%s' % mix_kw), User.mobile.contains('%s' % mix_kw))
        page_data = query.filter(rule).order_by(User.id.desc()).paginate(page=page, per_page=Config.PER_PAGE)
    else:
        page_data = query.order_by(User.id.desc()).paginate(page=page, per_page=Config.PER_PAGE)
​
    # 将数据进行返回
    resp_data = {
        'list': page_data,
        'status_mapping': constants.STATUS_MAPPING
    }
​
    # 显示页面,发送数据
    return ops_render('account/index.html', resp_data)

代码详解:

  1. 获取分页参数

    • page = int(request.args.get('page', 1))从请求参数中获取页码,默认为第一页

  2. 构建查询

    • query = User.query初始化为所有用户的查询

    • 根据状态参数过滤:query = query.filter(User.status == status_name)

  3. 关键字搜索

    • 获取搜索关键字:mix_kw = request.args.get('mix_kw', '')

    • 使用SQLAlchemy的or_函数实现多字段搜索:昵称或手机号包含关键字

    • contains方法实现模糊查询

  4. 分页处理

    • 使用SQLAlchemy的paginate方法进行分页

    • per_page=Config.PER_PAGE使用配置文件中定义的分页大小

  5. 准备响应数据

    • 分页数据:page_data

    • 状态映射:constants.STATUS_MAPPING(将状态码映射为可读文本)

  6. 渲染模板

    • 使用自定义的ops_render函数渲染模板并传递数据

分页宏实现
{% macro page_nav(page_data, obj_url) %}
    <div class="pull-right">
        <div>
            {% if page_data %}
                {% set status = request.args.get('status', -1) %}
                {% set cat_id = request.args.get('cat_id', 0) %}
                {% set mix_kw = request.args.get('mix_kw', '') %}
                <nav aria-label="Page navigation">
                    <ul class="pagination pagination-sm no-margin">
                        <li>
                            <a href="{{ url_for(obj_url, page=1) }}&status={{ status }}&cat_id={{ cat_id }}&mix_kw={{ mix_kw }}">首页</a>
                        </li>
                        {% if page_data.has_prev %}
                            <li>
                                <a href="{{ url_for(obj_url, page=page_data.prev_num) }}&status={{ status }}&cat_id={{ cat_id }}&mix_kw={{ mix_kw }}" aria-label="Previous">
                                    <span aria-hidden="true">上一页</span>
                                </a>
                            </li>
                        {% else %}
                            <li class="disabled">
                                <a href="javascript:;"
                                   aria-label="Previous">
                                    <span aria-hidden="true">上一页</span>
                                </a>
                            </li>
                        {% endif %}
​
                        {% for page_num in page_data.iter_pages() %}
                            {% set page_num = page_num|d('...', True) %}
                            {% if page_num == '...' %}
                                <li><a href="javascript:;">{{ page_num }}</a></li>
                            {% else %}
                                {% if page_num == page_data.page %}
                                    <li class="active">
                                        <a href="javascript:;">{{ page_num }}</a>
                                    </li>
                                {% else %}
                                    <li>
                                        <a href="{{ url_for(obj_url, page=page_num) }}&status={{ status }}&cat_id={{ cat_id }}&mix_kw={{ mix_kw }}">{{ page_num }}</a>
                                    </li>
                                {% endif %}
                            {% endif %}
                        {% endfor %}
​
                        {% if page_data.has_next %}
                            <li>
                                <a href="{{ url_for(obj_url, page=page_data.next_num) }}&status={{ status }}&cat_id={{ cat_id }}&mix_kw={{ mix_kw }}"
                                   aria-label="Next">
                                    <span aria-hidden="true">下一页</span>
                                </a>
                            </li>
                        {% else %}
                            <li class="disabled">
                                <a href="javascript:;"
                                   aria-label="Next">
                                    <span aria-hidden="true">下一页</span>
                                </a>
                            </li>
                        {% endif %}
                        <li>
                            <a href="{{ url_for(obj_url, page=page_data.pages) }}&status={{ status }}&cat_id={{ cat_id }}&mix_kw={{ mix_kw }}">尾页</a>
                        </li>
                    </ul>
                </nav>
            {% endif %}
        </div>
    </div>
{% endmacro %}

代码详解:

  1. 宏定义

    • 使用{% macro %}定义可重用的分页组件

    • 参数:page_data(分页数据对象)和obj_url(路由端点名称)

  2. 保留查询参数

    • 获取当前的状态、分类ID和搜索关键字参数

    • 在分页链接中保留这些参数,确保翻页后筛选条件不变

  3. 分页导航

    • 首页链接:直接跳转到第一页

    • 上一页/下一页:根据当前页面状态启用或禁用

    • 页码迭代:使用page_data.iter_pages()生成页码列表

    • 尾页链接:直接跳转到最后一页

  4. 样式处理

    • 当前页使用active类高亮显示

    • 禁用状态使用disabled类并移除链接功能

用户编辑和添加

路由配置与数据处理
# 用户编辑和添加
@manage_bp.route('/account/edit', methods=['GET', 'POST'])
def account_edit():
    # 编辑或者是添加的页面
    if request.method == 'GET':
        u_id = int(request.args.get('id', 0))
​
        user_obj = None
        if u_id:
            user_obj = User.query.get(u_id)
​
        resp_data = {'info': user_obj}
        return ops_render('account/set.html', resp_data)
​
    if request.method == 'POST':
        resp = {'code': 200, 'msg': '修改成功!', 'data': {}}
        req = request.values
        u_id = req['id'] if 'id' in req else 0
        nickname = req['nickname'] if 'nickname' in req else ''
        mobile = req['mobile'] if 'mobile' in req else ''
        email = req['email'] if 'email' in req else ''
        login_name = req['login_name'] if 'login_name' in req else ''
        login_pwd = req['login_pwd'] if 'login_pwd' in req else ''
​
        # 数据验证
        if nickname is None or len(nickname) < 2:
            resp['code'] = -1
            resp['msg'] = '请输入符合规范的昵称!'
            return jsonify(resp)
​
        mobile_pattern = re.compile(r'^(13[0-9]|14[5|7]|15[0|1|2|3|4|5|6|7|8|9]|18[0|1|2|3|5|6|7|8|9])\d{8}$')
        if mobile is None or not mobile_pattern.match(mobile):
            resp['code'] = -1
            resp['msg'] = '请输入有效的手机号!'
            return jsonify(resp)
​
        if email is None or len(email) < 1:
            resp['code'] = -1
            resp['msg'] = 'Email不能为空!'
            return jsonify(resp)
​
        email_pattern = re.compile(r'^\w+([-+.]\w+)*@\w+([-.]\w+)*\.\w+([-.]\w+)*$')
        if not email_pattern.match(email):
            resp['code'] = -1
            resp['msg'] = '请输入有效的Email地址!'
            return jsonify(resp)
​
        if login_name is None or len(login_name) < 2:
            resp['code'] = -1
            resp['msg'] = '请输入符合规范的用户名!'
            return jsonify(resp)
​
        if login_pwd is None or len(login_pwd) < 6:
            resp['code'] = -1
            resp['msg'] = '请输入符合规范的密码!'
            return jsonify(resp)
​
        # 检查用户名是否已存在
        has_user = User.query.filter(User.login_name == login_name, User.id != u_id).first()
        if has_user:
            resp['code'] = -1
            resp['msg'] = '该用户登录名已存在,请更换一个重新注册!'
            return jsonify(resp)
​
        # 获取或创建用户对象
        user_obj = User.query.get(u_id)
        if not user_obj:
            resp['msg'] = '新增成功!'
            user_obj = User()
​
        # 更新用户信息
        user_obj.nickname = nickname
        user_obj.mobile = mobile
        user_obj.email = email
        user_obj.login_name = login_name
        
        # 密码加密处理
        login_salt = random_salt()
        user_obj.login_salt = login_salt
        user_obj.login_pwd = gene_pwd(login_pwd, login_salt)
​
        # 保存到数据库
        db.session.add(user_obj)
        db.session.commit()
        return jsonify(resp)

代码详解:

  1. 双方法路由

    • 支持GET和POST两种HTTP方法

    • GET:显示编辑/添加页面

    • POST:处理表单提交

  2. GET请求处理

    • 从请求参数获取用户ID

    • 根据ID查询用户信息(编辑时)或创建空对象(添加时)

    • 渲染编辑页面并传递用户数据

  3. POST请求处理

    • 初始化响应对象

    • 获取并验证表单数据

    • 检查用户名是否已存在(排除当前编辑的用户)

    • 根据ID判断是更新还是新增用户

    • 对密码进行加密处理

    • 保存到数据库并返回操作结果

  4. 数据验证

    • 昵称:不能为空且长度至少2字符

    • 手机号:使用正则表达式验证格式

    • 邮箱:不能为空且格式正确

    • 用户名:不能为空且长度至少2字符

    • 密码:不能为空且长度至少6字符

工具方法

随机盐生成
import random
import string
​
def random_salt(length=9):
    """生成随机盐值"""
    return ''.join(random.sample(string.ascii_letters + string.digits, length))

代码详解:

  • 从字母和数字中随机选择字符生成指定长度的字符串

  • 默认长度为9个字符

  • 用于密码加密的盐值,增加密码安全性

密码加密
import hashlib
import base64
​
def gene_pwd(pwd, salt):
    """加密密码(MD5+salt)"""
    m = hashlib.md5()
    _str = "%s--%s" % (base64.encodebytes(pwd.encode('utf-8')), salt)
    m.update(_str.encode('utf-8'))
    return m.hexdigest()

代码详解:

  1. 使用base64编码密码字符串

  2. 将编码后的密码与盐值组合

  3. 使用MD5算法对组合字符串进行哈希计算

  4. 返回十六进制格式的哈希值作为加密后的密码

安全考虑:

  • 使用盐值防止彩虹表攻击

  • MD5算法虽然不再是最安全的选项,但结合盐值仍提供基本的安全性

  • 在实际生产环境中,可以考虑使用更安全的算法如bcrypt

总结

Day04的内容主要实现了退出登录和账号管理模块:

  1. 退出登录

    • 删除认证Cookie

    • 重定向到登录页面

  2. 账号管理

    • 用户列表展示与分页

    • 搜索和筛选功能

    • 可重用的分页组件

    • 用户编辑和添加功能

    • 完整的数据验证机制

  3. 安全特性

    • 密码加盐加密存储

    • 输入数据验证和过滤

    • 防止用户名重复

这些功能共同构成了一个完整的用户管理系统,为后台管理提供了必要的账号管理能力。


网站公告

今日签到

点亮在社区的每一天
去签到