pythonmysqlbootstrap条件搜索分页支持登录和退出使用session或者redis(默认)先看效果1项目目录2表字段CREATE TABLE user ( id INT ( 11 ) NOT NULL AUTO_INCREMENT, username VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, password VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, image VARCHAR ( 255 ) DEFAULT NULL, sex INT ( 11 ) DEFAULT NULL, remark VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, create_time datetime DEFAULT NULL, PRIMARY KEY ( id ) ) ENGINE MyISAM AUTO_INCREMENT 38 DEFAULT CHARSET utf8;3requirements.txtblinker1.9.0 click8.3.2 colorama0.4.6 DBUtils3.1.2 Flask3.1.3 itsdangerous2.2.0 Jinja23.1.6 MarkupSafe3.0.3 PyMySQL1.1.2 redis7.4.0 Werkzeug3.1.84add.html{% extends layout.html %} {% block body %} form methodpost h1 stylealign-content: center;text-align: center;用户添加/h1 div classform-group label classcol-sm-2 col-form-label用户名/label div classcol-sm-10 input typetext classform-control nameusername /div /div br div classform-group label classcol-sm-2 col-form-label密码/label div classcol-sm-10 input typepassword classform-control namepassword /div /divbr div classform-group label classcol-sm-2 col-form-label图片/label div classcol-sm-10 input typetext classform-control nameimage /div /divbr div classform-group label classcol-sm-2 col-form-label性别/label div classcol-sm-10 label classradio-inline input typeradio namesex value1 男 /label label classradio-inline input typeradio namesex value2 女 /label /div /divbr div classform-group label classcol-sm-2 col-form-label备注/label div classcol-sm-10 textarea classform-control rows3 nameremark/textarea /div /divbr button typesubmit classbtn btn-success styletext-align: center;margin: auto;添加/button /form {% endblock %}5edit.html{% extends layout.html %} {% block body %} form methodpost action/user/editAction h1 stylealign-content: center;text-align: center;用户编辑/h1 input typehidden value{{one[id]}} nameid input typehidden namesearch value{{ request.args.get(search, ) }} input typehidden namepage value{{ request.args.get(page, 1) }} div classform-group label classcol-sm-2 col-form-label用户名/label div classcol-sm-10 input typetext classform-control nameusername value{{one[username]}} /div /div br div classform-group label classcol-sm-2 col-form-label密码/label div classcol-sm-10 input typepassword classform-control namepassword value{{one[password]}} /div /divbr div classform-group label classcol-sm-2 col-form-label图片/label div classcol-sm-10 input typetext classform-control nameimage value{{one[image]}} /div /divbr div classform-group label classcol-sm-2 col-form-label性别/label div classcol-sm-10 label classradio-inline input typeradio namesex value1 {% if one[sex] 1 %}checked{% endif %} 男 /label label classradio-inline input typeradio namesex value2 {% if one[sex] 2 %}checked{% endif %} 女 /label /div /div br div classform-group label classcol-sm-2 col-form-label备注/label div classcol-sm-10 textarea classform-control rows3 nameremark{{one[remark]}}/textarea /div /divbr button typesubmit classbtn btn-success styletext-align: center;margin: auto;确认编辑/button /form {% endblock %}6layout.html!DOCTYPE html html langen head meta charsetUTF-8 titleTitle/title link relstylesheet href/static/bootstrap-3.4.1/css/bootstrap.css /head body div nav classnavbar navbar-default div classcontainer !-- Brand and toggle get grouped for better mobile display -- div classnavbar-header button typebutton classnavbar-toggle collapsed>{% extends layout.html %} {% block body %} div classrow stylemargin-bottom: 15px; {# 搜索框占据左侧区域 #} div classcol-md-6 form classnavbar-form navbar-left rolesearch methodget action/user/list stylemargin:0; padding:0; div classform-group input typetext classform-control placeholder按用户名搜索 namesearch value{{ search }} /div button typesubmit classbtn btn-default搜索/button /form /div {# 添加按钮占据右侧区域并用 text-right 推到最右边 #} div classcol-md-6 text-right div classbtn btn-success styledisplay: inline-block; a classbtn btn-success href/user/add添加/a /div /div /div table classtable table-bordered border1 stylemargin: auto; thead thID/th th用户名称/th th密码/th th性别/th th图片/th th备注/th th创建时间/th th操作/th /thead tbody {% for item in data_list %} tr td{{ item.id }}/td td{{ item.username }}/td td{{ item.password }}/td td{{ sex_dict[item.sex] }}/td td{{ item.image }}/td td{{ item.remark }}/td td{{ item.create_time }}/td td a href/user/delete?id{{ item.id }}search{{ search }}page{{ page }}删除/a a href/user/edit?id{{ item.id }}search{{ search }}page{{ page }}修改/a /td /tr {% endfor %} /tbody /table {# 分页代码 #} nav aria-labelPage navigation styletext-align: center; ul classpagination styledisplay: inline-block; {# 上一页 #} {% if page 1 %} li classdisabled a hrefjavascript:void(0); aria-labelPrevious span aria-hiddentruelaquo;/span /a /li {% else %} li {% set prev_url /user/list?page ~ (page - 1) ~ size ~ size ~ (search ~ search if search else ) %} a href{{ prev_url }} aria-labelPrevious span aria-hiddentruelaquo;/span /a /li {% endif %} {# 页码 #} {% for p in range(1, total_pages 1) %} {% if p page %} li classactivea hrefjavascript:void(0);{{ p }}/a/li {% else %} li {% set page_url /user/list?page ~ p ~ size ~ size ~ (search ~ search if search else ) %} a href{{ page_url }}{{ p }}/a /li {% endif %} {% endfor %} {# 下一页 #} {% if page total_pages %} li classdisabled a hrefjavascript:void(0); aria-labelNext span aria-hiddentrueraquo;/span /a /li {% else %} li {% set next_url /user/list?page ~ (page 1) ~ size ~ size ~ (search ~ search if search else ) %} a href{{ next_url }} aria-labelNext span aria-hiddentrueraquo;/span /a /li {% endif %} /ul /nav {% endblock %}8login.html!DOCTYPE html html langen head meta charsetUTF-8 titleTitle/title link relstylesheet href/static/bootstrap-3.4.1/css/bootstrap.css script src/static/jquery-3.4.1.min.js/script script src/static/bootstrap-3.4.1/js/bootstrap.js/script /head body div stylealign-content: center;margin-top: 100px;margin-left: auto;margin-right: auto;width: 600px;border:1px solid #ddd;padding: 30px; form methodpost h1 stylealign-content: center;text-align: center;用户登录/h1 div classform-group label classcol-sm-2 col-form-label用户名/label div classcol-sm-10 input typetext classform-control nameusername /div /div br div classform-group label classcol-sm-2 col-form-label密码/label div classcol-sm-10 input typepassword classform-control namepassword /div /divbr button typesubmit classbtn btn-primary styletext-align: center;margin: auto;登录/button span stylecolor:red;{{ request.args.get(error, ) }}/span /form /div /body /html9login.pyfrom flask import Blueprint, render_template,request,redirect,session,make_response from utils import db # 导入我们刚写的Redis工具方法 from utils.cache import save_login_user_to_redis,delete_login_user_from_redis # 蓝图名字必须叫 login_bp login_bp Blueprint(login, __name__) #session登录版 # login_bp.route(/login,methods[GET,POST]) # def login(): # if request.method GET: # return render_template(login.html) # # username request.form.get(username) # password request.form.get(password) # # user_dict db.fetchone(select * from user where username%s and password%s ,(username,password)) # if user_dict: # session[user_info] {real_name:user_dict[username]} # return redirect(/user/list) # else: # return redirect(/login?error用户名或密码错误) # login_bp.route(/logout) # def logout(): # # 清除 session 中保存的用户信息 # session.pop(user_info, None) # 推荐写法只删除 user_info不影响其他 session # # 或者直接清空所有 session二选一即可 # # session.clear() # return redirect(/login) #redis登录版 login_bp.route(/login,methods[GET,POST]) def login(): if request.method GET: return render_template(login.html) # POST提交登录表单 username request.form.get(username) password request.form.get(password) # 原来的数据库查询完全不变 user_dict db.fetchone(select * from user where username%s and password%s ,(username,password)) if user_dict: # 1. 登录成功把用户信息存入Redis获取登录token login_token save_login_user_to_redis(user_dict) # 2. 重定向到首页同时把token写入浏览器Cookie前端后续请求会自动携带 response make_response(redirect(/user/list)) # Cookie有效期30分钟和Redis过期时间保持一致 response.set_cookie(login_token, login_token, max_age1800) return response else: return redirect(/login?error用户名或密码错误) # 退出登录接口同步改成Redis版 login_bp.route(/logout) def logout(): # 1. 从Cookie取出token login_token request.cookies.get(login_token) # 2. 删除Redis里的登录信息 if login_token: delete_login_user_from_redis(login_token) # 3. 清除浏览器Cookie里的token重定向到登录页 response make_response(redirect(/login)) response.delete_cookie(login_token) return response10user.pyfrom flask import Blueprint, session, redirect,render_template,request from utils import db from datetime import datetime from utils.cache import get_login_user_from_redis import math from utils import cache # 蓝图名字必须叫 od和视频一致 user_bp Blueprint(user, __name__) import math #session版 # user_bp.route(/user/list) # def user_list(): # user_info session.get(user_info) # if not user_info: # return redirect(/login) # # # 1. 获取搜索词和分页参数 # search request.args.get(search, ).strip() # 获取搜索词并去除首尾空格 # page int(request.args.get(page, 1)) # size int(request.args.get(size, 3)) # # # 2. 拼接 SQL 和参数根据是否有搜索词动态生成 # if search: # # 有搜索词使用 LIKE 模糊查询 # count_sql SELECT COUNT(*) as total FROM user WHERE username LIKE %s # data_sql SELECT * FROM user WHERE username LIKE %s order by create_time desc LIMIT %s, %s # # %s 不能直接写 %张%必须把 %张% 作为一个整体传给 params # like_str f%{search}% # total_count db.userCount(count_sql, [like_str]) # start_index (page - 1) * size # data_list db.fetchAll(data_sql, [like_str, start_index, size]) # else: # # 无搜索词查询全部 # count_sql SELECT COUNT(*) as total FROM user # data_sql SELECT * FROM user order by create_time desc LIMIT %s, %s # total_count db.userCount(count_sql, []) # start_index (page - 1) * size # data_list db.fetchAll(data_sql, [start_index, size]) # # # 3. 计算总页数 # total_pages math.ceil(total_count / size) if total_count 0 else 1 # # # 4. 字典 # sex_dict {1: 男, 2: 女} # # # 5. 返回模板注意把 search 也传回前端 # return render_template( # list.html, # data_listdata_list, # sex_dictsex_dict, # pagepage, # sizesize, # total_pagestotal_pages, # searchsearch # 传给前端用于回显和拼接分页链接 # ) # user_bp.route(/user/add,methods[GET,POST]) # def user_create(): # user_info session.get(user_info) # if not user_info: # return redirect(/login) # if request.method GET: # return render_template(add.html) # else: # username request.form.get(username) # password request.form.get(password) # image request.form.get(image) # sex request.form.get(sex) # remark request.form.get(remark) # create_time datetime.now() # params [username,password,image,sex,remark,create_time] # db.execute(insert into user (username,password,image,sex,remark,create_time) values(%s,%s,%s,%s,%s,%s),params) # return redirect(/user/list) # # user_bp.route(/user/delete) # def delete_create(): # user_info session.get(user_info) # if not user_info: # return redirect(/login) # # # 1. 获取编辑页面传过来的 搜索词 和 页码 # search request.args.get(search, ) # page request.args.get(page, 1) # id request.args.get(id) # params [id] # db.execute(delete from user where id%s, params) # return redirect(f/user/list?page{page}size3search{search}) # user_bp.route(/user/editAction, methods[GET, POST]) # def edit_action(): # user_info session.get(user_info) # if not user_info: # return redirect(/login) # # id int(request.form.get(id)) # username request.form.get(username) # password request.form.get(password) # image request.form.get(image) # sex request.form.get(sex) # remark request.form.get(remark) # # # 1. 获取编辑页面传过来的 搜索词 和 页码 # search request.form.get(search, ) # page request.form.get(page, 1) # # params [username, password, image, sex, remark, id] # db.execute(update user set username%s, password%s, image%s, sex%s, remark%s where id%s, params) # # # 2. 重定向时把 search 和 page 拼回去 # return redirect(f/user/list?page{page}size3search{search}) # user_bp.route(/user/edit) # def edit(): # user_info session.get(user_info) # if not user_info: # return redirect(/login) # # id request.args.get(id) # params [id] # # 查询用户数据 # one db.fetchone(select * from user where id%s , params) # # 状态字典 # sex_dict { # 1: 男, # 2: 女 # } # # 返回模板 # return render_template(edit.html, oneone, sex_dictsex_dict) # #redis版 # 从Cookie获取登录token user_bp.route(/user/list) def user_list(): # 从Cookie获取登录token login_token request.cookies.get(login_token) # 从Redis查询用户信息校验是否登录 login_username get_login_user_from_redis(login_token) # 未登录→强制跳登录页 if not login_username: return redirect(/login) # 1. 获取搜索词和分页参数 search request.args.get(search, ).strip() # 获取搜索词并去除首尾空格 page int(request.args.get(page, 1)) size int(request.args.get(size, 3)) # 2. 拼接 SQL 和参数根据是否有搜索词动态生成 if search: # 有搜索词使用 LIKE 模糊查询 count_sql SELECT COUNT(*) as total FROM user WHERE username LIKE %s data_sql SELECT * FROM user WHERE username LIKE %s order by create_time desc LIMIT %s, %s # %s 不能直接写 %张%必须把 %张% 作为一个整体传给 params like_str f%{search}% total_count db.userCount(count_sql, [like_str]) start_index (page - 1) * size data_list db.fetchAll(data_sql, [like_str, start_index, size]) else: # 无搜索词查询全部 count_sql SELECT COUNT(*) as total FROM user data_sql SELECT * FROM user order by create_time desc LIMIT %s, %s total_count db.userCount(count_sql, []) start_index (page - 1) * size data_list db.fetchAll(data_sql, [start_index, size]) # 3. 计算总页数 total_pages math.ceil(total_count / size) if total_count 0 else 1 # 4. 字典 sex_dict {1: 男, 2: 女} # 5. 返回模板注意把 search 也传回前端 return render_template( list.html, data_listdata_list, sex_dictsex_dict, pagepage, sizesize, total_pagestotal_pages, searchsearch # ) user_bp.route(/user/add,methods[GET,POST]) def user_create(): # 从Cookie获取登录token login_token request.cookies.get(login_token) # 从Redis查询用户信息校验是否登录 login_username get_login_user_from_redis(login_token) # 未登录→强制跳登录页 if not login_username: return redirect(/login) if request.method GET: return render_template(add.html) else: username request.form.get(username) password request.form.get(password) image request.form.get(image) sex request.form.get(sex) remark request.form.get(remark) create_time datetime.now() params [username,password,image,sex,remark,create_time] db.execute(insert into user (username,password,image,sex,remark,create_time) values(%s,%s,%s,%s,%s,%s),params) return redirect(/user/list) user_bp.route(/user/delete) def delete_create(): # 从Cookie获取登录token login_token request.cookies.get(login_token) # 从Redis查询用户信息校验是否登录 login_username get_login_user_from_redis(login_token) # 未登录→强制跳登录页 if not login_username: return redirect(/login) # 1. 获取编辑页面传过来的 搜索词 和 页码 search request.args.get(search, ) page request.args.get(page, 1) id request.args.get(id) params [id] db.execute(delete from user where id%s, params) return redirect(f/user/list?page{page}size3search{search}) user_bp.route(/user/editAction, methods[GET, POST]) def edit_action(): # 从Cookie获取登录token login_token request.cookies.get(login_token) # 从Redis查询用户信息校验是否登录 login_username get_login_user_from_redis(login_token) # 未登录→强制跳登录页 if not login_username: return redirect(/login) id int(request.form.get(id)) username request.form.get(username) password request.form.get(password) image request.form.get(image) sex request.form.get(sex) remark request.form.get(remark) # 1. 获取编辑页面传过来的 搜索词 和 页码 search request.form.get(search, ) page request.form.get(page, 1) params [username, password, image, sex, remark, id] db.execute(update user set username%s, password%s, image%s, sex%s, remark%s where id%s, params) # 2. 重定向时把 search 和 page 拼回去 return redirect(f/user/list?page{page}size3search{search}) user_bp.route(/user/edit) def edit(): # 从Cookie获取登录token login_token request.cookies.get(login_token) # 从Redis查询用户信息校验是否登录 login_username get_login_user_from_redis(login_token) # 未登录→强制跳登录页 if not login_username: return redirect(/login) id request.args.get(id) params [id] # 查询用户数据 one db.fetchone(select * from user where id%s , params) # 状态字典 sex_dict { 1: 男, 2: 女 } # 返回模板 return render_template(edit.html, oneone, sex_dictsex_dict)11__init__.pyfrom flask import Flask,request,session,redirect # 导入你 cache.py 里的 Redis 校验方法 from utils.cache import get_login_user_from_redis #session版本 # def auth(): # print(request.path) # if request.path.startswith(/static): # return # # if request.path /login: # #继续向下执行 # return # else: # user_info session.get(user_info) # if user_info: # return # else: # return redirect(/login) # def get_real_name(): # user_info session.get(user_info) # return user_info[real_name] # #使用redis def auth(): print(request.path) if request.path.startswith(/static): return if request.path /login: #继续向下执行 return else: login_token request.cookies.get(login_token) login_username get_login_user_from_redis(login_token) if login_username: return else: return redirect(/login) #redis版本 def get_real_name(): login_token request.cookies.get(login_token) real_name get_login_user_from_redis(login_token) return real_name def create_app(): app Flask(__name__) # app.secret_key asdasdasda; # 现在已经不用 session 了可留可删 from .views import login from .views import user app.register_blueprint(login.login_bp) app.register_blueprint(user.user_bp) app.before_request(auth) # 模板全局函数页面里直接用 {{ get_real_name() }} app.template_global()(get_real_name) return app #普通登录存到session和cookie # 1浏览器每次请求 → 自动带上这个 session Cookie # 2服务器拿到 Cookie → 解密 → 得到 user_info # 3服务器判断你已登录 # 为什么由session改成redis # 1登录流程账号密码校验通过 → 生成唯一 Token → Token 用户信息存入 Redis带过期→ Token 写入浏览器 Cookie # 2访问流程前端请求接口 → 自动携带 Cookie 里的 Token → 后端从 Redis 查 Token 是否有效 → 有效则放行无效跳登录 # 3退出流程删除 Redis 里的 Token → 删除浏览器 Cookie → 彻底登出 # 细节问题: # Redis key 前缀login:token:和你原来的队列 key 完全隔离不会冲突 # Token 用 uuid 全局唯一不会重复、不会被猜解 # Redis 自动过期 访问自动续期闲置 30 分钟自动登出 # 只存用户名不存明文密码到 Redis更安全12cache.pyimport redis import uuid # Redis连接池配置和你原来配置完全一致 POOL redis.ConnectionPool( host127.0.0.1, port6379, password123456, encodingutf-8, decode_responsesTrue, # 自动解码bytes→字符串不用手动转码 max_connections1000 ) # 获取Redis连接对象 def get_redis_conn(): return redis.Redis(connection_poolPOOL) # 以下是登录专用Redis方法 # 登录成功生成唯一Token存入Redis返回Token def save_login_user_to_redis(user_info_dict): user_info_dict数据库查询出来的用户字典user_dict 返回登录token字符串 conn get_redis_conn() # 生成全局唯一登录令牌token login_token str(uuid.uuid4()) # Redis存储用户信息过期时间30分钟1800秒可自行修改 conn.setex( nameflogin:token:{login_token}, time1800, valueuser_info_dict[username] # 保存登录用户名和你原来session逻辑一致 ) return login_token # 根据token从Redis获取登录用户信息 def get_login_user_from_redis(token): conn get_redis_conn() key flogin:token:{token} username conn.get(key) # 续期每次访问自动延长30分钟有效期可选优化 if username: conn.expire(key, 1800) return username # 退出登录删除Redis里的token def delete_login_user_from_redis(token): conn get_redis_conn() conn.delete(flogin:token:{token}) # 你原来的队列方法保留不动 def push_queue(value): conn get_redis_conn() conn.lpush(day20260415_task_queue,value)13db.pyimport pymysql from pymysql import cursors from dbutils.pooled_db import PooledDB POOL PooledDB( creatorpymysql, maxconnections10, mincached2, maxcached5, blockingTrue, setsession[], ping0, hostlocalhost, port3306, userroot, passwordroot, dbtest, charsetutf8 ) def fetchone(sql,param): conn POOL.connection() cursor conn.cursor(cursorcursors.DictCursor) cursor.execute(sql,param) result cursor.fetchone() cursor.close() conn.close() return result def fetchAll(sql,param): conn POOL.connection() cursor conn.cursor(cursorcursors.DictCursor) cursor.execute(sql,param) result cursor.fetchall() cursor.close() conn.close() return result def execute(sql,param): conn POOL.connection() cursor conn.cursor(cursorcursors.DictCursor) cursor.execute(sql,param) conn.commit() cursor.close() conn.close() return cursor.lastrowid def userCount(sql, param): 新增查询数据条数返回 int 数字 使用示例count(select count(*) as num from user where status%s, [1]) conn POOL.connection() cursor conn.cursor(cursorcursors.DictCursor) cursor.execute(sql, param) result cursor.fetchone() # 取出第一条数据的第一个值返回 int 类型 num result.get(list(result.keys())[0]) if result else 0 cursor.close() conn.close() return num CREATE TABLE user ( id INT ( 11 ) NOT NULL AUTO_INCREMENT, username VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, password VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, image VARCHAR ( 255 ) DEFAULT NULL, sex INT ( 11 ) DEFAULT NULL, remark VARCHAR ( 255 ) CHARACTER SET utf8 COLLATE utf8_general_ci DEFAULT NULL, create_time datetime DEFAULT NULL, PRIMARY KEY ( id ) ) ENGINE MyISAM AUTO_INCREMENT 38 DEFAULT CHARSET utf8; 14app.pyfrom test20260415 import create_app app create_app() if __name__ __main__: app.run(debugTrue)没了