相关网址:七牛开发者中心
上传流程概述
- 后端生成上传凭证:服务器端使用七牛云 SDK 生成上传凭证(uptoken)
- 前端获取凭证:前端通过 API 向后端请求上传凭证
- 前端上传图片:前端使用获取的凭证将图片上传到七牛云
- 处理上传结果:七牛云返回上传结果,前端或后端处理结果
后端 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>