订餐后台管理系统 -day03 登录模块

发布于:2025-08-31 ⋅ 阅读:(26) ⋅ 点赞:(0)

项目概述与架构设计

基于Flask框架开发的订餐后台管理系统,登录模块是系统的核心安全组件,负责管理员身份验证和访问控制。整个系统采用MVC架构设计,前端使用Bootstrap框架,后端使用Flask处理业务逻辑。

系统架构图

前端页面 (HTML+CSS+JS)
    ↓
Flask路由 (Blueprint)
    ↓
业务逻辑处理 (视图函数)
    ↓
数据模型 (SQLAlchemy)
    ↓
数据库 (MySQL/SQLite)

前端页面详细讲解

1. 主布局模板分析

主布局模板(layout_main.html)提供了整个系统的统一结构和样式框架:

<!DOCTYPE html>
<html>
<head>
    <!-- 元数据设置 -->
    <meta charset="utf-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <title>管理后台</title>
    
    <!-- 引入第三方CSS库 -->
    <link href="{{ url_for('static', filename='bootstrap/bootstrap.min.css') }}" rel="stylesheet">
    <link href="{{ url_for('static', filename='font-awesome/css/font-awesome.min.css') }}" rel="stylesheet">
    
    <!-- 引入自定义CSS -->
    <link href="{{ url_for('static', filename='css/style.css') }}" rel="stylesheet">
    
    <!-- 子模板可扩展的CSS区域 -->
    {% block css %}{% endblock %}
</head>
<body>
<div id="wrapper">
    <!-- 侧边导航栏 -->
    <nav class="navbar-default navbar-static-side" role="navigation">
        <!-- 公司Logo和名称 -->
        <div class="nav-header">
            <div class="profile-element text-center">
                <img alt="image" class="img-circle" src="{{ url_for('static', filename='images/common/logo.png') }}"/>
                <p class="text-muted">XX公司</p>
            </div>
        </div>
        
        <!-- 导航菜单 -->
        <ul class="nav metismenu" id="side-menu">
            <li class="default">
                <a href="{{ url_for('manage.index') }}">
                    <i class="fa fa-dashboard fa-lg"></i>
                    <span class="nav-label">仪表盘</span>
                </a>
            </li>
            <!-- 更多菜单项... -->
        </ul>
    </nav>
​
    <!-- 主内容区域 -->
    <div id="page-wrapper" class="gray-bg">
        <!-- 顶部导航栏 -->
        <nav class="navbar navbar-static-top" role="navigation">
            <div class="navbar-header">
                <!-- 侧边栏折叠按钮 -->
                <a class="navbar-minimalize minimalize-styl-2 btn btn-primary" href="javascript:void(0);">
                    <i class="fa fa-bars"></i> 
                </a>
            </div>
            
            <!-- 用户信息区域 -->
            <ul class="nav navbar-top-links navbar-right">
                <li class="dropdown user_info">
                    <a class="dropdown-toggle" data-toggle="dropdown" href="javascript:void(0);">
                        <!-- 用户头像 -->
                        <img alt="image" class="img-circle" src="{{ url_for('static', filename='images/common/avatar.png') }}"/>
                    </a>
                    <!-- 用户信息下拉菜单 -->
                    <ul class="dropdown-menu dropdown-messages">
                        <li>
                            <div class="dropdown-messages-box">
                                姓名:{{ current_user.nickname }} 
                                <a href="{{ url_for('manage.user_edit') }}" class="pull-right">编辑</a>
                            </div>
                        </li>
                        <!-- 更多用户信息... -->
                    </ul>
                </li>
            </ul>
        </nav>
        
        <!-- 内容区块(由子模板填充) -->
        {% block content %}{% endblock %}
    </div>
