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

news2025/6/7 14:41:41

         看了看,上篇开发手记是去年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){
                // 预读本地文件示例,不支持ie8
                obj.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 Image

ADMIN_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.filename
        extname = 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_path
        if 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_path
        db.session.commit()
        session[ADMIN_USER_AVATAR] = sava_path
        g.admin_avatar = sava_path
    return 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 ..' + filename
    return 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.coloradmin.cn/o/2403014.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

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…

MCP协议重构AI Agent生态:万能插槽如何终结工具孤岛?

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

阿里云事件总线 EventBridge 正式商业化,构建智能化时代的企业级云上事件枢纽

作者&#xff1a;肯梦、稚柳 产品演进历程&#xff1a;在技术浪潮中的成长之路 早在 2018 年&#xff0c;Gartner 评估报告便将事件驱动模型&#xff08;Event-Driven Model&#xff09;列为十大战略技术趋势之一&#xff0c;指出事件驱动架构&#xff08;EDA&#xff0c;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

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

Pytorch安装后 如何快速查看经典的网络模型.py文件(例如Alexnet,VGG)(已解决)

当你用conda 安装好虚拟环境后&#xff0c; 找到你的Anaconda 的安装位置。 我的在D盘下&#xff1b; 然后 从Anaconda3文件夹开始&#xff1a;一级一级的查看&#xff0c;一直到models Anaconda3\envs\openmmlab\Lib\site-packages\torchvision\models 在models下面&#x…

有人-无人(人机)交互记忆、共享心智模型与AI准确率的边际提升

有人-无人&#xff08;人机&#xff09;交互记忆、共享心智模型与AI准确率的边际提升是人工智能发展中相互关联且各有侧重的三个方面。人机交互记忆通过记录和理解用户与机器之间的交互历史&#xff0c;增强机器对用户需求的个性化响应能力&#xff0c;从而提升用户体验和协作效…

【OpenGL学习】(五)自定义着色器类

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

408第一季 - 数据结构 - 栈与队列的应用

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

超声波清洗设备的清洗效果如何?

超声波清洗设备是一种常用于清洗各种物体的技术&#xff0c;它通过超声波振荡产生的微小气泡在液体中破裂的过程来产生高能量的冲击波&#xff0c;这些冲击波可以有效地去除表面和细微裂缝中的污垢、油脂、污染物和杂质。超声波清洗设备在多个领域得到广泛应用&#xff0c;包括…

“草台班子”的成长路径分析

一、草台班子的起点&#xff1a;用最小成本验证价值 特点&#xff1a; 团队规模小&#xff08;通常3-5人&#xff09;&#xff0c;成员背景杂&#xff08;可能是程序员产品经理运营的混搭&#xff09;&#xff1b;资源匮乏&#xff08;无资金、无技术中台、无客户积累&#x…

软件测评服务如何依据标准确保品质?涵盖哪些常见内容?

软件测评服务涉及对软件的功能和性能等多维度进行评估和检验&#xff0c;这一过程有助于确保软件的品质&#xff0c;降低故障发生率及维护费用&#xff0c;对于软件开发和维护环节具有至关重要的价值。 测评标准依据 GB/T 25000.51 - 2016是软件测评的核心依据。依照这一标准…

Python打卡第46天

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

Unity优化篇之DrawCall

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

SpringCloud学习笔记-2

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

从混乱到秩序:探索管理系统如何彻底改变工作流程

内容摘要 在许多企业与组织中&#xff0c;工作流程混乱是阻碍发展的“绊脚石”。员工们常常被繁琐的步骤、模糊的职责和沟通不畅等问题搞得焦头烂额&#xff0c;工作效率低下&#xff0c;错误频发。而与之形成鲜明对比的是&#xff0c;一些引入了先进管理系统的团队&#xff0…

最新研究揭示云端大语言模型防护机制的成效与缺陷

一项全面新研究揭露了主流云端大语言模型&#xff08;LLM&#xff09;平台安全机制存在重大漏洞与不一致性&#xff0c;对当前人工智能安全基础设施现状敲响警钟。该研究评估了三大领先生成式AI平台的内容过滤和提示注入防御效果&#xff0c;揭示了安全措施在阻止有害内容生成与…

HTML5+CSS3+JS小实例:具有粘性重力的磨砂玻璃导航栏

实例:具有粘性重力的磨砂玻璃导航栏 技术栈:HTML+CSS+JS 效果: 源码: 【HTML】 <!DOCTYPE html> <html lang="zh-CN"><head><meta charset="UTF-8"><meta name="viewport" content="width=device-width…

Python爬虫伪装

一、网站防爬机制概述 在当今互联网环境中&#xff0c;具有一定规模或盈利性质的网站几乎都实施了各种防爬措施。这些措施主要分为两大类&#xff1a; 身份验证机制&#xff1a;直接将未经授权的爬虫阻挡在外反爬技术体系&#xff1a;通过各种技术手段增加爬虫获取数据的难度…

Webpack的基本使用 - babel

Mode配置 Mode配置选项可以告知Webpack使用相应模式的内置优化 默认值是production&#xff08;什么都不设置的情况下&#xff09; 可选值有&#xff1a;none | development | production; 这几个选项有什么区别呢&#xff1f; 认识source-map 我们的代码通常运行在浏览器…