Browse Source

基础模块之 文件上传 案例完成

3.x
Yangkai.Shen 2 years ago
parent
commit
27f7bef621
14 changed files with 329 additions and 668 deletions
  1. +2
    -0
      .gitignore
  2. +0
    -25
      demo-base/demo-base-upload/.gitignore
  3. +176
    -477
      demo-base/demo-base-upload/README.md
  4. +61
    -54
      demo-base/demo-base-upload/pom.xml
  5. +2
    -2
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/UploadApplication.java
  6. +22
    -0
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/autoconfigure/QiniuProperties.java
  7. +11
    -21
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/autoconfigure/UploadAutoConfiguration.java
  8. +24
    -24
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java
  9. +0
    -25
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java
  10. +11
    -18
      demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/service/QiNiuService.java
  11. +6
    -6
      demo-base/demo-base-upload/src/main/resources/application.yml
  12. +0
    -16
      demo-base/demo-base-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java
  13. +13
    -0
      demo-base/demo-base-upload/src/test/java/com/xkcoding/upload/UploadApplicationTests.java
  14. +1
    -0
      demo-base/pom.xml

+ 2
- 0
.gitignore View File

@@ -38,3 +38,5 @@ logs/

### VS CODE ###
.vscode/

tmp/

+ 0
- 25
demo-base/demo-base-upload/.gitignore View File

@@ -1,25 +0,0 @@
/target/
!.mvn/wrapper/maven-wrapper.jar

### STS ###
.apt_generated
.classpath
.factorypath
.project
.settings
.springBeans
.sts4-cache

### IntelliJ IDEA ###
.idea
*.iws
*.iml
*.ipr

### NetBeans ###
/nbproject/private/
/build/
/nbbuild/
/dist/
/nbdist/
/.nb-gradle/

+ 176
- 477
demo-base/demo-base-upload/README.md View File

@@ -2,503 +2,202 @@

> 本 demo 演示了 Spring Boot 如何实现本地文件上传以及如何上传文件至七牛云平台。前端使用 vue 和 iview 实现上传页面。

## pom.xml
## 1.开发步骤

### 1.1.添加依赖

```xml
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>

<artifactId>spring-boot-demo-upload</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>spring-boot-demo-upload</name>
<description>Demo project for Spring Boot</description>

<parent>
<groupId>com.xkcoding</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>

<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>
</dependencies>

<build>
<finalName>spring-boot-demo-upload</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>
<dependencies>
<dependency>
<groupId>com.xkcoding</groupId>
<artifactId>common-tools</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>

<!-- 七牛云 sdk 必须需要 okhttp 客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.4</version>
</dependency>

<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>
```

## UploadConfig.java
### 1.2.七牛云配置类

```java
/**
* <p>
* 上传配置
* </p>
*
* @author yangkai.shen
* @date Created in 2018-10-23 14:09
*/
@Configuration
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(MultipartProperties.class)
public class UploadConfig {
@Value("${qiniu.accessKey}")
private String accessKey;

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

private final MultipartProperties multipartProperties;

@Autowired
public UploadConfig(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}

/**
* 上传配置
*/
@Bean
@ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}

/**
* 注册解析器
*/
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}

/**
* 华东机房
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
return new com.qiniu.storage.Configuration(Zone.zone0());
}

/**
* 构建一个七牛上传工具实例
*/
@Bean
public UploadManager uploadManager() {
return new UploadManager(qiniuConfig());
}

/**
* 认证信息实例
*/
@Bean
public Auth auth() {
return Auth.create(accessKey, secretKey);
}

/**
* 构建七牛空间管理实例
*/
@Bean
public BucketManager bucketManager() {
return new BucketManager(auth(), qiniuConfig());
}
@Data
@ConfigurationProperties(prefix = "qiniu")
public class QiniuProperties {
private String accessKey;
private String secretKey;
private String bucket;
private String prefix;
}
```

## UploadController.java
### 1.3.自动装配类

