图片上传 el+node后端+数据库

模版部分:

鼠标悬浮到头像的部分就出现下拉框显示可以修改头像,

el-upload是隐藏的,可能只是为了实现on-change函数和before-upload函数吧

这块做的确实有点马虎了。

 <div class="r-content"><el-dropdown><span class="el-dropdown-link"><img :src="getImageUrl" class="avatar"></span><template #dropdown><el-dropdown-menu><el-dropdown-item @click="handleUpdateAvatar">修改头像</el-dropdown-item><el-dropdown-item @click="handleLoginOut">退出登录</el-dropdown-item></el-dropdown-menu></template></el-dropdown><el-uploadclass="avatar-uploader"action="#":show-file-list="true":on-change="handleAvatarChange":before-upload="beforeAvatarUpload"style="display: none;"ref="uploadRef"><!-- <el-button ref="btn" size="large" type="primary">选取文件</el-button> --></el-upload></div>
import { ref, computed,nextTick } from 'vue'
import { useAllDataStore } from '../stores'
import { useRouter } from 'vue-router'
// import { ElMessage,ElUpload } from 'element-plus'
import { handleAvatarChange } from '@/services/editService.js'
const store = useAllDataStore()
import defaultAvatar from '@/assets/images/user-default.png'
const getImageUrl = computed(() => {return store.state.avatar||defaultAvatar; 
})
// 监听状态变化(调试用)
watch(() => store.state.avatar,(newVal, oldVal) => {console.log('头像更新了:', newVal, oldVal)},{ immediate: true } // 立即执行一次
)
const uploadRef = ref(null)
function handleUpdateAvatar() {console.log('handleUpdateAvatar')uploadRef.value.$el.querySelector('input').click()
}// 上传前的校验,比如限制文件类型、大小等
function beforeAvatarUpload(file) {const isJPG = file.type === 'image/jpeg' || file.type === 'image/png';const isLt2M = file.size / 1024 / 1024 < 2; if (!isJPG) {ElMessage.error('上传头像只能是 JPG/PNG 格式!');}if (!isLt2M) {ElMessage.error('上传头像大小不能超过 2MB!');}return isJPG && isLt2M;
}//跨组件之间的传值  
const router = useRouter()
const handleLoginOut = () => {store.clean();router.push('/login')
}

前端发送请求部分:

