项目中经常要用到文件存储
方案1: 文件传给后端,后端存到本地,当静态资源
方案2: 文件传给后端,后端上传到文件服务器(如minio)
方案3: 前端从后端获取到文件服务器的账户和密码,然后前端直接上传到文件文件服务器,
本篇案例为 前后不分离代码
方案1:
前端代码(和方案2 公用一套前端代码,方案1后端代码不包含删除)
index.html 放在resource/static/web目录下
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0">-->
<title>上传文件</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<el-upload
class="file-upload"
ref="upload"
action="http://localhost:8633/api/upload"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="handleSuccess"
multiple
:limit="fileLimit"
:on-exceed="handleExceed"
:file-list="fileList">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<div label="需要删除的链接" prop="author">
<el-input v-model="deleteUrl" ></el-input>
<el-button type="primary" @click="onDelete">删除</el-button>
</div>
</div>
</body>
<script>
new Vue({
el: "#app",
data() {
return {
fileLimit: 3,
fileList: [],
fileUrl: '',
deleteUrl:'http://localhost:9000/mall/1628847099991_20210813173139_th.png'
}
},
methods: {
handleRemove(file, fileList) {
},
onDelete() {
axios.get('http://localhost:8633/api/delete?fileName='+ this.deleteUrl)
.then(response => (this.info = response))
.catch(function (error) {
// 请求失败处理
console.log(error);
})
},
handlePreview(file) {
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 ${
this.fileLimit} 个文件,本次选择了 ${
files.length} 个文件,共选择了 ${
files.length + fileList.length} 个文件`)
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${
file.name}?`)
},
handleSuccess(response) {
this.url = response
this.$emit('onUpload')
this.$message.warning('上传成功')
},
clear() {
console.log('就看看运行没有')
}
}
})
</script>
<style>
</style>
</html>
后端代码:
1 设置路由
@Configuration
public class SpringMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/api/file/**").addResourceLocations("classpath:/static/img/");
registry.addResourceHandler("/**").addResourceLocations("classpath:/static/web/");
}
}
//设置跳转
@Controller
public class IndexController {
@RequestMapping("/")
public String index() {
return "redirect:/index.html";
}
}
@CrossOrigin
@PostMapping("api/upload")
public String fileUpload(MultipartFile file) throws Exception {
String filePath = System.getProperty("user.dir") + "\\springboot_17_minio\\target\\classes\\static\\img";
String fileName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date());
File imageFolder = new File(filePath);
File f = new File(imageFolder, fileName + file.getOriginalFilename()
.substring(file.getOriginalFilename().length() - 4));
if (!f.getParentFile().exists())
f.getParentFile().mkdirs();
try {
file.transferTo(f);
String imgURL = "http://localhost:8080/api/file/" + f.getName();
return imgURL;
} catch (IOException e) {
e.printStackTrace();
return "";
}
}
方案二: 由后端传到minio服务器
docker安装minio服务 dockercompose文件如下
version: "3.7"
services:
minio:
image: minio/minio
container_name: minio
ports:
- 9000:9000
#指定容器中的目录 /data
command: server /data
environment:
MINIO_ACCESS_KEY: admin # 用户名
MINIO_SECRET_KEY: admin123456 # 密码
# MINIO_REGION_NAME: cn-home
# MINIO_REGION_COMMENT: homelab
# MINIO_BROWSER: on
# MINIO_DOMAIN: oss.halobug.cn
volumes:
- ./data:/data
- ./config:/root/.minio
logging:
options:
max-size: "50M" # 最大文件上传限制
max-file: "10"
driver: json-file
前端代码(同方案一)
后端代码:
参考: https://blog.csdn.net/weixin_32801895/article/details/112249442
引入pom
<dependency>
<groupId>io.minio</groupId>
<artifactId>minio</artifactId>
<version>3.0.10</version>
</dependency>
后端代码
@Slf4j
@Service
public class MinioServiceImpl implements MinioService {
private String url = "http://localhost:9000";
// 存储桶名称
private String bucketName = "mall";
// 访问的key
private String accessKey = "admin";
// 访问的秘钥
private String secretKey = "admin123456";
@Override
public String uploadFile(MultipartFile file) {
try {
//创建一个MinIO的Java客户端
MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
boolean isExist = minioClient.bucketExists(bucketName);
if (isExist) {
log.info("存储桶已经存在!");
} else {
//创建存储桶并设置只读权限
minioClient.makeBucket(bucketName);
minioClient.setBucketPolicy(bucketName, "*.*", PolicyType.READ_ONLY);
}
String filename = file.getOriginalFilename();
// 设置存储对象名称
String objectName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date())+ "_" + filename;
// 使用putObject上传一个文件到存储桶中
minioClient.putObject(bucketName, objectName, file.getInputStream(), file.getContentType());
log.info("文件上传成功!");
String fileUrl = url +"/"+bucketName +"/"+ objectName;
return fileUrl;
} catch (Exception e) {
e.printStackTrace();
log.error("文件上传出错!");
}
return null;
}
@Override
public Boolean deleteFile(String objectName) {
// 文件名称是 文件名称 前面的路径不包含
//只有 1628847099991_20210813173139_th.png
try {
MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
String fileName = objectName.replace(url + "/" + bucketName + "/", "");
minioClient.removeObject(bucketName, fileName);
return true;
} catch (Exception e) {
e.printStackTrace();
}
return false;
}
}
项目结构:
**方案三: 从后端获取签名,使用带有签名的链接上传文件
前端代码
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<!-- <meta name="viewport" content="width=device-width, initial-scale=1.0">-->
<title>上传文件</title>
<script src="https://unpkg.com/vue/dist/vue.js"></script>
<!-- 引入样式 -->
<link rel="stylesheet" href="https://unpkg.com/element-ui/lib/theme-chalk/index.css">
<!-- 引入组件库 -->
<script src="https://unpkg.com/element-ui/lib/index.js"></script>
<!-- 引入axios -->
<script src="https://unpkg.com/axios/dist/axios.min.js"></script>
</head>
<body>
<div id="app">
<el-upload
class="file-upload"
ref="upload"
action="123"
:http-request="fileUpload"
:on-preview="handlePreview"
:on-remove="handleRemove"
:before-remove="beforeRemove"
:on-success="handleSuccess"
multiple
:limit="fileLimit"
:on-exceed="handleExceed"
:file-list="fileList">
<el-button size="small" type="primary">点击上传</el-button>
<div slot="tip" class="el-upload__tip">只能上传jpg/png文件,且不超过500kb</div>
</el-upload>
<div label="需要删除的链接" prop="author">
<el-input v-model="deleteUrl" ></el-input>
<el-button type="primary" @click="onDelete">删除</el-button>
</div>
</div>
</body>
<script>
new Vue({
el: "#app",
data() {
return {
fileLimit: 3,
fileList: [],
fileUrl: '',
deleteUrl:'http://localhost:9000/mall/1628847099991_20210813173139_th.png'
}
},
methods: {
fileUpload(res) {
let file = res.file
console.log(file)
console.log('这里被触发了')
let config = {
headers: {
'Content-Type': 'multipart/form-data'
}
}
axios.get('http://localhost:8633/api/getUploadUrl?fileName='+file.name)
.then(response => (
console.log('返回路径: '+response.data),
uploadUrl = response.data,
console.log('上传路径: '+uploadUrl),
axios.put(uploadUrl, file, config).then(res => (
console.log('这是上传后的结果'),
console.log(res),
console.log('文件路径为: '+uploadUrl.substr(0,uploadUrl.lastIndexOf('?')))
))
))
},
handleRemove(file, fileList) {
},
handlePreview(file) {
},
handleExceed(files, fileList) {
this.$message.warning(`当前限制选择 ${
this.fileLimit} 个文件,本次选择了 ${
files.length} 个文件,共选择了 ${
files.length + fileList.length} 个文件`)
},
beforeRemove(file, fileList) {
return this.$confirm(`确定移除 ${
file.name}?`)
},
handleSuccess(response) {
this.url = response
this.$emit('onUpload')
this.$message.warning('上传成功')
}
}
})
</script>
<style>
</style>
</html>
后端代码:
参考: https://blog.csdn.net/xixiyuguang/article/details/119571051
public String getUploadUrl(String fileName) {
System.out.println(fileName);
try {
MinioClient minioClient = new MinioClient(url, accessKey, secretKey);
Map<String, String> reqParams = new HashMap<String, String>();
reqParams.put("response-content-type", "application/json");
String objectName = System.currentTimeMillis() + "_" + new SimpleDateFormat("yyyyMMddHHmmss").format(new Date()) + "_" + fileName;
System.out.println("文件名称: "+objectName);
String product = minioClient.getPresignedObjectUrl( Method.PUT , bucketName,objectName,60 * 60 * 24,reqParams);
System.out.println(product); // 前端直传需要的url地址
return product;
} catch (Exception e) {
}
return null;
}
另外还有一种方式是 前端从后端获取账号和密码,然后前端自己上传,理论上这个是可以的,但是获取账号和密码的过程中,会泄露minio的账号和密码
参考 http://docs.minio.org.cn/docs/master/javascript-client-api-reference
https://www.jianshu.com/p/fd1dcb5ca6a8