```java
/**
* <p>
* 文件上传 Controller
* </p>
*
* @author yangkai.shen
* @date Created in 2018-11-06 16:33
*/
@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", "文件上传失败");
}
}
@Configuration
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties({MultipartProperties.class, QiniuProperties.class})
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadAutoConfiguration {
private final MultipartProperties multipartProperties;
private final QiniuProperties qiniuProperties;

/**
* 上传配置
*/
@Bean
@ConditionalOnMissingBean
public MultipartConfigElement multipartConfigElement() {
return this.multipartProperties.createMultipartConfig();
}

/**
* 注册解析器
*/
@Bean(name = DispatcherServlet.MULTIPART_RESOLVER_BEAN_NAME)
@ConditionalOnMissingBean(MultipartResolver.class)
public StandardServletMultipartResolver multipartResolver() {
StandardServletMultipartResolver multipartResolver = new StandardServletMultipartResolver();
multipartResolver.setResolveLazily(this.multipartProperties.isResolveLazily());
return multipartResolver;
}

/**
* 华东机房
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
return new com.qiniu.storage.Configuration(Region.region0());
}

/**
* 构建一个七牛上传工具实例
*/
@Bean
public UploadManager uploadManager() {
return new UploadManager(qiniuConfig());
}

/**
* 认证信息实例
*/
@Bean
public Auth auth() {
return Auth.create(qiniuProperties.getAccessKey(), qiniuProperties.getSecretKey());
}

/**
* 构建七牛空间管理实例
*/
@Bean
public BucketManager bucketManager() {
return new BucketManager(auth(), qiniuConfig());
}
}
```

## QiNiuServiceImpl.java
### 1.4.七牛云上传代码

```java
/**
* <p>
* 七牛云上传Service
* </p>
*
* @author yangkai.shen
* @date Created in 2018-11-06 17:22
*/
@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);
}
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class QiNiuService implements InitializingBean {
private final Auth auth;
private final UploadManager uploadManager;
private final QiniuProperties qiniuProperties;

private StringMap putPolicy;


/**
* 七牛云上传文件
*
* @param file 文件
* @return 七牛上传Response
* @throws QiniuException 七牛异常
*/
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(qiniuProperties.getBucket(), null, 3600, putPolicy);
}
}
```

## index.html

```html
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport"
content="width=device-width, user-scalable=no, initial-scale=1.0, maximum-scale=1.0, minimum-scale=1.0">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>spring-boot-demo-upload</title>
<!-- import Vue.js -->
<script src="https://cdn.bootcss.com/vue/2.5.17/vue.min.js"></script>
<!-- import stylesheet -->
<link href="https://cdn.bootcss.com/iview/3.1.4/styles/iview.css" rel="stylesheet">
<!-- import iView -->
<script src="https://cdn.bootcss.com/iview/3.1.4/iview.min.js"></script>
</head>
<body>
<div id="app">
<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>
<script>
new Vue({
el: '#app',
data: {
local: {
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到
file: null,
// 标记上传状态
loadingStatus: false,
log: {
status: 0,
message: "",
fileName: "",
filePath: ""
}
},
yun: {
// 选择文件后,将 beforeUpload 返回的 file 保存在这里,后面会用到
file: null,
// 标记上传状态
loadingStatus: false,
log: {
status: 0,
message: "",
fileName: "",
filePath: ""
}
}
},
methods: {
// beforeUpload 在返回 false 或 Promise 时,会停止自动上传,这里我们将选择好的文件 file 保存在 data里,并 return false
handleLocalUpload(file) {
this.local.file = file;
return false;
},
// 这里是手动上传,通过 $refs 获取到 Upload 实例,然后调用私有方法 .post(),把保存在 data 里的 file 上传。
// iView 的 Upload 组件在调用 .post() 方法时,就会继续上传了。
localUpload() {
this.local.loadingStatus = true; // 标记上传状态
this.$refs.localUploadRef.post(this.local.file);
},
// 上传成功后,清空 data 里的 file,并修改上传状态
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,并修改上传状态
handleLocalError() {
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('上传失败');
}
}
})
</script>
</body>
</html>
```
### 1.5.测试Controller及前端页面

> 不是专业前端,就不在 README 献丑了,代码直接参考 `resources/templates/index.html` 即可

## 2.测试

> 注意:测试七牛云时,需要先在 `application.yml` 中配置 `qiniu.xxx` 相关参数

启动 `UploadApplication` ,打开浏览器,输入 http://localhost:8080/demo

上传几张图片试试~

