七牛云图片上传 前后端全过程

发布于:2025-06-15 ⋅ 阅读:(21) ⋅ 点赞:(0)

相关网址:七牛开发者中心

相关网站: 七牛开发者中心

上传流程概述

  1. 后端生成上传凭证:服务器端使用七牛云 SDK 生成上传凭证(uptoken)
  2. 前端获取凭证:前端通过 API 向后端请求上传凭证
  3. 前端上传图片:前端使用获取的凭证将图片上传到七牛云
  4. 处理上传结果:七牛云返回上传结果,前端或后端处理结果

后端 Java 代码实现

首先需要添加七牛云 SDK 依赖:

<dependency>
    <groupId>com.qiniu</groupId>
    <artifactId>qiniu-java-sdk</artifactId>
    <version>[7.7.0, 7.7.99]</version>
</dependency>

还需要在 application.properties 中配置七牛云密钥:

# 七牛云配置
qiniu.accessKey=你的AccessKey
qiniu.secretKey=你的SecretKey
qiniu.bucket=你的存储空间名称
qiniu.domain=你的存储空间域名

QiniuController.java:

package com.example.controller;

import com.qiniu.util.Auth;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

import java.util.HashMap;
import java.util.Map;

@RestController
@RequestMapping("/api/qiniu")
public class QiniuController {

    @Value("${qiniu.accessKey}")
    private String accessKey;
    
    @Value("${qiniu.secretKey}")
    private String secretKey;
    
    @Value("${qiniu.bucket}")
    private String bucket;
    
    @Value("${qiniu.domain}")
    private String domain;

    /**
     * 获取七牛云上传凭证
     */
    @GetMapping("/token")
    public Map<String, Object> getUploadToken() {
        Map<String, Object> result = new HashMap<>();
        
        try {
            // 创建Auth对象,用于生成凭证
            Auth auth = Auth.create(accessKey, secretKey);
            
            // 生成上传凭证,有效期3600秒
            String upToken = auth.uploadToken(bucket, null, 3600, null);
            
            result.put("code", 200);
            result.put("message", "获取凭证成功");
            result.put("data", new HashMap<String, Object>() {{
                put("token", upToken);
                put("domain", domain);
            }});
        } catch (Exception e) {
            result.put("code", 500);
            result.put("message", "获取凭证失败: " + e.getMessage());
        }
        
        return result;
    }
}

前端 Vue3 代码实现

api/quniu.js:

import request from '@/utils/request.js'

export const uploadFile = (url, filePath) => {
  return new Promise((resolve, reject) => {
    uni.uploadFile({
      url,
      filePath,
      name: 'file', // 后端接收文件的字段名
      success: (res) => {
        if (res.statusCode === 200) {
          try {
            const data = JSON.parse(res.data);
            resolve(data);
          } catch (err) {
            reject(new Error('解析响应数据失败'));
          }
        } else {
          reject(new Error(`上传失败,状态码: ${res.statusCode}`));
        }
      },
      fail: (err) => {
        reject(err);
      }
    });
  });
};

UploadImage.vue:

<template>
  <view class="image-uploader">
    <!-- 上传按钮 -->
    <view class="upload-btn" @click="chooseImage">
      <text class="btn-text">选择图片</text>
    </view>
    
    <!-- 已选择图片预览 -->
    <view class="preview-container" v-if="previewImage">
      <image 
        :src="previewImage" 
        mode="aspectFill" 
        class="preview-image"
        @click="previewImageDetail"
      ></image>
      <view class="delete-btn" @click="deleteImage">
        <text class="delete-text">×</text>
      </view>
    </view>
    
    <!-- 上传状态提示 -->
    <view class="status-tip" v-if="uploadStatus !== 'idle'">
      <text v-if="uploadStatus === 'uploading'">上传中...</text>
      <text v-if="uploadStatus === 'success'" class="success-text">上传成功</text>
      <text v-if="uploadStatus === 'error'" class="error-text">上传失败: {{ errorMessage }}</text>
    </view>
  </view>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue';
import { uploadFile } from '@/api/qiniu'; // 导入您的后端上传API

