目录
一、文件上传
1.使用Ajax实现文件上传
首先,需要在页面中引入jQuery
框架,先准备一个jQuery
的.js
文件,放到静态资源目录下,然后,在HTML文件中引用:
为了避免点击按钮时提交同步请求,先将按钮的类型改为button
,然后绑定单击事件:
此时,启动项目,点击按钮时,即可弹出警告!
测试无误后,在以上函数中实现异步提交:
2.处理多文件上传
在处理多文件上传时,首先,必须明确这多个文件的数量、定位是否明确!
假设需要实现的功能是“上传身份证照片的正反面照片”,则需要上传2张图片,且图片的数量及每一张图片的定位(作用)是明确的,则可以在页面中设计2个上传控件,这2个控件使用不同的名字,例如:
<form id="form-upload">
<p>请选择身份证的正面照片(个人信息面):</p>
<p><input type="file" name="file1"></p>
<p>请选择身份证的反面照片(国徽面):</p>
<p><input type="file" name="file2"></p>
<p><input id="btn-submit" type="button" value="上传"></p>
</form>
然后,在控制器中,声明2个MultipartFile
类型的参数用于接收这2张图片对应的对象:
@PostMapping("/upload")
public String upload(MultipartFile file1, MultipartFile file2) {
// 分别处理file1和file2
}
假设需要上传的文件的数量是不确定的,各文件的定位(作用)是相同的,例如发表一篇微博,或者管理网络上的相册等,在设计页面时,可以在<input type="file" />
控件上添加multiple
属性,在用户在浏览需要上传的文件时,可以按住键盘的Ctrl键选择多个文件:
<form id="form-upload">
<p>请选择选择您要上传的文件(按住Ctrl键可以选择多个):</p>
<p><input type="file" name="files" multiple="multiple"></p>
<p><input id="btn-submit" type="button" value="上传"></p>
</form>
然后,在控制器中,使用MultipartFile
数组来接收客户端提交的多个文件并处理即可:
@PostMapping("/upload")
public String upload(MultipartFile[] files) {
// 可以使用循环语法对参数files进行处理
}
二、自定义summernote上传
1.定义浏览图片后的回调
使用summernote上传文件时,当浏览图片并确定,默认情况下,就会将图片转换为Base64编码的字符串,并创建Image
对象插入到summernote编辑区,同时,summernote允许自定义浏览图片并确定后的回调!
具体操作步骤如下:在create.html
的最下方存在初始化summernote的代码:
应该将以上代码(除了最下方的$('select')
那一行以外)使用专门的文件来保存,所以,在static/js/commons
下创建init_summernote.js
文件,将以上代码剪切到该文件中:
并在create.html
中引用该文件:
接下来,在init_summernote.js
中自定义回调:
再次运行项目,尝试上传图片,当选中图片后,会提示警告框,并且,选中的图片也不会被转换为Base64编码并插入到编辑区了:
2.在服务端接收客户端上传的图片
可以在api-question
中实现文件上传,先在StrawApiQuestionApplication
和StrawGatewayApplication
中自定义允许上传的文件的大小的上限值(因为SpringBoot默认已经设置了上限文件大小的限制):
@Bean
public MultipartConfigElement multipartConfigElement() {
MultipartConfigFactory factory = new MultipartConfigFactory();
factory.setMaxFileSize(DataSize.ofMegabytes(500));
factory.setMaxRequestSize(DataSize.ofMegabytes(500));
return factory.createMultipartConfig();
}
关于以上配置,也可以在
application.properties
中添加配置来实现!
在处理上传的过程中,还会进行相关的判断,如果上传的图片不符合规则,应该抛出对应的异常!则在straw-commons
的cn.tedu.straw.commons.ex
包下创建异常类:
// 自定义的“上传文件异常”的基类异常
public class FileUploadException extends RuntimeException {}
// 上传的文件为空,父类是自定义的异常(注意:另有一个异常与自定义的FileUploadException同名)
public class FileEmptyException extends FileUploadException {}
public class FileSizeException extends FileUploadException {}
public class FileTypeException extends FileUploadException {}
public class FileUploadIOException extends FileUploadException {}
public class FileStateException extends FileUploadException {}
由于创建了新的异常类(1个基数 + 5个子类),需要在R.State
中添加5个对应的常量:
/**
* 错误:上传的文件为空
*/
Integer ERR_FILE_EMPTY = 4006;
/**
* 错误:上传的文件大小超出了限制
*/
Integer ERR_FILE_SIZE = 4007;
/**
* 错误:上传的文件类型超出了限制
*/
Integer ERR_FILE_TYPE = 4008;
/**
* 错误:上传文件时出现读写错误
*/
Integer ERR_FILE_IO = 4009;
/**
* 错误:上传文件的状态错误
*/
Integer ERR_FILE_STATE = 4010;
同时,在straw-api-question
的GlobalExceptionHandler
类中处理以上5种异常:
关于检查上传时的限制(例如文件大小的上限、文件的类型)可以定义在配置文件中,便于统一管理!可以在straw-api-question
的application.properties
中添加自定义配置,同时,还可以添加相关的配置信息,例如将上传的文件保存到哪里:
# 自定义配置:上传文件-问题的图片-文件大小限制值
project.fileupload.question-image.max-size=5242880
# 自定义配置:上传文件-问题的图片-文件类型限制值
project.fileupload.question-image.content-types=image/jpeg, image/png, image/gif
# 自定义配置:上传文件-根目录
project.fileupload.base-dir=E:/static-resource
需要读取以上配置值时,可以通过@Value
注解来读取:
@Value("${project.fileupload.question-image.max-size}")
long maxSize;
@Value("${project.fileupload.question-image.content-types}")
List<String> contentTypes;
@Value("${project.fileupload.base-dir}")
String baseDir;
在straw-api-question
的QuestionController
中添加处理请求的方法,处理上传的请求:
@PostMapping("/image/upload")
public R imageUpload(MultipartFile file) {
// 检查上传的图片是否为空
if (file.isEmpty()) {
throw new FileEmptyException("上传失败!请选择有效的图片文件!");
}
// 检查上传的图片大小是否超标
if (file.getSize() > maxSize) {
throw new FileSizeException("上传失败!不允许上传超过" + maxSize / 1024 / 1024 + "MB的图片文件!");
}
// 检查上传的图片类型是否超标
if (!contentTypes.contains(file.getContentType())) {
throw new FileTypeException("上传失败!图片类型错误!允许上传的文件类型有:" + contentTypes);
}
// 确定上传的文件保存到哪个文件夹
// 利用配置文件中的配置值,结合年月得到例如 E:/static-resource/2020/09 这样的文件夹
// 2020/09 < DateTimeFormatter.of("yyyy/MM").format(LocalDate.now())
// 并结合File对象的exists()判断文件夹是否存在,如果不存在,则通过mkdirs()创建
String yearAndMonth = DateTimeFormatter.ofPattern("yyyy/MM").format(LocalDate.now());
File parent = new File(baseDir, yearAndMonth);
if (!parent.exists()) {
// parent.mkdir();
parent.mkdirs();
}
// 确定上传的文件使用什么文件名
String filename = System.currentTimeMillis() + "-" + System.nanoTime();
// 确定上传的文件使用什么扩展名
String originalFilename = file.getOriginalFilename();
int beginIndex = originalFilename.lastIndexOf(".");
String suffix = originalFilename.substring(beginIndex);
// 确定上传的文件使用什么文件全名
String child = filename + suffix;
// 创建上传的文件在保存时使用的File对象
File dest = new File(parent, child);
try {
// 执行保存上传的文件
file.transferTo(dest);
} catch (IOException e) {
// 抛出FileUploadIOException
throw new FileUploadIOException("上传失败!上传过程中出现读写错误,请稍后再次尝试!");
} catch (IllegalStateException e) {
// 抛出FileStateException
throw new FileStateException("上传失败!文件状态有误,请检查上传的文件是否有效!");
}
// 日志
log.debug("上传成功!文件路径:{}", dest);
// 返回,暂定为R.ok()
return R.ok();
}
3.整合页面实现文件上传
在init_summernote.js
中处理选择文件后的回调函数:
$(document).ready(function () {
$('#summernote').summernote({
height: 300,
tabsize: 2,
lang: 'zh-CN',
placeholder: '请输入问题的详细描述...',
callbacks: {
onImageUpload: function (files) {
// alert('将执行自定义的上传图片回调!文件数量=' + files.length);
if (files.length > 1) {
alert("一次只能选择1张图片!");
return;
}
let file = files[0];
let data = new FormData();
data.append('file', file);
$.ajax({
url: '/api-question/v1/questions/image/upload',
data: data,
contentType: false,
processData: false,
type: 'POST',
success: function (json) {
if (json.state == 2000) {
alert('上传成功!');
} else {
alert(json.message);
}
}
});
}
}
});
});
完成后,依次重启straw-api-question
和straw-gateway
项目,在summernote中浏览文件并确定,将会上传选中的文件,并且,在E:/static-resource
文件下来可以看到创建的子级文件夹及文件。
4.使用资源项目对外提供图片访问
创建完成后,调整pom.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 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>cn.tedu</groupId>
<artifactId>straw</artifactId>
<version>0.0.1-SNAPSHOT</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>cn.tedu</groupId>
<artifactId>straw-resource</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>straw-resource</name>
<dependencies>
<!-- Eureka Client -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-netflix-eureka-client</artifactId>
</dependency>
<!-- web:允许项目启动在Tomcat -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
</dependencies>
</project>
然后,删除项目中的test
文件夹!
然后,在application.properties
中添加必要的配置:
# 显式的配置当前项目部署到服务器时运行在哪个端口号
server.port=8888
# 应用程序名称
spring.application.name=resource
# 静态资源目录
spring.resources.static-locations=file:E:/static-resource
# 是否使用IP地址来注册,如果是,则注册IP地址,如果否,则注册主机名
eureka.instance.prefer-ip-address=true
# 当前项目在Eureka Server中注册的主机名,仅当prefer-ip-address设置为false时有效
eureka.instance.hostname=localhost
# 当前项目在Eureka Server中注册的IP地址,当prefer-ip-address设置为true时有效
eureka.instance.ip-address=127.0.0.1
# 当前项目在Eureka状态页显示的信息
eureka.instance.instance-id=${spring.application.name}@${eureka.instance.ip-address}:${server.port}
启动项目后,在浏览器中,通过例如 http://localhost:8888/2020/09/1600672303652-886423607391600.png 这样的URL即可访问到此前成功上传的图片文件:
然后,在straw-gateway
的application.properties
中配置针对这个项目的转发规则:
然后,重启straw-gateway
后,原本访问图片的URL改为 http://localhost/resource/2020/09/1600672303652-886423607391600.png 也可以访问成功:
5.服务器端向客户端响应上传的文件的URL
当客户端成功的上传了文件后,服务器端应该将上传的文件的URL响应到客户端,则客户端才可以正常的显示图片!
先在straw-api-question
的application.properties
中补充自定义配置:
并在QuestionController
中读取该配置:
在上传成功之后,拼接出访问该图片的路径:
注意:不要忽略拼接时需要添加的/
,否则会导致最终路径出错!
完成后,重启straw-api-question
项目,再次上传图片,在控制台可以看到输出结果:
将以上路径复制,并补充 http://localhost 前缀,得到完整的例如 http://localhost/resource/2020/09/1600676569950-890689905787100.png 这样的URL,在浏览器中访问,即可看到刚才上传的图片!
当检查以上imageUrl
的值无误后,在处理上传请求的方法的最后,返回该值到客户端:
则客户端上传图片后,当上传成功时,得到的响应结果大致是:
{
"state": 2000,
"data": "/resource/2020/09/1600676569950-890689905787100.png"
}
最后,在init_summernote.js
中,可以测试输出结果:
完成后,分别重启straw-api-question
和straw-gateway
项目,再次上传图片,在浏览器的控制台即可看到输出的图片路径:
6.在网页中显示新上传的文件
如果需要将上传的图片插入到summernote的编辑区,需要根据以上得到的路径值创建图片标签的对象,并调用summernote的API执行插入:
完成后,再次重启straw-gateway
,发布一篇带图片的“问题”,在数据库中即可看到图片是通过路径来引用显示的,不再是此前的Base64编码!
7.添加测试数据
通过truncate
命令清空question
、question_tag
、user_question
这3张表的数据,然后,通过页面登录,并发布不少于20篇问题,并且,至少有1个账号发布不少于15篇问题,以便于后续的功能开发及演示。
添加完成后,直接在question
表中编辑数据,手动修改每个“问题”的hits
值,尽量使得每个值都不同。