export const handleAvatarChange=async (file)=> {try {// 创建 FormData 对象,用于上传文件const formData = new FormData();formData.append('file', file.raw); // 调用后端接口上传头像,这里的接口地址根据实际后端定义填写const res = await axios.post(`${API_URL}/updateAvatar`, formData, {headers: {'Content-Type': 'multipart/form-data', 'Authorization':`Bearer ${localStorage.getItem('token')}`,},withCredentials: true});if (res.data.code === 200) { // 假设后端返回 code 为 200 表示成功ElMessage.success('头像修改成功');// 更新 store 中的头像地址store.updateImg(res.data.data.avatar);}} catch (error) {// console.error('上传头像出错!:', error);ElMessage.error('网络异常,头像修改失败!');}
}

后端处理请求部分:

import { Router } from 'express';
import multer from 'multer';
import path from 'path';
import { fileURLToPath } from 'url';
import fs from 'fs/promises';
import pool from '../config/db.js';
import jwt from 'jsonwebtoken';
import  { JWT_SECRET }  from '../config/config.js';
import bodyParser from 'body-parser';
const router = Router();// 1. 直接通过 import.meta.url 计算上传目录路径(不使用 __dirname)
const currentFileUrl = new URL(import.meta.url); //当前文件完整的地址
const currentDirPath = path.dirname(fileURLToPath(currentFileUrl)); // 当前文件所在目录routes的地址
const uploadDir = path.join(currentDirPath, '../public/avatars'); // 拼接上传目录路径
// console.log('uploadDir:',uploadDir);
// 初始化上传目录
try {await fs.access(uploadDir);
} catch {await fs.mkdir(uploadDir, { recursive: true });
}// 2. 配置 multer 存储规则
const storage = multer.diskStorage({destination: (req, file, cb) => {cb(null, uploadDir);},filename: (req, file, cb) => {const safeName = file.originalname.replace(/[^a-zA-Z0-9_.-]/g, '');const uniqueName = `${Date.now()}-${safeName}`;cb(null, uniqueName);//生成唯一文件名}
});// 3. 创建 multer 实例
const upload = multer({limits: { fileSize: 2 * 1024 * 1024 },fileFilter: (req, file, cb) => {if (file.mimetype.startsWith('image/')) {cb(null, true);} else {cb(new Error('只允许上传图片文件!'), false);}},storage: storage
});// 4.添加认证中间件  
const authenticate = (req, res, next) => {const token = req.headers.authorization?.split(' ')[1];console.log(req.headers);if (!token) {return res.status(401).json({ code: 401, message: '未提供Token认证信息!' });}try {// 获取发送请求方的token信息,验证发送人   同时在post请求中顺利的修改请求人的数据库字段const decoded = jwt.verify(token, JWT_SECRET);req.user = decoded;console.log('req.user:',req.user);next();} catch (error) {res.status(401).json({ code: 401, message: '这是无效或者过期的Token!' });
}
}
// 5. 处理头像上传请求
router.post('/updateAvatar',authenticate, upload.single('file'), async (req, res) => {try {if (!req.file) {return res.status(400).json({ code: 400, message: '文件为空,请选择要上传的头像!' });}console.log('req.file.filename:',req.file.filename);// 生成图片访问 URLconst avatarUrl = `http://localhost:3000/avatars/${req.file.filename}`;// 验证登录态if (!req.user?.id) {return res.status(401).json({ code: 401, message: '未登录,无法修改头像' });}const userId = req.user.id;// 更新数据库const [results] = await pool.query('UPDATE users SET avatarUrl = ? WHERE id = ?',[avatarUrl, userId]);if (results.affectedRows === 0) {return res.status(404).json({ code: 404, message: '用户不存在,更新失败' });}res.status(200).json({code: 200,message: '头像修改成功',data: { avatar: avatarUrl }});} catch (error) {console.error('头像上传失败:', error);res.status(500).json({ code: 500, message: '服务器错误,上传失败' });}
});

因为有文件处理中间件这些的吧所以确实麻烦了些,还有认证中间件,可以自行删减,注意头像要上传原图片,然后对应的格式就是multipart/form-data了 这块content-type我这个项目里

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/diannao/93335.shtml
繁体地址,请注明出处:http://hk.pswp.cn/diannao/93335.shtml

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

相关文章

[java 常用类API] 新手小白的编程字典

目录 1.API 1.1定义: 2.Object类 2.1 toString() 方法 2.2 equals() 方法 3. Arrays 类 3.1 equals() 方法 3.2 sort() 方法 3.2.1排序 3.2.2 自定义对象排序 3.3 binarySearch() 方法 3.4 copyOf() 方法 3.5 fill() 方法 3.6 toString() 方法 4.基本数据类型包装类 4.…

去除视频字幕 2, 使用 PaddleOCR 选取图片中的字幕区域, 根据像素大小 + 形状轮廓

有人问我在搞什么&#xff1a;就是做这里的第2步。问题描述这里误导&#xff0c;误判&#xff0c;太严重了。如果我把这个区域当做是 mask ,那么真正的目标会被去除掉还有什么建议吗&#xff1f;比如我能否根据这个mask 的大致形状来判断它是不是字幕&#xff0c; 如果不是细长…

Ubuntu 连接Visual SVN

Windows服务器上的svn仓库为&#xff1a; https://ldw_online:8443/svn/OnlineRepository/LVC IP地址为192.168.8.8 4. 从 Ubuntu 测试连通性 在 Ubuntu 上可以用&#xff1a; bash 复制编辑 curl -vk https://192.168.8.8:8443/ 如果返回 HTTP 头或 SSL 握手成功&#xff…

JAVA:Spring Boot 集成 Protobuf 的技术指南

🚀 1、简述 在分布式服务通信中,数据序列化与反序列化的效率对系统性能影响极大。Protocol Buffers(Protobuf) 是由 Google 提出的一种高效的结构化数据序列化协议,具有: 🔥 高性能(远优于 JSON/XML) 📦 跨语言支持 📉 较小的体积 本篇将带你了解如何在 Spring…

SQLServer内存释放工具介绍:一款实用的数据库性能优化助手

SQLServer内存释放工具介绍&#xff1a;一款实用的数据库性能优化助手 去发现同类优质开源项目:https://gitcode.com/ 在数据库管理中&#xff0c;内存释放是优化服务器性能的重要环节。本文将为您详细介绍一款名为SQLServer内存释放工具的开源项目&#xff0c;帮助您轻松管理…

《蓝耘容器全栈技术指南:企业级云原生与异构计算实战大全》

&#x1f31f; 嗨&#xff0c;我是Lethehong&#xff01;&#x1f31f; &#x1f30d; 立志在坚不欲说&#xff0c;成功在久不在速&#x1f30d; &#x1f680; 欢迎关注&#xff1a;&#x1f44d;点赞⬆️留言收藏&#x1f680; &#x1f340;欢迎使用&#xff1a;小智初学计…

计算器3.0:实现用户自定义组件

前言&#xff1a; 马总给我提出计算器3.0新需求&#xff1a;可以在页面上输入一个组件&#xff0c;用户的组件库里面就多一个组件&#xff0c;用户就可以使用 一、解决方法&#xff1a; 1. 新增成员变量和初始化 // 新增的输入框 private InputBox newInputBox; // 新增的组…

PIG AI 全新升级:全新 MCP 能力加持,让企业级 AI 开发效率翻倍!

你是否曾为 AI 应用的开发门槛而头疼?调试代码耗费数小时、集成外部工具需要复杂配置、想要快速构建智能系统却不知从何下手…别担心!PIG AI 最新版本带来的 MCP(Model Context Protocol)能力,正为这些问题提供一站式解决方案。本文将带你深入浅出地了解这一重磅升级,手把…

Springboot+vue超市管理系统的设计与实现

文章目录前言详细视频演示具体实现截图后端框架SpringBoot前端框架Vue持久层框架MyBaits成功系统案例&#xff1a;代码参考数据库源码获取前言 博主介绍:CSDN特邀作者、985高校计算机专业毕业、现任某互联网大厂高级全栈开发工程师、Gitee/掘金/华为云/阿里云/GitHub等平台持续…

一文快速了解Docker和命令详解

本文让你快速了解Docker是什么的东西&#xff0c;在我们程序开发的时候到底有什么作用&#xff0c;为什么需要去学习它。本文章只是做一个简单的概述配套黑马课程让你快速了解、使用Docker。 一、什么是Docker&#xff1f; Docker是一个开源的容器化平台&#xff0c;允许开发者…

【GaussDB】如何从GaussDB发布包中提取出内核二进制文件

【GaussDB】如何从GaussDB发布包中提取出内核二进制文件 背景 GaussDB 从505和506版本起&#xff08;前面的版本不清楚&#xff09;&#xff0c;华为官方不再提供用脚本安装GaussDB的方式&#xff08;应该是基于运维交付标准化的角度考虑&#xff09;&#xff0c;仅支持使用T…

ETH 交易流程深度技术详解

概述在前面对 PolkaVM 和 Revive 的文章中&#xff0c;我们介绍了很多技术细节&#xff0c;开发工具。还对比 EVM&#xff0c;知道了 PolkaVM 的优势。很多同学还是对 Polkadot SDK 为什么可以运行 EVM 兼容的智能合约&#xff0c;以及交易处理的整个流程不太清楚。这篇文章将会…

【算法训练营Day17】二叉树part7

文章目录二叉树的最近公共祖先二叉搜索树的最近公共祖先二叉搜索树中的插入操作删除二叉搜索树中的节点二叉树的最近公共祖先 题目链接&#xff1a;236. 二叉树的最近公共祖先 解题逻辑&#xff1a; 最近公共祖先的定义为&#xff1a;对于有根树 T 的两个节点 p、q&#xff0c…

Vue插件与组件核心区别详解

在 Vue 中&#xff0c;插件&#xff08;Plugin&#xff09; 和 组件&#xff08;Component&#xff09; 是两种不同层次的概念&#xff0c;它们的主要区别如下&#xff1a;1. 组件 (Component) 定义&#xff1a; Vue 应用的基本构建单元&#xff0c;是可复用的 Vue 实例&#x…

基础NLP | 02 深度学习基本原理

文章目录 深度学习基本原理 数学基础 线代 numpy 常用操作 导数, 梯度 梯度下降法 梯度下降代码 GradientDescent.py 反向传播 完整的反向传播过程 权重更新方式 pytorch 网络结构 全连接层 (线性层) 例子 - 手动实现模拟一个线性层 DNNforward.py 激活函数 激活函数-Sigmoid…

MySQL面试题及详细答案 155道(001-020)

《前后端面试题》专栏集合了前后端各个知识模块的面试题&#xff0c;包括html&#xff0c;javascript&#xff0c;css&#xff0c;vue&#xff0c;react&#xff0c;java&#xff0c;Openlayers&#xff0c;leaflet&#xff0c;cesium&#xff0c;mapboxGL&#xff0c;threejs&…

Ansible安装与入门

目录 Ansible ansible任务执行模式 ansible执行流程 ansible命令执行过程&#xff08;背会&#xff09; ansible的安装方式 ansible的程序结构&#xff08;yum安装为例&#xff09; ansible的配置文件查找顺序&#xff08;背会&#xff09; 核心配置文件 ansible的配置…

【Spring】Spring Boot启动过程源码解析

目录 一、启动入口 二、SpringApplication的构造过程 2.1 设置应用类型 2.2 设置初始化器&#xff08;Initializer&#xff09; 2.2.1 获取BootstrapRegistryInitializer对象 2.2.2 获取ApplicationContextInitializer对象 2.3 设置监听器&#xff08;Listener&#xff…

CDN架构全景图

CDN架构全景图 CDN&#xff08;内容分发网络&#xff09;是一种通过在全球范围内部署边缘节点服务器&#xff0c;将内容缓存至离用户最近的位置&#xff0c;从而加速内容分发、降低延迟并减轻源站压力的分布式网络架构。其核心设计目标是优化互联网内容传输效率&#xff0c;提升…

【pytest高阶】源码的走读方法及插件hook

一、pytest源码走读方法 依赖库认知篇 &#x1f4e6;这是理解 pytest 源码的 “前菜”&#xff0c;先认识 3 个超重要的小伙伴&#xff1a;iniconfig &#x1f4c4;&#xff1a;像个 “文件小管家”&#xff0c;专门负责读取 ini 配置文件&#xff08;比如 pytest 的配置&#…