// 组件props
const props = defineProps({
  maxSize: {
    type: Number,
    default: 5 // 默认最大5MB
  },
  accept: {
    type: String,
    default: 'image/*' // 接受的文件类型
  },
  multiple: {
    type: Boolean,
    default: false // 是否允许多选
  },
  uploadUrl: {
    type: String,
	default: 'https://www.wenbaby.tech/api/qiniu/upload',
	// default: 'http://localhost:8888/api/qiniu/upload',
    required: true // 后端上传接口URL
  }
});

// 组件emits
const emits = defineEmits(['success', 'error', 'change']);

// 状态管理
const previewImage = ref(''); // 预览图片路径
const uploadStatus = ref('idle'); // 上传状态: idle, uploading, success, error
const errorMessage = ref(''); // 错误信息

// 选择图片
const chooseImage = async () => {
  try {
    const res = await uni.chooseImage({
      count: props.multiple ? 9 : 1,
      sizeType: ['original', 'compressed'],
      sourceType: ['album', 'camera'],
    });
    
    if (res.tempFilePaths && res.tempFilePaths.length > 0) {
      // 获取第一张图片
      const imagePath = res.tempFilePaths[0];
      
      // 检查图片大小
      const { size } = await uni.getFileInfo({
        filePath: imagePath
      });
      
      const maxSizeInBytes = props.maxSize * 1024 * 1024;
      if (size > maxSizeInBytes) {
        uni.showToast({
          title: `图片大小不能超过${props.maxSize}MB`,
          icon: 'none'
        });
        return;
      }
      
      // 设置预览图
      previewImage.value = imagePath;
      
      // 自动触发上传
      await uploadImage(imagePath);
    }
  } catch (err) {
    console.error('选择图片失败:', err);
    uni.showToast({
      title: '选择图片失败',
      icon: 'none'
    });
  }
};

// 上传图片到后端
const uploadImage = async (filePath) => {
  try {
    // 更新上传状态
    uploadStatus.value = 'uploading';
    errorMessage.value = '';
    
    // 调用后端API上传图片
    const result = await uploadFile(props.uploadUrl, filePath);
    
    // 处理上传结果
    if (result.success) {
      uploadStatus.value = 'success';
      emits('success', result);
      emits('change', result);
      
      uni.showToast({
        title: '上传成功',
        icon: 'success'
      });
    } else {
      uploadStatus.value = 'error';
      errorMessage.value = result.message || '上传失败';
      emits('error', result);
      
      uni.showToast({
        title: '上传失败',
        icon: 'none'
      });
    }
  } catch (err) {
    console.error('上传图片失败:', err);
    uploadStatus.value = 'error';
    errorMessage.value = err.message || '上传失败';
    emits('error', { message: err.message });
    
    uni.showToast({
      title: '上传失败',
      icon: 'none'
    });
  }
};

// 删除已选择的图片
const deleteImage = () => {
  previewImage.value = '';
  uploadStatus.value = 'idle';
  errorMessage.value = '';
  emits('change', null);
};

// 预览图片详情
const previewImageDetail = () => {
  if (previewImage.value) {
    uni.previewImage({
      urls: [previewImage.value],
      current: previewImage.value
    });
  }
};

// 重置组件状态
const reset = () => {
  previewImage.value = '';
  uploadStatus.value = 'idle';
  errorMessage.value = '';
};

// 暴露给父组件的方法
defineExpose({
  reset
});
</script>

<style scoped>
.image-uploader {
  padding: 15px;
}

.upload-btn {
  width: 100px;
  height: 100px;
  background-color: #f5f5f5;
  border: 1px dashed #ddd;
  border-radius: 8px;
  display: flex;
  justify-content: center;
  align-items: center;
  margin-bottom: 15px;
}

.btn-text {
  color: #666;
  font-size: 14px;
}

.preview-container {
  position: relative;
  width: 100px;
  height: 100px;
  margin-bottom: 15px;
}

.preview-image {
  width: 100%;
  height: 100%;
  border-radius: 8px;
}

.delete-btn {
  position: absolute;
  top: -5px;
  right: -5px;
  width: 20px;
  height: 20px;
  background-color: rgba(0, 0, 0, 0.6);
  border-radius: 50%;
  display: flex;
  justify-content: center;
  align-items: center;
}

.delete-text {
  color: white;
  font-size: 16px;
  line-height: 1;
}

.status-tip {
  font-size: 14px;
  margin-top: 10px;
}

.success-text {
  color: #00B42A;
}

.error-text {
  color: #F53F3F;
}
</style>