</div>
​
<!-- JavaScript 文件 -->
<script src="{{ url_for('static', filename='plugins/jquery-2.1.1.js') }}"></script>
<script src="{{ url_for('static', filename='bootstrap/bootstrap.min.js') }}"></script>
<script src="{{ url_for('static', filename='plugins/layer/layer.js') }}"></script>
<script src="{{ url_for('static', filename='js/common.js') }}"></script>
​
<!-- 子模板可扩展的JS区域 -->
{% block js %}{% endblock %}
</body>
</html>

设计要点:

  1. 使用Bootstrap提供响应式布局

  2. 采用模板继承机制,提高代码复用性

  3. 使用Font Awesome提供图标支持

  4. 模块化设计,预留CSS和JS扩展区域

2. 仪表盘页面分析

仪表盘页面(index.html)继承自主布局,展示系统关键数据:

{% extends "common/layout_main.html" %}
{% block content %}
<div class="wrapper wrapper-content">
    <div class="row">
        <!-- 营收概况卡片 -->
        <div class="col-lg-3">
            <div class="ibox float-e-margins">
                <div class="ibox-title">
                    <span class="label label-primary pull-right">日统计</span>
                    <h5>营收概况</h5>
                </div>
                <div class="ibox-content">
                    <h1 class="no-margins">1005.00</h1>
                    <small>近30日:31177.00</small>
                </div>
            </div>
        </div>
        <!-- 更多统计卡片... -->
    </div>
    
    <!-- 数据图表区域 -->
    <div class="row">
        <div class="col-lg-12" id="member_order" style="height: 400px;">
            使用highchart画图
        </div>
        <!-- 更多图表区域... -->
    </div>
</div>
{% endblock %}
​
{% block js %}
<!-- 图表库 -->
<script src="{{ url_for('static', filename='plugins/highcharts/highcharts.js') }}"></script>
<!-- 图表工具 -->
<script src="{{ url_for('static', filename='js/chart.js') }}"></script>
<!-- 页面特定JS -->
<script src="{{ url_for('static', filename='js/index/index.js') }}"></script>
{% endblock %}

设计要点:

  1. 使用卡片式布局展示关键指标

  2. 预留图表区域用于数据可视化

  3. 按需加载JS资源,提高页面性能

后端实现详细讲解

1. 配置文件详解

配置文件(system_config.py)集中管理应用程序的各种设置:

import datetime
import os
​
class Config:
    # 认证Cookie名称 - 用于存储用户登录凭证
    AUTH_COOKIE_NAME = "lgsp_food"
    
    # Session过期时间 - 设置用户会话有效期为30天
    PERMANENT_SESSION_LIFETIME = datetime.timedelta(days=30)
    
    # 项目路径配置 - 动态获取项目根目录、静态文件夹和模板文件夹路径
    BASE_DIR = os.path.abspath(os.path.dirname(os.path.dirname(__file__)))
    STATIC_DIR = os.path.join(BASE_DIR, 'static')
    TEMPLATES_DIR = os.path.join(BASE_DIR, 'templates')
    
    # 域名配置 - 设置应用程序的基础URL
    DOMAIN = 'http://127.0.0.1:5000'
    
    # 文件上传配置 - 限制上传文件类型和存储路径
    UPLOAD = {
        'ext': ['jpg', 'gif', 'bmp', 'jpeg', 'png'],  # 允许的文件扩展名
        'prefix_path': 'static/upload/',  # 文件存储路径
        'prefix_url': 'static/upload/'   # 文件访问URL前缀
    }
    
    # 分页配置 - 设置每页显示的数据条数
    PER_PAGE = 2
    
    # 不需要认证的URL - 这些URL不需要登录即可访问
    IGNORE_URLS = [
        '^/manage/login',  # 登录页面
        '^/api'            # API接口
    ]
    
    # 不需要检查登录状态的URL - 这些URL完全不进行登录检查
    IGNORE_CHECK_LOGIN_URLS = [
        '^/static',        # 静态资源
        '^/favicon.ico',   # 网站图标
    ]
    
    # API忽略URL - API接口的特殊处理规则
    API_IGNORE_URLS = [
        '^/api'
    ]

