Flask+LayUI开发手记(七):头像的上传及突破static目录限制
看了看,上篇开发手记是去年8月份写的,到现在差2个月整一年了。停更这么长时间,第一个原因是中间帮朋友忙一个活,那个技术架构是用springboot的,虽然前端也用layUI,但和Flask-python完全不搭界,所以,有半年时间就忙那事了。第二个原因,从今年三月份虽然又开始继续做Flask+Layui的框架,不过对layUI的编程已经从造猫画虎的模仿阶段变成了理解其内在机制可以天马行空实现功能的阶段了。
现在再看前面写的这些手记,如果用两个字来形容,那就是“生涩”(其实我很想说垃圾的,但这样说去年的自己,确实不太好)。
讲真,从layui.use()到layui.define()再到layui.config(),这一层层学上来,把这三个都理解透了,也敢用了,才敢真正说自己掌握了layui的脉络,也才能理解layui的强大。虽然layUI现在确实已经不流行了(不过还是在更新中),之所以选择这个,还是因为这是一个传统程序员最喜欢的工具箱,而不是VUE那种加入诸多工具的框架。
你可以随时在原生JS、JQuery和layUI的编程之间无缝切换,遇到困难,觉得layUI里有什么好用就马上拿过来用,没好用的,转头上网找个小工具程序加进来,也可以。而不是“一入框架深似海,从此JS成路人”那样,被粘上后就只能在框架打滚了。当然,VUE和layUI本来就是两个层次的东西,两个是可以结合的,这也是下一步准备尝试的。
而且,就算某一天layUI不更新完全过气了,有在layUI上的编程经验,转身去学element-UI也没啥难度,其实这些工具箱的思路都是一样的,就是最大程度的把一些编程中经常用到的组件模块化工具化。在这个AI的时代,遇到问题可以通过各种途径去查答案,限制程序员能力的从来都只是想象力,而不是技术水平了。
好吧,废话不多说,继续上次的手记,这次介绍一下头像上传的功能实现,同样也是前端用layUI的上传组件,后台用flask-python的接收文件功能。加一点特色的地方,就是上传的头像目录没有放在static下面,而是在项目根目录下新开了一个srvdata目录,在上传文件时当然不是问题,但是html静态文件的<img>标签中内嵌头像文件名时就出问题了,好在这些也都解决了。
整个程序分成了三个部分,第一是前端页面+JS程序,第二个是服务端接收文件的路由服务程序,第三个是如何在静态html中实现图像文件不在static目录下。
首先是第一部分前端页面程序,这个包括html的界面展示和JS的程序实现。注意,现在的程序里,将完全取消掉jinja2模板的编程实现,所有后端和前端的数据交互均通过ajax/post完成。这主要是在年初接触了restful API编程概念,前后端分离,前端进行流控,后端无状态只提供资源,觉得完全是我想要的,所以,基本把以前的程序都翻了一遍,除了页面流转用到render_template()外,其它flask提供的前后端连接函数基本都弃了。
<!DOCTYPE html>
<html>
<head><meta charset="utf-8"><meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"><title>设置头像</title><link rel="stylesheet" href="/static/layui/css/layui.css" media="all">
</head>
<body>
<div style="padding:20px;"><form class="layui-form" id="avatarform" action="" enctype='multipart/form-data' method="post" lay-filter="avatarform" ><div class="layui-form-item"><label class="layui-form-label">上传头像</label><div class="layui-input-block"><img id="userAvatar" src="/static/images/avatar/avatar_def.png" alt="默认头像" width="100" height="100"><div class="layui-row layui-inline" style="margin-left:40px;width:420px;"><div class="layui-row layui-inline" style="width:38%"><button type="button" class="layui-btn" id="ID-upload-btn"><i class="layui-icon layui-icon-username"></i>更换头像</button><button type="button" class="layui-btn" id="ID-upload-action" style="margin-top:10px"><i class="layui-icon layui-icon-upload"></i>开始上传</button><div class="layui-word-aux">图片限制2MB以下</div></div><div class="layui-row layui-inline" style="width:40%"><div class="layui-upload-list layui-inline" style="text-align:center;width:120px"><img class="layui-upload-img" id="ID-upload-img" style="width:100%; height: 92px;"><div id="ID-upload-text"></div><div class="layui-progress layui-progress-big" lay-showPercent="yes" lay-filter="filter-upload"><div class="layui-progress-bar" lay-percent=""></div></div></div></div></div></div></form>
</div>
<script src="/static/layui/layui.js"></script>
<script>layui.use(['layer'], function () {var $ = layui.jquery,layer = layui.layer,element = layui.element,upload = layui.upload;let loginInfo = JSON.parse(sessionStorage.getItem('loginInfo'));console.log('loginInfo:',loginInfo);user_avatar = loginInfo.user_avatar;if (user_avatar && user_avatar!='None') {$('#userAvatar').attr('src',user_avatar);}var uploadInst = upload.render({elem: '#ID-upload-btn',url: '/avatar', auto : false,bindAction: '#ID-upload-action',size : 2000,acceptMime: 'image/*',choose: function(obj){// 预读本地文件示例,不支持ie8obj.preview(function(index, file, result){$('#ID-upload-img').attr('src', result); // 图片链接(base64)});},before: function(obj){element.progress('filter-upload', '0%'); // 进度条复位layer.msg('上传中', {icon: 16, time: 0});},done: function(res){// 若上传失败if(res.success == 0){return layer.msg('上传失败');}// 上传成功的操作$('#ID-upload-text').html(''); // 置空上传失败的状态let src_file = res.avatar + '?time=' + new Date().getTime();$('#userAvatar').attr('src',src_file);let p_userava = parent.layui.$('#userAvatar');$(p_userava).attr('src',src_file);},error: function(){// 演示失败状态,并实现重传var demoText = $('#ID-upload-text');demoText.html('<span style="color: #FF5722;">上传失败</span> <a class="layui-btn layui-btn-xs demo-reload">重试</a>');demoText.find('.demo-reload').on('click', function(){uploadInst.upload();});},// 进度条progress: function(n, elem, e){element.progress('filter-upload', n + '%'); // 可配合 layui 进度条元素使用if(n == 100){layer.msg('上传完毕', {icon: 1});}}});});
</script>
</body>
</html>
html部分就不仔细介绍了,基本是从layUI教程中扒下来的示例,只是做了一些界面设计,如下图这样。流程上就是先点击“更换头像”选择本地图像文件,之后在右框中会显示缩微图像,如果满意,再点击“开始上传”,之后,会显示进度条,传完后,就OK。
JS程序也不复杂,主体就是调用layUI的upload文件上传控件,相关的内容说明文档中都有。本程序为了详细测试一下upload组件,采用了两阶段提交模式,即先选择文件之后再上传,所以用到choose参数,正常的图像上传建议还是选择上传自动连续比较好。
JS开头部分是设置头像文件路径,先从sessionStorage中取出loginInfo,这个对象存储了用户相关的注册信息,包括用户ID、姓名以及头像文件路径,是在系统主框架部分从服务端取下来放到客户端session中的,然后系统中所有的页面程序均可取出来使用。sessionStorage中只能存字符串,所以,要存储loginInfo,得先用JSON.stringify()将其变为字符串再存,相应的,取出时则用JSON.parse()解析回对象即可。
下面是第二部分,服务端的python程序。主体就是接收图像文件,将其更名为“avatar_用户ID.png”的文件,并存入到srvdata/uploads/avatar这个目录下。在存储成功后,要去更新用户表中将对相应的avatar文件路径字段,并修改系统中一些环境变量,在调试时可以去掉那些不用的东西。
from flask import Blueprint,send_from_directory,render_template
from flask import make_response,Response,request,session,g,jsonify
from io import BytesIO
import json
from PIL import ImageADMIN_USER_AVATAR = "HEBOANHEAV"
ADMIN_USER_ID = 'HEBOANHEHE'
SRVDATA_UPLOAD = 'srvdata/uploads'#头像服务
@app.route('/avatar',methods=['GET','POST'])
@login_required
def avatar():if request.method == 'GET':return render_template('admin/avatar.html.j2')else:avt = request.files.get('file')sour_file_name = avt.filenameextname = sour_file_name.split('.')[1]uid = session[ADMIN_USER_ID] new_file_name = 'avatar_' + str(uid) + '.' + 'png'save_path = SRVDATA_UPLOAD + '/avatar/' + new_file_name#avt.save(save_path)img = Image.open(avt)#img = img.resize(128,128)img.save(save_path,'PNG')avatar_file = '/' + save_pathif avatar_userupdate(uid,avatar_file) :rs_data = {'success':1,'msg':'更新头像成功','avatar':avatar_file,'code':0}else :rs_data = {'success':0,'msg':'更新头像失败','avatar': '','code':201}return json.dumps(rs_data)def avatar_userupdate(id,sava_path):irow = db.session.query(Users).filter_by(id=id).first()if irow :irow.avatar = sava_pathdb.session.commit()session[ADMIN_USER_AVATAR] = sava_pathg.admin_avatar = sava_pathreturn True
上面两段程序交互后,即可把头像文件上传,如果图像文件上传到static目录下,那程序到这儿就结束了。但是,熟悉JAVA/WEB编程的人都知道,static目录只能存静态文件,象上传下载的文件,应该开辟新目录,省得对程序打包安装时出麻烦。不过,当在flask编程时这么想时,那麻烦就出来了,静态html页面中<img>src="文件名“</img>中这个文件名必须在static目录下,放在别的目录,系统提示404。
好在虽然flask没有啥地方能配置增加资源目录,但还是有变通办法解决的,就是写下面一段程序来解决。
from flask import Flask,send_from_directory@app.route('/srvdata/<path:filename>')
def server_get_file(filename):logging.debug('srvdata...... %s' % filename)#return 'srvdata ..' + filenamereturn send_from_directory('srvdata/', filename)
写一个路由程序,将文件名带入,。这段程序十分短小,但却真是能解决大问题,开始还没看明白,后来是越看越觉得思路巧妙。flask服务,页面上任何的路径都会先被理解为路由,找不到路由服务的话,才会被当成文件路径来处理,这段程序就是使用了这个规则。
在html页面上定义的图像文件是一个全路径名称,比如”/srvdata/uploads/avatar/avatar-4.png",那么我们就定义一个与主目录名完全一样的路由程序,路由命名为“主路由+路径参数”,这样所有在此目录下的文件名都会被定向到到这个路由服务中,之后要做的,就是如何把文件下传了,send_from_directory()就是干这个活的。 上传完了的效果是这样的。
同时,别忘了把系统主框架上的用户头像更新一下。JS程序中这两句就是做这个用的。
let p_userava = parent.layui.$('#userAvatar');
$(p_userava).attr('src',src_file);
相关文章:

Flask+LayUI开发手记(七):头像的上传及突破static目录限制
看了看,上篇开发手记是去年8月份写的,到现在差2个月整一年了。停更这么长时间,第一个原因是中间帮朋友忙一个活,那个技术架构是用springboot的,虽然前端也用layUI,但和Flask-python完全不搭界,所…...
uv管理spaCy语言模型
本文记录如何在使用uv管理python项目dependencies时,把spaCy的模型也纳入其中. spaCy 一、spaCy简介 spaCy是一个开源的自然语言处理(NLP)库,它主要用于处理文本数据。它支持多种语言,包括英语、中文等。它是由Expl…...

MiniExcel模板填充Excel导出
目录 1.官方文档 2. 把要导出的数据new一个匿名对象 3.导出 4.注意事项 5.模板制作 6.结果 1.官方文档 https://gitee.com/dotnetchina/MiniExcel/#%E6%A8%A1%E6%9D%BF%E5%A1%AB%E5%85%85-excel // 1. By POCO var value new {Name "Jack",CreateDate n…...
NoSQL之redis哨兵
一、哨兵的核心功能 监控(Monitoring) 持续检查主节点和从节点的运行状态(是否存活、延迟等)。 自动故障转移(Automatic Failover) 当主节点不可用时,自动选举一个从节点升级为主节点。 更新…...

MCP协议重构AI Agent生态:万能插槽如何终结工具孤岛?
前言 在人工智能技术快速发展的2025年,MCP(Model Context Protocol,模型上下文协议)正逐渐成为AI Agent生态系统的关键基础设施。这一由Anthropic主导的开放协议,旨在解决AI模型与外部工具和数据源之间的连接难题,被业界形象地称…...

阿里云事件总线 EventBridge 正式商业化,构建智能化时代的企业级云上事件枢纽
作者:肯梦、稚柳 产品演进历程:在技术浪潮中的成长之路 早在 2018 年,Gartner 评估报告便将事件驱动模型(Event-Driven Model)列为十大战略技术趋势之一,指出事件驱动架构(EDA,Eve…...

CentOS8.3+Kubernetes1.32.5+Docker28.2.2高可用集群二进制部署
一、准备工作 1.1 主机列表 HostnameHost IPDocker IPRolek8s31.vm.com192.168.26.3110.26.31.1/24master&worker、etcd、dockerk8s32.vm.com192.168.26.3210.26.32.1/24master&worker、etcd、dockerk8s33.vm.com192.168.26.3310.26.33.1/24master&worker、etcd、…...

学习日记-day23-6.6
完成目标: 知识点: 1.IO流_转换流使用 ## 转换流_InputStreamReader1.字节流读取中文在编码一致的情况,也不要边读边看,因为如果字节读不准,读不全,输出的内容有可能会出现乱码 2.所以,我们学了字符流,字符流读取文本文档中的内容如果编码一致,就不会出…...

Pytorch安装后 如何快速查看经典的网络模型.py文件(例如Alexnet,VGG)(已解决)
当你用conda 安装好虚拟环境后, 找到你的Anaconda 的安装位置。 我的在D盘下; 然后 从Anaconda3文件夹开始:一级一级的查看,一直到models Anaconda3\envs\openmmlab\Lib\site-packages\torchvision\models 在models下面&#x…...
《ERP原理与应用教程》第3版习题和答案
ERP原理与应用教程是一门系统介绍企业资源计划(Enterprise Resource Planning, ERP)系统核心理论、技术架构及实施应用的综合性课程。它主要面向管理类、信息类、工程类等专业学生及企业管理者,旨在培养对现代企业信息化管理的理解与实践能力。以下是该课程的详细解析: 一…...
JavaScript中的正则表达式:文本处理的瑞士军刀
JavaScript中的正则表达式:文本处理的瑞士军刀 在编程世界中,正则表达式(Regular Expression,简称RegExp)被誉为“文本处理的瑞士军刀”。它能够高效地完成字符串匹配、替换、提取和验证等任务。无论是前端开发中的表…...
vue对axios的封装和使用
在 Vue 项目中,使用 axios 进行 HTTP 请求是非常常见的做法。为了提高代码的可维护性、统一错误处理和请求拦截/响应拦截逻辑,对axios进行封装使用。 一、基础封装(适用于 Vue 2 / Vue 3) 1. 安装 axios npm install axios2. 创…...
软考 系统架构设计师系列知识点之杂项集萃(82)
接前一篇文章:软考 系统架构设计师系列知识点之杂项集萃(81) 第148题 “41”视图主要用于描述系统逻辑架构,最早由Philippe Kruchten于1995年提出。其中( )视图用于描述对象模型,并说明系统应该…...
DrissionPage调试工具:网页自动化与数据采集的革新利器
在网页自动化测试与数据采集领域,开发者长期面临两难选择:使用Selenium等工具操作浏览器时效率不足,而直接调用Requests库又难以应对复杂动态页面。DrissionPage的出现完美解决了这一矛盾,这款基于Python开发的工具创新性地将浏览…...

有人-无人(人机)交互记忆、共享心智模型与AI准确率的边际提升
有人-无人(人机)交互记忆、共享心智模型与AI准确率的边际提升是人工智能发展中相互关联且各有侧重的三个方面。人机交互记忆通过记录和理解用户与机器之间的交互历史,增强机器对用户需求的个性化响应能力,从而提升用户体验和协作效…...
如何使用k8s安装redis呢
在Kubernetes (k8s) 上安装Redis 在Kubernetes上安装Redis有几种方法,下面我将介绍两种常见的方式:使用StatefulSet直接部署和使用Helm chart部署。 一、安装redis 1.1 拉去ARM镜像(7.4.2) docker pull registry.cn-hangzhou.ali…...
AI对测试行业的应用
AI对测试行业的应用 AI技术在软件测试领域的应用已从概念验证全面迈向工程化落地,正在重构测试流程、提升效率边界,并为质量保障体系带来范式级变革。以下从技术突破、行业实践与未来趋势三个维度展开深度解析: ⚙️ 一、核心技术突破&#…...

【OpenGL学习】(五)自定义着色器类
文章目录 【OpenGL学习】(五)自定义着色器类着色器类插值着色统一着色 【OpenGL学习】(五)自定义着色器类 项目结构: 着色器类 // shader_s.h #ifndef SHADER_H #define SHADER_H#include <glad/glad.h>#inc…...

408第一季 - 数据结构 - 栈与队列的应用
括号匹配 用瞪眼法就可以知道的东西 栈在表达式求值运用 先简单看看就行,题目做了就理解了 AB是操作符,也是被狠狠加入后缀表达式了,然后后面就是*,只要优先级比栈顶运算符牛逼就放里面,很显然,*比牛逼 继续前进&#…...

超声波清洗设备的清洗效果如何?
超声波清洗设备是一种常用于清洗各种物体的技术,它通过超声波振荡产生的微小气泡在液体中破裂的过程来产生高能量的冲击波,这些冲击波可以有效地去除表面和细微裂缝中的污垢、油脂、污染物和杂质。超声波清洗设备在多个领域得到广泛应用,包括…...
k8s部署dify
以前部署过,最近重新部署发现还是存在很多问题,这里进行记录 1.基础配置内容 配置信息和账号密码 # dify-deployment.yaml--- # Namespace apiVersion: v1 kind: Namespace metadata:name: dify-min--- # ConfigMap for shared environment variables…...

“草台班子”的成长路径分析
一、草台班子的起点:用最小成本验证价值 特点: 团队规模小(通常3-5人),成员背景杂(可能是程序员产品经理运营的混搭);资源匮乏(无资金、无技术中台、无客户积累&#x…...
RAG技术解析:实现高精度大语言模型知识增强
RAG技术解析:实现高精度大语言模型知识增强 RAG概述 RAG(Retrieval-Augmented Generation,检索增强生成)是一种结合检索系统和生成模型的技术架构,旨在提高大语言模型回答问题的准确性和相关性。当遇到如"如何退…...

软件测评服务如何依据标准确保品质?涵盖哪些常见内容?
软件测评服务涉及对软件的功能和性能等多维度进行评估和检验,这一过程有助于确保软件的品质,降低故障发生率及维护费用,对于软件开发和维护环节具有至关重要的价值。 测评标准依据 GB/T 25000.51 - 2016是软件测评的核心依据。依照这一标准…...
大数据学习(131)-Hive数据分析函数总结
🍋🍋大数据学习🍋🍋 🔥系列专栏: 👑哲学语录: 用力所能及,改变世界。 💖如果觉得博主的文章还不错的话,请点赞👍收藏⭐️留言Ǵ…...
SCAU数值计算OJ
18957.计算自然对数ln(x)的导数 Description 求自然对数ln(x)的导数,输入双精度实数x>1,输出自然对数ln(x)的导数(精确到小数点后2位有效数,小数点后第2位四舍五入所得)。输入格式 m(整数,实验数据总…...
c++ 基于openssl MD5用法
基于openssl MD5用法 #include <iostream> #include <openssl/md5.h> using namespace std; int main(int argc, char* argv[]) { cout << "Test Hash!" << endl; unsigned char data[] "测试md5数据"; unsigned char out[1024…...

Python打卡第46天
浙大疏锦行 注意力 注意力机制是一种让模型学会「选择性关注重要信息」的特征提取器,就像人类视觉会自动忽略背景,聚焦于图片中的主体(如猫、汽车)。 从数学角度看,注意力机制是对输入特征进行加权求和,…...

Unity优化篇之DrawCall
当然可以!以下是完整、详尽、可发布的博客文章,专注讲解 Unity 的静态合批与动态合批机制,并详细列出它们对 Shader 的要求和所有限制条件。文章结构清晰、技术深度足够,适合发布在 CSDN、掘金、知乎等技术平台。 urp默认隐藏动态…...

SpringCloud学习笔记-2
说明:来源于网络,如有侵权请联系我删除 1.提问:如果注册中心宕机,远程调用还能成功吗 答:当微服务发起请求时,会向注册中心请求所有的微服务地址,然后在向指定的微服务地址发起请求。在设计实…...