Flask+LayUI开发手记(七):头像的上传及突破static目录限制

article/2025/6/22 19:45:19

         看了看,上篇开发手记是去年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);
 


http://www.hkcw.cn/article/IxIvgZwhdc.shtml

相关文章

第一篇:揭示模型上下文协议(MCP):AI的通用连接器

模型上下文协议&#xff08;MCP&#xff09;是 Anthropic 于 2024 年末推出的一项开放标准&#xff0c;旨在彻底改变人工智能&#xff08;AI&#xff09;模型与外部数据源及工具的连接方式。它被誉为 AI 应用的“USB-C 接口”&#xff0c;其核心目的是标准化 AI 助理与数据所在…

(九)学生写作画像可视化

在上次报告中提到的无法正确识别登录状态的问题已经解决&#xff0c;现在调用后端api时可以正确load_user并得到登录状态。 登录状态问题解决后&#xff0c;本次主要实现的是学生写作画像的数据可视化&#xff0c;学生可以登陆后查看自己之前的作文列表与历史各维度得分&#x…

国家能源集团称从未设拓展中心 警惕假冒机构

国家能源集团称从未设拓展中心 警惕假冒机构。国家能源集团近期发现有不法分子假冒其名义在全国多地设立所谓“拓展中心”,涉嫌从事违法活动,并通过抖音、微信视频号等网络平台进行虚假宣传。对此,国家能源集团发布严正声明,强调从未在全国任何地区设立任何“拓展中心”或“…

官方证实!美国停止对华芯片设计软件(EDA)销售 打压“中国芯”再升级

面对中国在先进芯片领域取得的重大突破,美国特朗普政府将目光转向了设计芯片的软件,试图借此打压“中国芯”。英国《金融时报》5月28日报道称,美国政府已采取措施限制向中国出售此类产品。美国彭博社也报道了类似消息,称美国商务部工业与安全局已致函部分头部美国电子设计自…

乌称对俄军机发动大规模袭击 摧毁41架战略轰炸机

俄乌第二轮谈判定于6月2日举行。谈判前夕,乌克兰声称对俄罗斯实施了一次特别行动,摧毁了41架俄战略轰炸机。然而,俄媒认为乌方宣称的战果并不符合实际。乌克兰国家安全局在社交媒体上表示,6月1日晚对俄罗斯发起了一次代号为“蛛网”的特种作战行动,袭击了俄军的战略轰炸机…

离婚,失业,失亲……她40岁后的人生那么难?美丽无关年龄

离婚,失业,失亲……她40岁后的人生那么难?美丽无关年龄!美丽与年龄无关,即便他人不再认同你。都说年龄是女人最大的秘密,尤其是生儿育女之后,“衰老”这个词便成了女人内心最大的恐惧和痛处。如今,三八妇女节甚至被迫更名为“三八女神节”,以消除称呼上的忌讳。然而,…

巴西登革热疑似及确诊病例超143万 疫情严峻引发关注

巴西卫生部发布消息称,截至6月2日,今年该国登革热疑似及确诊病例已达到1430300例,死亡病例为1075例,另有818例疑似死亡病例正在调查中。圣保罗州、米纳斯吉拉斯州和巴拉那州是目前巴西登革热疫情最为严重的地区。责任编辑:zhangxiaohua

四川古蔺警方通报7人殴打未成年人 误会引发暴力事件

四川古蔺县警方于6月2日通报了一起涉及未成年人的暴力事件。据报道,两名未成年人在骑车时发出笑声,被15岁的陈某甲误以为是在嘲笑自己。随后,陈某甲与其他六人一起在地下停车场对这两名未成年人进行了殴打。目前,七名涉案人员均已被警方抓获,其中两人因涉嫌犯罪被刑事拘留…

多位男星减肥成功 压力已给到沈腾 贾冰瘦身引热议

5月31日,贾冰的妻子发布了一段视频,并配文“从此我家多了个瘦子”。在两人合影中,贾冰明显瘦了很多。评论区里大家纷纷询问他如何瘦下来的,甚至有人表示瘦得认不出来了。贾冰的妻子回复说,主要是通过少吃(一天只吃一顿)和运动来减肥的。贾冰在评论区幽默地回应说:“一次…

任务25:绘制全局时间线(TimeLine)