设计要点:

  1. 集中管理配置项,便于维护和修改

  2. 使用正则表达式模式匹配URL,提高灵活性

  3. 动态计算项目路径,增强可移植性

2. 工具函数详解

工具函数(utils.py)提供各种辅助功能:

import hashlib
from flask import request, current_app
from config.system_config import Config
from web.models.food.food_models import User
​
def gene_pwd(password, salt):
    """
    生成加密密码
    参数:
        password: 明文密码
        salt: 盐值
    返回:
        MD5加密后的密码字符串
    """
    m = hashlib.md5()
    _str = "%s-%s" % (password, salt)  # 将密码和盐值组合
    m.update(_str.encode('utf-8'))     # 编码为UTF-8并计算MD5
    return m.hexdigest()               # 返回十六进制格式的哈希值
​
def gene_auth_code(obj, _type):
    """
    生成认证码用于Cookie验证
    参数:
        obj: 用户对象
        _type: 对象类型('user'表示后台用户)
    返回:
        MD5哈希值作为认证码
    """
    m = hashlib.md5()
    _str = None
    if _type == 'user':
        # 使用用户的多项信息组合生成认证码,增加安全性
        _str = '%s-%s-%s-%s' % (obj.id, obj.login_name, obj.login_pwd, obj.login_salt)
    m.update(_str.encode('utf-8'))
    return m.hexdigest()
​
def check_login():
    """
    检查用户登录状态
    返回:
        如果用户已登录且有效,返回用户对象
        否则返回False
    """
    # 获取认证Cookie
    cookies = request.cookies
    auth_cookie = cookies[Config.AUTH_COOKIE_NAME] if Config.AUTH_COOKIE_NAME in cookies else None
    
    # 没有认证Cookie,直接返回未登录
    if auth_cookie is None:
        return False
    
    # 解析Cookie值,格式为"认证码#用户ID"
    auth_info = auth_cookie.split('#')
    if len(auth_info) != 2:  # 格式不正确
        return False
        
    try:
        # 通过ID获取用户对象
        user_obj = User.query.get(auth_info[1])
    except Exception as e:
        current_app.logger.info(e)  # 记录异常日志
        return False
​
    if user_obj is None:  # 用户不存在
        return False
​
    # 验证认证码是否匹配
    if auth_info[0] != gene_auth_code(user_obj, 'user'):
        return False
​
    # 检查用户状态是否正常(1表示正常)
    if user_obj.status != 1:
        return False
​
    return user_obj  # 返回用户对象
​
def random_salt(length=16):
    """
    生成随机盐值用于密码加密
    参数:
        length: 盐值长度,默认为16
    返回:
        随机字符串作为盐值
    """
    import random
    import string
    # 从字母和数字中随机选择字符生成盐值
    return ''.join(random.choice(string.ascii_letters + string.digits) for _ in range(length))
​
def ops_render(template, context={}):
    """
    自定义渲染函数,封装Flask的render_template
    参数:
        template: 模板文件名
        context: 传递给模板的上下文数据
    返回:
        渲染后的HTML内容
    """
    from flask import render_template
    # 统一渲染方法
    if context is None:
        context = {}
    if 'current_user' in g:
        context['current_user'] = g.current_user
    return render_template(template, **context)

设计要点:

  1. 密码使用加盐MD5加密,提高安全性

  2. 认证码使用用户多项信息生成,防止伪造

  3. 详细的错误处理和日志记录

  4. 函数功能单一,便于测试和维护

3. 视图函数详解

视图函数(admin.py)处理具体的业务逻辑:

