项目概述与架构设计
基于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>
设计要点:
使用Bootstrap提供响应式布局
采用模板继承机制,提高代码复用性
使用Font Awesome提供图标支持
模块化设计,预留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 %}
设计要点:
使用卡片式布局展示关键指标
预留图表区域用于数据可视化
按需加载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'
]
设计要点:
集中管理配置项,便于维护和修改
使用正则表达式模式匹配URL,提高灵活性
动态计算项目路径,增强可移植性
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)
设计要点:
密码使用加盐MD5加密,提高安全性
认证码使用用户多项信息生成,防止伪造
详细的错误处理和日志记录
函数功能单一,便于测试和维护
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')
设计要点:
使用蓝图组织相关路由
支持GET和POST两种请求方式
详细的参数验证和错误处理
使用JSON格式返回响应,便于前端处理
设置合理的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'))
设计要点:
使用Flask的
before_app_request
钩子实现全局拦截使用正则表达式匹配URL,灵活配置忽略规则
将用户信息存储到g对象中,便于在整个请求周期内使用
合理的重定向逻辑,确保未登录用户访问受限页面时跳转到登录页
登录模块工作流程详解
1. 认证流程步骤
用户访问受保护页面
拦截器检查认证状态
未认证用户被重定向到登录页面
用户提交登录表单
前端通过AJAX发送用户名和密码到服务器
服务器验证凭证的有效性
服务器验证凭证
检查用户名和密码是否为空
查询用户是否存在
验证密码是否正确(加盐MD5比较)
检查用户状态是否正常
设置认证Cookie
生成认证码(基于用户信息计算MD5)
设置包含认证码和用户ID的Cookie
设置120天的长有效期
返回登录结果
成功:返回成功消息和设置Cookie的响应
失败:返回具体的错误信息
后续请求验证
拦截器解析Cookie中的认证信息
重新计算认证码并验证匹配
验证用户状态是否正常
2. 安全机制详解
密码加密
使用MD5加盐方式存储密码
每个用户使用不同的随机盐值
防止彩虹表攻击和密码明文存储
认证Cookie设计
Cookie包含用户ID和认证码
认证码基于用户多项信息计算,防止篡改
设置合理的过期时间,平衡安全性和用户体验
多层次验证
验证用户是否存在
验证密码是否正确
验证用户状态是否正常
验证认证码是否匹配
访问控制
使用拦截器实现全局访问控制
灵活配置忽略规则,避免过度拦截
对静态资源等不需要认证的URL跳过检查
3. 错误处理机制
系统提供了详细的错误反馈机制,帮助用户理解和解决问题:
用户名不能为空 - 提示用户输入用户名
密码不能为空 - 提示用户输入密码
用户名不正确 - 提示用户检查用户名
密码不正确 - 提示用户检查密码
账户被禁用 - 提示用户联系管理员
认证码生成失败 - 系统内部错误,记录日志
技术亮点与最佳实践
模块化设计
功能模块划分清晰,便于维护和扩展
使用蓝图组织相关路由
工具函数功能单一,便于测试和复用
安全最佳实践
密码加盐存储,防止彩虹表攻击
使用HTTPS传输敏感数据(实际部署时)
合理的Cookie安全属性设置(HttpOnly、Secure等)
用户体验优化
AJAX无刷新登录,提高用户体验
详细的错误提示,帮助用户解决问题
响应式设计,适配不同设备
可维护性设计
配置集中管理,便于修改和维护
详细的日志记录,便于故障排查
代码注释完整,便于团队协作
这个登录模块为订餐后台管理系统提供了稳定可靠的身份认证基础,确保了只有授权管理员能够访问系统功能,同时提供了良好的用户体验和安全保障。