![测试上传](https://static.xkcoding.com/spring-boot-demo/demo-base/demo-base-upload/2022-09-21-083229.png)

## 3.参考

- [Spring Boot 官方文档之文件上传](https://docs.spring.io/spring-boot/docs/3.0.0-M4/reference/htmlsingle/#howto.spring-mvc.multipart-file-uploads)

## 参考
- [七牛云官方文档之 Java SDK](https://developer.qiniu.com/kodo/1239/java)

1. Spring 官方文档:https://docs.spring.io/spring-boot/docs/2.1.0.RELEASE/reference/htmlsingle/#howto-multipart-file-upload-configuration
2. 七牛云官方文档:https://developer.qiniu.com/kodo/sdk/1239/java#5
## 4.特别感谢

- 感谢 [七牛云](https://portal.qiniu.com/signup?utm_source=kaiyuan&utm_media=xkcoding) 提供的免费云存储与 CDN 加速支持

+ 61
- 54
demo-base/demo-base-upload/pom.xml View File

@@ -1,70 +1,77 @@
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<artifactId>demo-upload</artifactId>
<parent>
<groupId>com.xkcoding</groupId>
<artifactId>demo-base</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>
</parent>

<modelVersion>4.0.0</modelVersion>

<artifactId>demo-base-upload</artifactId>
<version>1.0.0-SNAPSHOT</version>
<packaging>jar</packaging>

<name>demo-base-upload</name>
<description>Demo project for Spring Boot</description>

<name>demo-upload</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>17</java.version>

<parent>
<groupId>com.xkcoding</groupId>
<artifactId>spring-boot-demo</artifactId>
<version>1.0.0-SNAPSHOT</version>
</parent>
<qiniu.version>7.11.0</qiniu.version>
</properties>

<properties>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
<project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>com.xkcoding</groupId>
<artifactId>common-tools</artifactId>
</dependency>

<dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-thymeleaf</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>

<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>${qiniu.version}</version>
</dependency>

<dependency>
<groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId>
</dependency>
<!-- 七牛云 sdk 必须需要 okhttp 客户端 -->
<dependency>
<groupId>com.squareup.okhttp3</groupId>
<artifactId>okhttp</artifactId>
<version>3.14.4</version>
</dependency>

<dependency>
<groupId>com.qiniu</groupId>
<artifactId>qiniu-java-sdk</artifactId>
<version>[7.2.0, 7.2.99]</version>
</dependency>
</dependencies>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
</dependencies>

<build>
<finalName>demo-upload</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
<build>
<finalName>demo-base-upload</finalName>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>

</project>

demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/SpringBootDemoUploadApplication.java → demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/UploadApplication.java View File

@@ -12,9 +12,9 @@ import org.springframework.boot.autoconfigure.SpringBootApplication;
* @date Created in 2018-10-20 21:23
*/
@SpringBootApplication
public class SpringBootDemoUploadApplication {
public class UploadApplication {

public static void main(String[] args) {
SpringApplication.run(SpringBootDemoUploadApplication.class, args);
SpringApplication.run(UploadApplication.class, args);
}
}

+ 22
- 0
demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/autoconfigure/QiniuProperties.java View File

@@ -0,0 +1,22 @@
package com.xkcoding.upload.autoconfigure;

import lombok.Data;
import org.springframework.boot.context.properties.ConfigurationProperties;

/**
* <p>
* 七牛云配置
* </p>
*
* @author yangkai.shen
* @date 2022-09-21 16:09
*/

@Data
@ConfigurationProperties(prefix = "qiniu")
public class QiniuProperties {
private String accessKey;
private String secretKey;
private String bucket;
private String prefix;
}

demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/config/UploadConfig.java → demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/autoconfigure/UploadAutoConfiguration.java View File

@@ -1,11 +1,13 @@
package com.xkcoding.upload.config;
package com.xkcoding.upload.autoconfigure;

import com.qiniu.common.Zone;
import com.qiniu.storage.BucketManager;
import com.qiniu.storage.Region;
import com.qiniu.storage.UploadManager;
import com.qiniu.util.Auth;
import jakarta.servlet.MultipartConfigElement;
import jakarta.servlet.Servlet;
import lombok.RequiredArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
@@ -17,9 +19,6 @@ import org.springframework.web.multipart.MultipartResolver;
import org.springframework.web.multipart.support.StandardServletMultipartResolver;
import org.springframework.web.servlet.DispatcherServlet;

import javax.servlet.MultipartConfigElement;
import javax.servlet.Servlet;

/**
* <p>
* 上传配置
@@ -31,20 +30,11 @@ import javax.servlet.Servlet;
@Configuration
@ConditionalOnClass({Servlet.class, StandardServletMultipartResolver.class, MultipartConfigElement.class})
@ConditionalOnProperty(prefix = "spring.http.multipart", name = "enabled", matchIfMissing = true)
@EnableConfigurationProperties(MultipartProperties.class)
public class UploadConfig {
@Value("${qiniu.accessKey}")
private String accessKey;

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

@EnableConfigurationProperties({MultipartProperties.class, QiniuProperties.class})
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadAutoConfiguration {
private final MultipartProperties multipartProperties;

@Autowired
public UploadConfig(MultipartProperties multipartProperties) {
this.multipartProperties = multipartProperties;
}
private final QiniuProperties qiniuProperties;

/**
* 上传配置
@@ -71,7 +61,7 @@ public class UploadConfig {
*/
@Bean
public com.qiniu.storage.Configuration qiniuConfig() {
return new com.qiniu.storage.Configuration(Zone.zone0());
return new com.qiniu.storage.Configuration(Region.region0());
}

/**
@@ -87,7 +77,7 @@ public class UploadConfig {
*/
@Bean
public Auth auth() {
return Auth.create(accessKey, secretKey);
return Auth.create(qiniuProperties.getAccessKey(), qiniuProperties.getSecretKey());
}

/**

+ 24
- 24
demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/controller/UploadController.java View File

@@ -7,10 +7,12 @@ 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 com.xkcoding.upload.autoconfigure.QiniuProperties;
import com.xkcoding.upload.service.QiNiuService;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.web.servlet.MultipartProperties;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestMapping;
@@ -29,21 +31,22 @@ import java.io.IOException;
* @author yangkai.shen
* @date Created in 2018-11-06 16:33
*/
@RestController
@Slf4j
@RestController
@RequestMapping("/upload")
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class UploadController {
@Value("${spring.servlet.multipart.location}")
private String fileTempPath;
private final MultipartProperties multipartProperties;

@Value("${qiniu.prefix}")
private String prefix;
private final QiniuProperties qiniuProperties;

private final IQiNiuService qiNiuService;
private final QiNiuService qiNiuService;

@Autowired
public UploadController(IQiNiuService qiNiuService) {
this.qiNiuService = qiNiuService;
private String processLocalTmpFilePath(MultipartFile file) {
String fileName = file.getOriginalFilename();
String rawFileName = StrUtil.subBefore(fileName, ".", true);
String fileType = StrUtil.subAfter(fileName, ".", true);
return StrUtil.appendIfMissing(multipartProperties.getLocation(), "/") + rawFileName + "-" + DateUtil.current() + "." + fileType;
}

@PostMapping(value = "/local", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
@@ -51,10 +54,7 @@ public class UploadController {
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;
String localFilePath = processLocalTmpFilePath(file);
try {
file.transferTo(new File(localFilePath));
} catch (IOException e) {
@@ -63,18 +63,16 @@ public class UploadController {
}

log.info("【文件上传至本地】绝对路径:{}", localFilePath);
return Dict.create().set("code", 200).set("message", "上传成功").set("data", Dict.create().set("fileName", fileName).set("filePath", localFilePath));
return Dict.create().set("code", 200).set("message", "上传成功")
.set("data", Dict.create().set("fileName", file.getOriginalFilename()).set("filePath", localFilePath));
}

@PostMapping(value = "/yun", consumes = MediaType.MULTIPART_FORM_DATA_VALUE)
public Dict yun(@RequestParam("file") MultipartFile file) {
public Dict qiniu(@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;
String localFilePath = processLocalTmpFilePath(file);
try {
file.transferTo(new File(localFilePath));
Response response = qiNiuService.uploadFile(new File(localFilePath));
@@ -82,19 +80,21 @@ public class UploadController {
JSONObject jsonObject = JSONUtil.parseObj(response.bodyString());

String yunFileName = jsonObject.getStr("key");
String yunFilePath = StrUtil.appendIfMissing(prefix, "/") + yunFileName;
String yunFilePath = StrUtil.appendIfMissing(qiniuProperties.getPrefix(), "/") + 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));
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);
log.error("【文件上传至七牛云】失败,绝对路径:{}", localFilePath, e);
return Dict.create().set("code", 500).set("message", "文件上传失败");
}
}


+ 0
- 25
demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/service/IQiNiuService.java View File

@@ -1,25 +0,0 @@
package com.xkcoding.upload.service;

import com.qiniu.common.QiniuException;
import com.qiniu.http.Response;

import java.io.File;

/**
* <p>
* 七牛云上传Service
* </p>
*
* @author yangkai.shen
* @date Created in 2018-11-06 17:21
*/
public interface IQiNiuService {
/**
* 七牛云上传文件
*
* @param file 文件
* @return 七牛上传Response
* @throws QiniuException 七牛异常
*/
Response uploadFile(File file) throws QiniuException;
}

demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/service/impl/QiNiuServiceImpl.java → demo-base/demo-base-upload/src/main/java/com/xkcoding/upload/service/QiNiuService.java View File

@@ -1,15 +1,15 @@
package com.xkcoding.upload.service.impl;
package com.xkcoding.upload.service;

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 com.xkcoding.upload.autoconfigure.QiniuProperties;
import lombok.RequiredArgsConstructor;
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;
@@ -22,23 +22,16 @@ import java.io.File;
* @author yangkai.shen
* @date Created in 2018-11-06 17:22
*/
@Service
@Slf4j
public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {
private final UploadManager uploadManager;
@Service
@RequiredArgsConstructor(onConstructor_ = @Autowired)
public class QiNiuService implements InitializingBean {
private final Auth auth;

@Value("${qiniu.bucket}")
private String bucket;
private final UploadManager uploadManager;
private final QiniuProperties qiniuProperties;

private StringMap putPolicy;

@Autowired
public QiNiuServiceImpl(UploadManager uploadManager, Auth auth) {
this.uploadManager = uploadManager;
this.auth = auth;
}

/**
* 七牛云上传文件
@@ -47,7 +40,6 @@ public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {
* @return 七牛上传Response
* @throws QiniuException 七牛异常
*/
@Override
public Response uploadFile(File file) throws QiniuException {
Response response = this.uploadManager.put(file, file.getName(), getUploadToken());
int retry = 0;
@@ -61,7 +53,8 @@ public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {
@Override
public void afterPropertiesSet() {
this.putPolicy = new StringMap();
putPolicy.put("returnBody", "{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}");
putPolicy.put("returnBody",
"{\"key\":\"$(key)\",\"hash\":\"$(etag)\",\"bucket\":\"$(bucket)\",\"width\":$(imageInfo.width), \"height\":${imageInfo.height}}");
}

/**
@@ -70,6 +63,6 @@ public class QiNiuServiceImpl implements IQiNiuService, InitializingBean {
* @return 上传凭证
*/
private String getUploadToken() {
return this.auth.uploadToken(bucket, null, 3600, putPolicy);
return this.auth.uploadToken(qiniuProperties.getBucket(), null, 3600, putPolicy);
}
}

+ 6
- 6
demo-base/demo-base-upload/src/main/resources/application.yml View File

@@ -4,17 +4,17 @@ server:
context-path: /demo
qiniu:
## 此处填写你自己的七牛云 access key
accessKey:
accessKey: xx
## 此处填写你自己的七牛云 secret key
secretKey:
secretKey: xxx
## 此处填写你自己的七牛云 bucket
bucket:
bucket: blog
## 此处填写你自己的七牛云 域名
prefix:
prefix: https://static.xkcoding.com/
spring:
servlet:
multipart:
enabled: true
location: /Users/yangkai.shen/Documents/code/back-end/spring-boot-demo/spring-boot-demo-upload/tmp
location: /Users/yangkai.shen/Codes/xkcoding/spring-boot-demo/tmp
file-size-threshold: 5MB
max-file-size: 20MB
max-file-size: 20MB

+ 0
- 16
demo-base/demo-base-upload/src/test/java/com/xkcoding/upload/SpringBootDemoUploadApplicationTests.java View File

@@ -1,16 +0,0 @@
package com.xkcoding.upload;

import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.test.context.junit4.SpringRunner;

@RunWith(SpringRunner.class)
@SpringBootTest
public class SpringBootDemoUploadApplicationTests {

@Test
public void contextLoads() {
}

}

+ 13
- 0
demo-base/demo-base-upload/src/test/java/com/xkcoding/upload/UploadApplicationTests.java View File

@@ -0,0 +1,13 @@
package com.xkcoding.upload;

import org.junit.jupiter.api.Test;
import org.springframework.boot.test.context.SpringBootTest;

@SpringBootTest
class UploadApplicationTests {

@Test
void contextLoads() {
}

}

+ 1
- 0
demo-base/pom.xml View File

@@ -24,6 +24,7 @@
<module>demo-base-async</module>
<module>demo-base-exception</module>
<module>demo-base-email</module>
<module>demo-base-upload</module>
</modules>

</project>

Loading…
Cancel
Save