import datetime
import json
import re
from decimal import Decimal
​
from flask import Blueprint, request, jsonify, make_response, redirect, url_for, g
from sqlalchemy import or_
​
# 导入数据模型
from web.models.food.food_models import *
from web.models.wx_food.wx_models import *
​
# 导入工具函数
from utils.utils import gene_pwd, gene_auth_code, ops_render, random_salt
from config import constants
from application import db
from config.system_config import Config
​
# 创建蓝图 - 将相关路由组织在一起
manage_bp = Blueprint('manage', __name__)
​
@manage_bp.route('/login', methods=['GET', 'POST'])
def login():
    """
    登录视图函数
    支持GET和POST两种请求方式
    GET: 显示登录页面
    POST: 处理登录表单提交
    """
    # GET请求 - 显示登录页面
    if request.method == 'GET':
        return ops_render('user/login.html')
    
    # POST请求 - 处理登录数据
    if request.method == 'POST':
        # 准备响应数据
        resp = {'code': 200, 'msg': '登录成功', 'data': {}}
        
        # 获取请求参数
        req = request.values
        login_name = req['login_name'] if 'login_name' in req else ''
        login_pwd = req['login_pwd'] if 'login_pwd' in req else ''
        
        # 参数验证 - 确保用户名和密码不为空
        if not login_name or len(login_name) < 1:
            resp['code'] = -1
            resp['msg'] = '登录用户名不能为空'
            return jsonify(resp)
            
        if not login_pwd or len(login_pwd) < 1:
            resp['code'] = -1
            resp['msg'] = '登录密码不能为空'
            return jsonify(resp)
        
        # 查询用户 - 根据用户名查找用户
        user_obj = User.query.filter_by(login_name=login_name).first()
        
        # 用户不存在
        if not user_obj:
            resp['code'] = -1
            resp['msg'] = '请输入正确的用户名'
            return jsonify(resp)
        
        # 验证密码 - 比较加密后的密码是否匹配
        if user_obj.login_pwd != gene_pwd(login_pwd, user_obj.login_salt):
            resp['code'] = -1
            resp['msg'] = '请输入正确的密码'
            return jsonify(resp)
        
        # 检查用户状态 - 确保账号未被禁用
        if user_obj.status != 1:
            resp['code'] = -1
            resp['msg'] = '账号已被禁用,请联系管理员处理!'
            return jsonify(resp)
        
        # 创建响应对象
        response = make_response(json.dumps(resp))
        
        # 生成认证码
        _gene_auth_code = gene_auth_code(user_obj, 'user')
        if _gene_auth_code is None:
            resp['code'] = -1
            resp['msg'] = 'GeneAuthCode不能为None!'
            return jsonify(resp)
        
        # 设置认证Cookie(120天有效期)
        # Cookie格式: "认证码#用户ID"
        response.set_cookie(
            Config.AUTH_COOKIE_NAME, 
            '%s#%s' % (_gene_auth_code, user_obj.id), 
            60 * 60 * 24 * 120  # 120天有效期
        )
        
        return response
​
@manage_bp.route('/')
def index():
    """首页路由 - 显示系统仪表盘"""
    return ops_render('index.html')

设计要点:

  1. 使用蓝图组织相关路由

  2. 支持GET和POST两种请求方式

  3. 详细的参数验证和错误处理

  4. 使用JSON格式返回响应,便于前端处理

  5. 设置合理的Cookie过期时间

4. 请求拦截器详解

请求拦截器(views.py)在每个请求前检查用户认证状态:

import json
import os
import re
from flask import Blueprint, request, current_app, redirect, url_for, g, jsonify
from utils.utils import add_access_log, upload_image, check_login, list_image, upload_file
from config.system_config import Config
​
utils_bp = Blueprint('utils', __name__)
​
@utils_bp.before_app_request
def before_request():
    """
    全局请求拦截器
    在每个请求处理前执行,用于检查用户认证状态
    """
    # 获取请求路径
    path = request.path
    
    # 检查是否为不需要登录验证的URL(静态资源等)
    ignore_check_login_urls = Config.IGNORE_CHECK_LOGIN_URLS
    pattern = re.compile('%s' % '|'.join(ignore_check_login_urls))
    
    # 如果匹配忽略列表,直接返回不进行登录检查
    if pattern.match(path):
        return
    
    # 验证登录状态
    user_obj = check_login()
    g.current_user = None  # 初始化g对象中的当前用户
    
    # 如果用户已登录,将用户对象存储到g中供后续使用
    if user_obj:
        g.current_user = user_obj
    
    # 记录访问日志
    add_access_log()
    
    # 检查是否为不需要认证的URL(登录页面等)
    ignore_urls = Config.IGNORE_URLS
    pattern = re.compile('%s' % '|'.join(ignore_urls))
    
    # 如果匹配忽略列表,直接返回不进行重定向
    if pattern.match(path):
        return
    
    # 未登录用户重定向到登录页
    if not user_obj:
        return redirect(url_for('manage.login'))

