@@ -23,6 +23,12 @@ | |||||
</properties> | </properties> | ||||
<dependencies> | <dependencies> | ||||
<dependency> | |||||
<groupId>org.projectlombok</groupId> | |||||
<artifactId>lombok</artifactId> | |||||
<optional>true</optional> | |||||
</dependency> | |||||
<dependency> | <dependency> | ||||
<groupId>org.springframework.boot</groupId> | <groupId>org.springframework.boot</groupId> | ||||
<artifactId>spring-boot-starter-web</artifactId> | <artifactId>spring-boot-starter-web</artifactId> | ||||
@@ -0,0 +1,106 @@ | |||||
package com.xkcoding.upload.controller; | |||||
import cn.hutool.core.date.DateUtil; | |||||
import cn.hutool.core.io.FileUtil; | |||||
import cn.hutool.core.lang.Dict; | |||||
import cn.hutool.core.util.StrUtil; | |||||
import cn.hutool.json.JSONObject; | |||||
import cn.hutool.json.JSONUtil; | |||||
import com.qiniu.http.Response; | |||||
import com.xkcoding.upload.service.IQiNiuService; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.http.MediaType; | |||||
import org.springframework.web.bind.annotation.PostMapping; | |||||
import org.springframework.web.bind.annotation.RequestMapping; | |||||
import org.springframework.web.bind.annotation.RequestParam; | |||||
import org.springframework.web.bind.annotation.RestController; | |||||
import org.springframework.web.multipart.MultipartFile; | |||||
import java.io.File; | |||||
import java.io.IOException; | |||||
/** | |||||
* <p> | |||||
* 文件上传 Controller | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.upload.controller | |||||
* @description: 文件上传 Controller | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2018/11/6 16:33 | |||||
* @copyright: Copyright (c) 2018 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@RestController | |||||
@Slf4j | |||||
@RequestMapping("/upload") | |||||
public class UploadController { | |||||
@Value("${spring.servlet.multipart.location}") | |||||
private String fileTempPath; | |||||
@Value("${qiniu.prefix}") | |||||
private String prefix; | |||||
private final IQiNiuService qiNiuService; | |||||
@Autowired | |||||
public UploadController(IQiNiuService qiNiuService) { | |||||
this.qiNiuService = qiNiuService; | |||||
} | |||||
@PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | |||||
public Dict local(@RequestParam("file") MultipartFile file) { | |||||
if (file.isEmpty()) { | |||||
return Dict.create().set("code", 400).set("message", "文件内容为空"); | |||||
} | |||||
String fileName = file.getOriginalFilename(); | |||||
String rawFileName = StrUtil.subBefore(fileName, ".", true); | |||||
String fileType = StrUtil.subAfter(fileName, ".", true); | |||||
String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; | |||||
try { | |||||
file.transferTo(new File(localFilePath)); | |||||
} catch (IOException e) { | |||||
log.error("【文件上传至本地】失败,绝对路径:{}", localFilePath); | |||||
return Dict.create().set("code", 500).set("message", "文件上传失败"); | |||||
} | |||||
log.info("【文件上传至本地】绝对路径:{}", localFilePath); | |||||
return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath)); | |||||
} | |||||
@PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE) | |||||
public Dict yun(@RequestParam("file") MultipartFile file) { | |||||
if (file.isEmpty()) { | |||||
return Dict.create().set("code", 400).set("message", "文件内容为空"); | |||||
} | |||||
String fileName = file.getOriginalFilename(); | |||||
String rawFileName = StrUtil.subBefore(fileName, ".", true); | |||||
String fileType = StrUtil.subAfter(fileName, ".", true); | |||||
String localFilePath = StrUtil.appendIfMissing(fileTempPath, "/") + rawFileName + "-" + DateUtil.current(false) + "." + fileType; | |||||
try { | |||||
file.transferTo(new File(localFilePath)); | |||||
Response response = qiNiuService.uploadFile(new File(localFilePath)); | |||||
if (response.isOK()) { | |||||
JSONObject jsonObject = JSONUtil.parseObj(response.bodyString()); | |||||
String yunFileName = jsonObject.getStr("key"); | |||||
String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName; | |||||
FileUtil.del(new File(localFilePath)); | |||||
log.info("【文件上传至七牛云】绝对路径:{}", yunFilePath); | |||||
return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", yunFileName).set("filePath", yunFilePath)); | |||||
} else { | |||||
log.error("【文件上传至七牛云】失败,{}", JSONUtil.toJsonStr(response)); | |||||
FileUtil.del(new File(localFilePath)); | |||||
return Dict.create().set("code", 500).set("message", "文件上传失败"); | |||||
} | |||||
} catch (IOException e) { | |||||
log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath); | |||||
return Dict.create().set("code", 500).set("message", "文件上传失败"); | |||||
} | |||||
} | |||||
} |
@@ -0,0 +1,30 @@ | |||||
package com.xkcoding.upload.service; | |||||
import com.qiniu.common.QiniuException; | |||||
import com.qiniu.http.Response; | |||||
import java.io.File; | |||||
/** | |||||
* <p> | |||||
* 七牛云上传Service | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.upload.service | |||||
* @description: 七牛云上传Service | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2018/11/6 17:21 | |||||
* @copyright: Copyright (c) 2018 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
public interface IQiNiuService { | |||||
/** | |||||
* 七牛云上传文件 | |||||
* | |||||
* @param file 文件 | |||||
* @return 七牛上传Response | |||||
* @throws QiniuException 七牛异常 | |||||
*/ | |||||
Response uploadFile(File file) throws QiniuException; | |||||
} |
@@ -0,0 +1,80 @@ | |||||
package com.xkcoding.upload.service.impl; | |||||
import com.qiniu.common.QiniuException; | |||||
import com.qiniu.http.Response; | |||||
import com.qiniu.storage.UploadManager; | |||||
import com.qiniu.util.Auth; | |||||
import com.qiniu.util.StringMap; | |||||
import com.xkcoding.upload.service.IQiNiuService; | |||||
import lombok.extern.slf4j.Slf4j; | |||||
import org.springframework.beans.factory.InitializingBean; | |||||
import org.springframework.beans.factory.annotation.Autowired; | |||||
import org.springframework.beans.factory.annotation.Value; | |||||
import org.springframework.stereotype.Service; | |||||
import java.io.File; | |||||
/** | |||||
* <p> | |||||
* 七牛云上传Service | |||||
* </p> | |||||
* | |||||
* @package: com.xkcoding.upload.service.impl | |||||
* @description: 七牛云上传Service | |||||
* @author: yangkai.shen | |||||
* @date: Created in 2018/11/6 17:22 | |||||
* @copyright: Copyright (c) 2018 | |||||
* @version: V1.0 | |||||
* @modified: yangkai.shen | |||||
*/ | |||||
@Service | |||||
@Slf4j | |||||
public class QiNiuServiceImpl implements IQiNiuService, InitializingBean { | |||||
private final UploadManager uploadManager; | |||||
private final Auth auth; | |||||
@Value("${qiniu.bucket}") | |||||
private String bucket; | |||||
private StringMap putPolicy; | |||||
@Autowired | |||||
public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) { | |||||
this.uploadManager = uploadManager; | |||||
this.auth = auth; | |||||
} | |||||
/** | |||||
* 七牛云上传文件 | |||||
* | |||||
* @param file 文件 | |||||
* @return 七牛上传Response | |||||
* @throws QiniuException 七牛异常 | |||||
*/ | |||||
@Override | |||||
public Response uploadFile(File file) throws QiniuException { | |||||
Response response = this.uploadManager.put(file, file.getName(), getUploadToken()); | |||||
int retry = 0; | |||||
while (response.needRetry() && retry < 3) { | |||||
response = this.uploadManager.put(file, file.getName(), getUploadToken()); | |||||
retry++; | |||||
} | |||||
return response; | |||||
} | |||||
@Override | |||||
public void afterPropertiesSet() { | |||||
this.putPolicy = new StringMap(); | |||||
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}"); | |||||
} | |||||
/** | |||||
* 获取上传凭证 | |||||
* | |||||
* @return 上传凭证 | |||||
*/ | |||||
private String getUploadToken() { | |||||
return this.auth.uploadToken(bucket, null, 3600, putPolicy); | |||||
} | |||||
} |
@@ -3,8 +3,14 @@ server: | |||||
servlet: | servlet: | ||||
context-path: /demo | context-path: /demo | ||||
qiniu: | qiniu: | ||||
accessKey: ## 此处填写你自己的七牛云 access key | |||||
secretKey: ## 此处填写你自己的七牛云 secret key | |||||
## 此处填写你自己的七牛云 access key | |||||
accessKey: | |||||
## 此处填写你自己的七牛云 secret key | |||||
secretKey: | |||||
## 此处填写你自己的七牛云 bucket | |||||
bucket: | |||||
## 此处填写你自己的七牛云 域名 | |||||
prefix: | |||||
spring: | spring: | ||||
servlet: | servlet: | ||||
multipart: | multipart: | ||||
@@ -7,64 +7,175 @@ | |||||
<meta http-equiv="X-UA-Compatible" content="ie=edge"> | <meta http-equiv="X-UA-Compatible" content="ie=edge"> | ||||
<title>spring-boot-demo-upload</title> | <title>spring-boot-demo-upload</title> | ||||
<!-- import Vue.js --> | <!-- import Vue.js --> | ||||
<script src="//vuejs.org/js/vue.min.js"></script> | |||||
<script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script> | |||||
<!-- import stylesheet --> | <!-- import stylesheet --> | ||||
<link rel="stylesheet" href="//unpkg.com/iview/dist/styles/iview.css"> | |||||
<link href="https://cdn.bootcss.com/iview/3.1.4/styles/iview.css" rel="stylesheet"> | |||||
<!-- import iView --> | <!-- import iView --> | ||||
<script src="//unpkg.com/iview/dist/iview.min.js"></script> | |||||
<script src="https://cdn.bootcss.com/iview/3.1.4/iview.min.js"></script> | |||||
</head> | </head> | ||||
<body> | <body> | ||||
<div id="app"> | <div id="app"> | ||||
<Upload | |||||
:before-upload="handleLocalUpload" | |||||
action="//jsonplaceholder.typicode.com/posts/" | |||||
ref="localUpload" | |||||
:on-success="handleLocalSuccess" | |||||
:on-error="handleLocalError" | |||||
> | |||||
<i-button icon="ios-cloud-upload-outline">选择文件</i-button> | |||||
</Upload> | |||||
<i-button | |||||
type="primary" | |||||
@click="localUpload" | |||||
:loading="localUpload.loadingStatus" | |||||
:disabled="!localUpload.file"> | |||||
{{ localUpload.loadingStatus ? '上传中' : '上传' }} | |||||
</i-button> | |||||
<Row :gutter="16" style="background:#eee;padding:10%"> | |||||
<i-col span="12"> | |||||
<Card style="height: 300px"> | |||||
<p slot="title"> | |||||
<Icon type="ios-cloud-upload"></Icon> | |||||
本地上传 | |||||
</p> | |||||
<div style="text-align: center;"> | |||||
<Upload | |||||
:before-upload="handleLocalUpload" | |||||
action="/demo/upload/local" | |||||
ref="localUploadRef" | |||||
:on-success="handleLocalSuccess" | |||||
:on-error="handleLocalError" | |||||
> | |||||
<i-button icon="ios-cloud-upload-outline">选择文件</i-button> | |||||
</Upload> | |||||
<i-button | |||||
type="primary" | |||||
@click="localUpload" | |||||
:loading="local.loadingStatus" | |||||
:disabled="!local.file"> | |||||
{{ local.loadingStatus ? '本地文件上传中' : '本地上传' }} | |||||
</i-button> | |||||
</div> | |||||
<div> | |||||
<div v-if="local.log.status != 0">状态:{{local.log.message}}</div> | |||||
<div v-if="local.log.status === 200">文件名:{{local.log.fileName}}</div> | |||||
<div v-if="local.log.status === 200">文件路径:{{local.log.filePath}}</div> | |||||
</div> | |||||
</Card> | |||||
</i-col> | |||||
<i-col span="12"> | |||||
<Card style="height: 300px;"> | |||||
<p slot="title"> | |||||
<Icon type="md-cloud-upload"></Icon> | |||||
七牛云上传 | |||||
</p> | |||||
<div style="text-align: center;"> | |||||
<Upload | |||||
:before-upload="handleYunUpload" | |||||
action="/demo/upload/yun" | |||||
ref="yunUploadRef" | |||||
:on-success="handleYunSuccess" | |||||
:on-error="handleYunError" | |||||
> | |||||
<i-button icon="ios-cloud-upload-outline">选择文件</i-button> | |||||
</Upload> | |||||
<i-button | |||||
type="primary" | |||||
@click="yunUpload" | |||||
:loading="yun.loadingStatus" | |||||
:disabled="!yun.file"> | |||||
{{ yun.loadingStatus ? '七牛云文件上传中' : '七牛云上传' }} | |||||
</i-button> | |||||
</div> | |||||
<div> | |||||
<div v-if="yun.log.status != 0">状态:{{yun.log.message}}</div> | |||||
<div v-if="yun.log.status === 200">文件名:{{yun.log.fileName}}</div> | |||||
<div v-if="yun.log.status === 200">文件路径:{{yun.log.filePath}}</div> | |||||
</div> | |||||
</Card> | |||||
</i-col> | |||||
</Row> | |||||
</div> | </div> | ||||
<script> | <script> | ||||
new Vue({ | new Vue({ | ||||
el: '#app', | el: '#app', | ||||
data: { | data: { | ||||
localUpload: { | |||||
local: { | |||||
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到 | // 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到 | ||||
file: null, | file: null, | ||||
// 标记上传状态 | // 标记上传状态 | ||||
loadingStatus: false | |||||
loadingStatus: false, | |||||
log: { | |||||
status: 0, | |||||
message: "", | |||||
fileName: "", | |||||
filePath: "" | |||||
} | |||||
}, | }, | ||||
yun: { | |||||
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到 | |||||
file: null, | |||||
// 标记上传状态 | |||||
loadingStatus: false, | |||||
log: { | |||||
status: 0, | |||||
message: "", | |||||
fileName: "", | |||||
filePath: "" | |||||
} | |||||
} | |||||
}, | }, | ||||
methods: { | methods: { | ||||
// beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false | // beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false | ||||
handleLocalUpload(file) { | handleLocalUpload(file) { | ||||
this.localUpload.file = file; | |||||
this.local.file = file; | |||||
return false; | return false; | ||||
}, | }, | ||||
// 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。 | // 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。 | ||||
// iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。 | // iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。 | ||||
localUpload() { | localUpload() { | ||||
this.localUpload.loadingStatus = true; // 标记上传状态 | |||||
this.$refs.localUpload.post(this.localUpload.file); | |||||
this.local.loadingStatus = true; // 标记上传状态 | |||||
this.$refs.localUploadRef.post(this.local.file); | |||||
}, | }, | ||||
// 上传成功后,清空 data 里的 file,并修改上传状态 | // 上传成功后,清空 data 里的 file,并修改上传状态 | ||||
handleLocalSuccess() { | |||||
this.localUpload.file = null; | |||||
this.localUpload.loadingStatus = false; | |||||
this.$Message.success('上传成功'); | |||||
handleLocalSuccess(response) { | |||||
this.local.file = null; | |||||
this.local.loadingStatus = false; | |||||
if (response.code === 200) { | |||||
this.$Message.success(response.message); | |||||
this.local.log.status = response.code; | |||||
this.local.log.message = response.message; | |||||
this.local.log.fileName = response.data.fileName; | |||||
this.local.log.filePath = response.data.filePath; | |||||
this.$refs.localUploadRef.clearFiles(); | |||||
} else { | |||||
this.$Message.error(response.message); | |||||
this.local.log.status = response.code; | |||||
this.local.log.message = response.message; | |||||
} | |||||
}, | }, | ||||
// 上传失败后,清空 data 里的 file,并修改上传状态 | // 上传失败后,清空 data 里的 file,并修改上传状态 | ||||
handleLocalError() { | handleLocalError() { | ||||
this.localUpload.file = null; | |||||
this.localUpload.loadingStatus = false; | |||||
this.local.file = null; | |||||
this.local.loadingStatus = false; | |||||
this.$Message.error('上传失败'); | |||||
}, | |||||
// beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false | |||||
handleYunUpload(file) { | |||||
this.yun.file = file; | |||||
return false; | |||||
}, | |||||
// 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。 | |||||
// iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。 | |||||
yunUpload() { | |||||
this.yun.loadingStatus = true; // 标记上传状态 | |||||
this.$refs.yunUploadRef.post(this.yun.file); | |||||
}, | |||||
// 上传成功后,清空 data 里的 file,并修改上传状态 | |||||
handleYunSuccess(response) { | |||||
this.yun.file = null; | |||||
this.yun.loadingStatus = false; | |||||
if (response.code === 200) { | |||||
this.$Message.success(response.message); | |||||
this.yun.log.status = response.code; | |||||
this.yun.log.message = response.message; | |||||
this.yun.log.fileName = response.data.fileName; | |||||
this.yun.log.filePath = response.data.filePath; | |||||
this.$refs.yunUploadRef.clearFiles(); | |||||
} else { | |||||
this.$Message.error(response.message); | |||||
this.yun.log.status = response.code; | |||||
this.yun.log.message = response.message; | |||||
} | |||||
}, | |||||
// 上传失败后,清空 data 里的 file,并修改上传状态 | |||||
handleYunError() { | |||||
this.yun.file = null; | |||||
this.yun.loadingStatus = false; | |||||
this.$Message.error('上传失败'); | this.$Message.error('上传失败'); | ||||
} | } | ||||
} | } | ||||