任务描述 知识点: DjangoECharts时间线图重 点: ECharts时间线图内 容: 参考ECharts官网示例创建timeline.js,绘制时间线图引入js文件,并调用绘图函数时间线图形配置项微调任务指导 1、参考ECharts官网示例:(https://echarts.apache.org/examples/zh/index.html),…

2025年渗透测试面试题总结-青藤云[校招]红队攻防岗(题目+回答)

安全领域各种资源&#xff0c;学习文档&#xff0c;以及工具分享、前沿信息分享、POC、EXP分享。不定期分享各种好玩的项目及好用的工具&#xff0c;欢迎关注。 目录 青藤云[校招]红队攻防岗 1. 数据库提权 2. Redis计划任务提权 3. 绕过杀软执行数据库命令 4. SQL Server…

HCIP:MPLS LDP的原理和配置

目录 一、MPLS LDP的原理 1.回顾MPLS 2.MPLS LDP的基本概念 3.LDP的工作过程主要分为两部分 ​4.LDP会话&#xff1a; 5.LDP的对等体 6.LDP的消息 7.LDP工作原理 8.标签的发布和管理 二、MPLD LDP的实验配置 1.配置IP地址和OSPF 2.配置MPLS LDP 3.查看各个设备的…

这个地级市,“含潮量”全国顶流? 潮玩之都的转型之路

这个地级市,“含潮量”全国顶流? 潮玩之都的转型之路!东莞市中心矗立着一座12米高的巨型潮玩雕塑“劳拉”,其轮廓以彩色线条勾勒,充满科幻感。这座2021年落成的城市新地标脚下,延伸出一条由设计工作室、潮玩展销中心和沉浸式体验馆串联的“潮玩大道”。这里不定期举行沙龙…

男子机场取车遭一嗨租车拦车 店员态度恶劣引发争议

男子机场取车遭一嗨租车拦车 店员态度恶劣引发争议!6月2日,苏先生在昆明长水机场停车时遇到了麻烦。他将车停在B2层S1区C7车位,该车位未标注“专用车位”且无人值守。然而当他返回取车时,“一嗨租车”的工作人员告知他该车位已被他们租下,并称他的车停到了他们的店里。苏先…

今年第一批吃菌子中毒的人出现了!小伙吃野生菌后竟开始隔空抓凤凰

近日,云南临沧,胡先生吃野生菌后中毒致幻,躺在病床上隔空抓物。胡先生称自己看到了凤凰、乌贼、水母、蜘蛛、螃蟹......除胡先生外,此前一对昆明情侣因吃见手青中毒,在医院过的“520”近日,有IP地址为昆明的网友发帖称,自己和丈夫在家做菌子吃,结果两人都出现中毒症状,…

乒超联赛门票开售 雄安首迎顶尖赛事

6月9日至11日,2025赛季中国乒乓球俱乐部超级联赛常规赛第一阶段比赛将在河北雄安新区雄安体育中心体育馆举行。门票于6月3日18:00在秀动票务平台开售,票价从288元到788元不等。2025赛季乒超联赛包括男子和女子团体赛,分为三个阶段的常规赛和总决赛,从6月持续到12月。常规赛…

【iOS】ARC 与 Autorelease

ARC 与 Autorelease 文章目录 ARC 与 Autorelease前言何为ARC内存管理考虑方式自己生成的对象,自己持有非自己生成的对象,自己也可以持有不再需要自己持有的对象时释放非自己持有的对象无法释放 ARC的具体实现编译期和运行期ARC做的事情ARC实现: __autoreleasing 与 Autoreleas…

成都一断头路骑手受伤倒地不幸身亡

6月2日,一位网友发视频称,他在前往四川乐山游玩的路上误入成都一个胡同,发现一名摩托车骑手受伤倒在地上,于是帮忙拨打了120急救电话。该网友表示,此事于6月1日发生在成都天府新区东山大道三段附近一处路段,骑手因伤势过重离世,“如果能更早发现,或许还能被抢救回来。”…

媒体:汽车行业的价格战该“刹车”了 行业协会倡议维护健康发展

“618”限时促销活动期间,部分国内车企发起了大幅降价活动,最高直降5.3万元,这标志着新能源汽车市场新一轮“价格战”开始。山东交通广播汽车栏目制作人、资深汽车媒体人胡明表示,这次价格战始于一家车企在5月23日发起的大规模降价,主要原因是该品牌面临更多有竞争力的对手…

男子免费帮老人收麦被强行投喂饮料 河南老乡的热情感动麦客

男子免费帮老人收麦被强行投喂饮料 河南老乡的热情感动麦客!近日,多名跨区作业的麦客通过视频表达了对河南农民的热情感激。这些麦客在河南割麦子期间,不断收到当地老乡的“投喂”。宝歌(化名)是一位来自河北的麦客。5月28日至30日,他在河南漯河割麦子时,一天内收到了五…