设计要点:

  1. 使用Flask的before_app_request钩子实现全局拦截

  2. 使用正则表达式匹配URL,灵活配置忽略规则

  3. 将用户信息存储到g对象中,便于在整个请求周期内使用

  4. 合理的重定向逻辑,确保未登录用户访问受限页面时跳转到登录页

登录模块工作流程详解

1. 认证流程步骤

  1. 用户访问受保护页面

    • 拦截器检查认证状态

    • 未认证用户被重定向到登录页面

  2. 用户提交登录表单

    • 前端通过AJAX发送用户名和密码到服务器

    • 服务器验证凭证的有效性

  3. 服务器验证凭证

    • 检查用户名和密码是否为空

    • 查询用户是否存在

    • 验证密码是否正确(加盐MD5比较)

    • 检查用户状态是否正常

  4. 设置认证Cookie

    • 生成认证码(基于用户信息计算MD5)

    • 设置包含认证码和用户ID的Cookie

    • 设置120天的长有效期

  5. 返回登录结果

    • 成功:返回成功消息和设置Cookie的响应

    • 失败:返回具体的错误信息

  6. 后续请求验证

    • 拦截器解析Cookie中的认证信息

    • 重新计算认证码并验证匹配

    • 验证用户状态是否正常

2. 安全机制详解

  1. 密码加密

    • 使用MD5加盐方式存储密码

    • 每个用户使用不同的随机盐值

    • 防止彩虹表攻击和密码明文存储

  2. 认证Cookie设计

    • Cookie包含用户ID和认证码

    • 认证码基于用户多项信息计算,防止篡改

    • 设置合理的过期时间,平衡安全性和用户体验

  3. 多层次验证

    • 验证用户是否存在

    • 验证密码是否正确

    • 验证用户状态是否正常

    • 验证认证码是否匹配

  4. 访问控制

    • 使用拦截器实现全局访问控制

    • 灵活配置忽略规则,避免过度拦截

    • 对静态资源等不需要认证的URL跳过检查

3. 错误处理机制

系统提供了详细的错误反馈机制,帮助用户理解和解决问题:

  1. 用户名不能为空 - 提示用户输入用户名

  2. 密码不能为空 - 提示用户输入密码

  3. 用户名不正确 - 提示用户检查用户名

  4. 密码不正确 - 提示用户检查密码

  5. 账户被禁用 - 提示用户联系管理员

  6. 认证码生成失败 - 系统内部错误,记录日志

技术亮点与最佳实践

  1. 模块化设计

    • 功能模块划分清晰,便于维护和扩展

    • 使用蓝图组织相关路由

    • 工具函数功能单一,便于测试和复用

  2. 安全最佳实践

    • 密码加盐存储,防止彩虹表攻击

    • 使用HTTPS传输敏感数据(实际部署时)

    • 合理的Cookie安全属性设置(HttpOnly、Secure等)

  3. 用户体验优化

    • AJAX无刷新登录,提高用户体验

    • 详细的错误提示,帮助用户解决问题

    • 响应式设计,适配不同设备

  4. 可维护性设计

    • 配置集中管理,便于修改和维护

    • 详细的日志记录,便于故障排查

    • 代码注释完整,便于团队协作

这个登录模块为订餐后台管理系统提供了稳定可靠的身份认证基础,确保了只有授权管理员能够访问系统功能,同时提供了良好的用户体验和安全保障。


网站公告

今日签到

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