310
社区成员




这个作业属于哪个课程 | 软件工程实践-2023学年-W班 |
---|---|
这个作业要求在哪里 | 软件工程实践总结&个人技术博客 |
这个作业的目标 | 个人技术博客 |
其他参考文献 | - |
在现代Web应用中,图片文件的上传和下载是一个常见的需求。本人在进行福鱼的商品图片功能全栈开发时,遇到了上传图片的问题。在查阅了资料后,已经能够完成商品图片的上传和获取。本文将探讨如何使用Spring Boot作为后端框架,结合Axios作为前端库,实现高效的图片文件互传。
@RestController
和@PostMapping
注解创建一个图片上传接口。MultipartFile
接收上传的图片。.post
方法,配置FormData
对象,将图片作为数据发送。.get
方法,如果是获得图片文件,设置配置响应类型为blob
,以便处理二进制图片流;如果是url,则直接发送url。前端
<template>
<div>
<form @submit.prevent="uploadImage">
<input type="file" @change="onFileChange" />
<button type="submit">上传图片</button>
</form>
</div>
</template>
<script>
import axios from 'axios';
export default {
methods: {
onFileChange(event) {
this.file = event.target.files[0];
},
uploadImage() {
let formData = new FormData();
formData.append('image', this.file);
axios.post('/upload', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
}).then(response => {
console.log('图片上传成功', response);
}).catch(error => {
console.error('图片上传失败', error);
});
}
}
};
</script>
后端
@PostMapping("/upload")
public ResponseEntity<?> uploadImage(@RequestParam("image") MultipartFile file) {
try {
// 保存图片到服务器
Path path = Files.createTempFile("images", file.getOriginalFilename());
Files.write(path, file.getBytes());
return ResponseEntity.ok("图片上传成功");
} catch (IOException e) {
return ResponseEntity.status(HttpStatus.INTERNAL_SERVER_ERROR).body("图片上传失败");
}
}
技术简介
流程说明
可以图片和JSON一起发送,这里使用了福鱼项目的CommodityWithImages数据结构作为示例
<template>
<frontstage_layout>
<router-link to="frontstage_ShopView" class="frontstage_review_results_close">
<button type="button" class="frontstage_review_results_close_button" >退出</button>
</router-link>
<div class="container">
<input type="text" v-model="name" placeholder="商品名称" style="margin-top: 10px;"></input>
<textarea type="text" v-model="description" placeholder="描述一下宝贝的品牌型号、货品来源" rows="5" style="margin-top: 10px;"></textarea>
<div class="image-upload-container">
<el-upload
action=""
:multiple="true"
:auto-upload="true"
:accept="acceptTypes"
:list-type="listType"
:file-list="files"
:before-upload="beforeUpload"
:http-request="handlePost"
:on-preview="handlePreview"
:on-change="changeImage"
:on-remove="handleRemove"
:on-exceed="extendLength"
>
<i class="el-icon-plus"></i>
<div slot="tip">最多可上传5张jpg/png图片,且每张图片大小不超过500kb</div>
</el-upload>
</div>
<select v-model="category" id="select">
<option value="book">书本</option>
<option value="digital">数码</option>
<option value="clothing">服饰</option>
<option value="game">游戏</option>
<option value="life">生活</option>
<option value="appliance">电器</option>
</select>
<editableTable ref="editableTable"></editableTable>
<div class="row">
<input
type="number"
v-model.number="price"
placeholder="价格(元)"
:min="0"
id="price"
style="margin-bottom: 10px;"
>
</div>
<div class="row2">
<button id="price" @click = "addCommodity()" style="background-color:#9e2121;color: white;">添加商品</button>
</div>
</div>
</frontstage_layout>
</template>
<script>
import frontstage_layout from '../../components/frontstage/frontstage_layout.vue';
import editableTable from '../../components/editableTable.vue';
import { ElLoading } from 'element-plus';
export default {
components: {
frontstage_layout,
editableTable,
},
data() {
return {
description: '',
file: null,
category: 'book',
price: null,
name:null,
formattedDate:null,
acceptTypes: 'jpg, jpeg,png, PNG',
listType: 'picture-card',
files: [],
maxUploadLimit : 5,
forceDelete : false,
maxSize : 500,
loading : null,
};
},
methods: {
addCommodity() {
if (!this.name || this.name.trim() === '') {
this.$message.error('商品名称不能为空');
return;
}
if (!this.description || this.description.trim() === '') {
this.$message.error('商品描述不能为空');
return;
}
// 图片检查
if (this.files.length === 0) {
this.$message.error('请上传商品图片');
return;
}
// 价格检查
if (this.price === null || this.price <= 0) {
this.$message.error('价格必须大于0');
return;
}
const userId = JSON.parse(localStorage.getItem('user')).userid;
this.formattedDate = this.getTime();
let formData = new FormData();
// Add commodity data
formData.append('commodity.shopId', userId);
formData.append('commodity.name', this.name);
formData.append('commodity.description', this.description);
formData.append('commodity.price', this.price);
formData.append('commodity.isActive', 1);
formData.append('commodity.formattedDate', this.formattedDate);
// Add images
this.files.forEach((fileItem, index) => {
formData.append('images', fileItem.raw);
});
const keyValues = JSON.parse(JSON.stringify(this.$refs.editableTable.tableData));
keyValues.forEach((item) => {
const keyValueString = `${item.key.replace(/:/g, '@')}:${item.value.replace(/:/g, '@')}`;
formData.append('keyValues', keyValueString);
});
this.loading = ElLoading.service({
lock: true,
customClass: 'global-loading',
})
// Send the request
this.$axios.post('commodity/information', formData, {
headers: {
'Content-Type': 'multipart/form-data'
}
})
.then(response => {
console.log(response);
this.$message.success('添加成功,请等待审核');
this.$router.push('/frontstage_ShopView');
this.loading.close();
})
.catch(error => {
console.error(error);
if (error.response) {
console.error(error.response.data);
console.error(error.response.status);
console.error(error.response.headers);
}
this.loading.close();
});
},
}
}
</script>
<style scoped>
</style>
后端
获取的请求体结构
package com.example.demo.requests;
import java.util.List;
import java.util.Map;
import org.springframework.web.multipart.MultipartFile;
import com.example.demo.models.Commodity;
public class CommodityWithImages {
private Commodity commodity;
public List<MultipartFile> images;
private List<String> keyValues;
//省略getter和setter
}
控制+服务层
@PostMapping("/information")
public ResponseEntity<Object> addCommodity(@ModelAttribute CommodityWithImages commodityWithImages) {
Commodity commodity = commodityWithImages.getCommodity();
if (commodity == null || commodity.getName() == null || commodity.getPrice() == null) {
return new ResponseEntity<>("商品信息不完整", HttpStatus.BAD_REQUEST);
}
Map<String, Object> response = commodityService.addCommodity(commodityWithImages);
return ResponseEntity.ok(response);
}
public Map<String, Object> addCommodity(CommodityWithImages commodityWithImages) {
Map<String, Object> response = new HashMap<>();
Commodity commodity = commodityWithImages.getCommodity();
commodityMapper.insertCommodity(commodity);
Long id = commodity.getId();
List<CommodityKeyValue> keyValueMap = new ArrayList<>();
if (commodityWithImages.getKeyValues() != null) {
for (String keyValue : commodityWithImages.getKeyValues()) {
String[] parts = keyValue.split(":");
String key = parts[0].replace("@", ":");
String value = parts[1].replace("@", ":");
CommodityKeyValue temp = new CommodityKeyValue();
temp.setCommodityId(id);
temp.setKeyName(key);
temp.setValue(value);
keyValueMap.add(temp);
}
addCommodityKey(keyValueMap);
}
if (commodityWithImages.getImages() != null) {
try {
for (MultipartFile image : commodityWithImages.getImages()) {
if (!image.isEmpty()) {
Path directoryPath = Paths.get("images/commodity/" + id);
if (!Files.exists(directoryPath)) {
Files.createDirectories(directoryPath);
}
String fileName = image.getOriginalFilename();
byte[] bytes = image.getBytes();
Path directory = Paths.get("images/commodity/", String.valueOf(id));
Path path = Files.createTempFile(directory, "_", fileName);
Files.write(path, bytes, StandardOpenOption.CREATE, StandardOpenOption.TRUNCATE_EXISTING, StandardOpenOption.WRITE);
}
}
} catch (IOException e) {
response.put("status", "error");
response.put("message", "上传失败: " + e.getMessage());
return response;
}
response.put("message", "图片上传成功");
}
response.put("status", "success");
response.put("message", "商品上传成功");
return response;
}
技术简介
流程说明
后端
@GetMapping("/get-image-url/{id}")
public ResponseEntity<?> getImageUrl(@PathVariable long id) {
String imageUrl = "http://example.com/images/" + id + ".jpg";
return ResponseEntity.ok(imageUrl);
}
<template>
<div>
<img :src="imageUrl" alt="商品图片">
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
imageUrl: ''
};
},
created() {
this.fetchImageUrl();
},
methods: {
fetchImageUrl() {
axios.get('/get-image-url/1').then(response => {
this.imageUrl = response.data;
}).catch(error => {
console.error('获取图片URL失败', error);
});
}
}
};
</script>
流程说明
后端
@GetMapping("/download-image/{id}")
public ResponseEntity<Resource> downloadImage(@PathVariable long id) {
// 假设根据id获取图片文件
File file = new File("path/to/image.jpg");
FileSystemResource fileSystemResource = new FileSystemResource(file);
return ResponseEntity.ok()
.header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=" + file.getName())
.contentType(MediaType.APPLICATION_OCTET_STREAM)
.body(fileSystemResource);
}
前端
<template>
<div>
<a :href="downloadUrl" download>下载图片</a>
</div>
</template>
<script>
import axios from 'axios';
export default {
data() {
return {
downloadUrl: ''
};
},
created() {
this.fetchDownloadUrl();
},
methods: {
fetchDownloadUrl() {
axios.get('/download-image/1', { responseType: 'blob' })
.then(response => {
this.downloadUrl = window.URL.createObjectURL(new Blob([response.data]));
}).catch(error => {
console.error('获取图片下载URL失败', error);
});
}
}
};
</script>
流程说明
后端
public Resource getImageZip(long id) {
List<Resource> imageResources = new ArrayList<>();
List<File> imageFiles = getImageFiles(id); //获得所有的图片File
for (File imageFile : imageFiles) {
imageResources.add(new FileSystemResource(imageFile));
}
File tempZipFile;
try {
tempZipFile = File.createTempFile("images", ".zip");
try (ZipOutputStream zipOutputStream = new ZipOutputStream(new FileOutputStream(tempZipFile))) {
for (Resource imageResource : imageResources) {
try (InputStream imageInputStream = imageResource.getInputStream()) {
ZipEntry zipEntry = new ZipEntry(imageResource.getFilename());
zipOutputStream.putNextEntry(zipEntry);
byte[] buffer = new byte[1024];
int length;
// 读取InputStream并写入ZipOutputStream
while ((length = imageInputStream.read(buffer)) != -1) {
zipOutputStream.write(buffer, 0, length);
}
zipOutputStream.closeEntry();
}
}
}
// 创建一个FileSystemResource来包装ZIP图片
Resource zipResource = new FileSystemResource(tempZipFile);
return zipResource;
} catch (IOException e) {
e.printStackTrace();
}
return null;
}
前端
<template>
<div class="product-details">
<el-carousel :interval="3000" arrow="always" class="product-slider" motion-blur=true height="90%" indicator-position="outside">
<el-carousel-item v-for="(image, index) in productImages" :key="index">
<img :src="image" alt="Product Image" class="product-image" />
</el-carousel-item>
</el-carousel>
<div class="product-info">
<h1 style="border-bottom: 3px solid #9e2121; border-top: 3px solid #9e2121; font-weight: bold; text-align: center; padding: 8% 8% 8% 0%;">{{ commodity.commodityName }}</h1>
<div class="seller-info" style="display: flex; align-items: center; justify-content: center; padding:8% 8% 8% 0% ;">
<p style="color: #9e2121; font-weight: bold; font-size: x-large;text-align: left;margin-right: 15%;">¥{{ commodity.price }}</p>
<img :src="seller.avatar" alt="Seller Avatar" style="width: 10%; height: 10%; border-radius: 50%;">
<div style="margin-left: 10px;">
<p style="font-size: large; font-weight: bold;">{{ commodity.userName }}</p>
<router-link
:to="linkDestination"
style="text-decoration: none; color: #9e2121;"
>
访问商家
</router-link>
</div>
</div>
<p style="border-bottom: 3px solid #9e2121;border-top: 3px solid #9e2121; padding: 5% 8% 5% 0%; font-size: x-large; text-align: center;"> {{ commodity.commodityDescription }}</p>
</div>
</div>
<div class="custom-table-container">
</div>
</template>
<script>
import JSZip from 'jszip';
import { ElLoading, ElCarousel, ElCarouselItem } from 'element-plus'
export default {
components: {
ElCarousel,
ElCarouselItem,
},
emits: ['update-info'],
data() {
return {
commodity:{},
productImages: [
],
currentIndex: 0 ,
seller: {
id:1,
avatar: 'https://ts1.cn.mm.bing.net/th/id/R-C.67c70ed0eae200d69455a91b43a9f407?rik=%2bjNY%2bmbfVS8RWQ&riu=http%3a%2f%2fwww.sucaijishi.com%2fuploadfile%2f2018%2f0508%2f20180508023717621.png%3fimageMogr2%2fformat%2fjpg%2fblur%2f1x0%2fquality%2f60&ehk=8jAEJxDPBhcrPlaoFp5%2bI%2fI5rdr3YShGr2RMKGwIuhM%3d&risl=&pid=ImgRaw&r=0',
name: '商家姓名',
link: '商家链接',
creditScore: '4.5',
friendliness: '4.7',
loading : null,
},
keyValues : {},
};
},
methods: {
getCommodityMessage() {
//获得商品信息
},
fetchCommodityPicture(id) {
//根据商品id获得对应的图片列表
this.controlLoading(this.productImages);
if (this.productImages.length > 0) return;
this.$axios.get(`/commodity/picture/${id}`, { responseType: 'arraybuffer' })
.then(response => {
const zip = new JSZip();
return zip.loadAsync(new Uint8Array(response.data));
})
.then(zip => {
const promises = [];
zip.forEach((relativePath, file) => {
if (file.name.match(/\.(jpg|jpeg|png|gif)$/)) {
promises.push(file.async('blob').then(content => {
this.productImages.push(
URL.createObjectURL(content)
);
}));
}
this.controlLoading(this.productImages);
});
return Promise.all(promises);
})
.catch(error => {
console.error('Error fetching and unzipping the picture:', error);
});
},
},
mounted() {
this.getCommodityMessage();
this.fetchCommodityPicture(this.$route.query.id);
},
};
</script>
<style scoped>
</style>
技术简介
流程说明