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

发布于:2025-07-29 ⋅ 阅读:(17) ⋅ 点赞:(0)

模版部分:

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

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-upload
                class="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);
    // 生成图片访问 URL
    const 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我这个项目里


网站公告

今日签到

点亮在社区的每一天
去签到