项目关于Summernote的图片处理和基于SpringMVC的文件上传(10)

网友投稿 748 2022-05-30

45. 我的问答列表-前端页面

在index.html页面,先找到整个列表区域的父级,为其添加id,以便于创建Vue对象:

1

2

找到每个问题的显示区域,为这个区域的根级添加v-for以循环显示:

1

2

关于问题的状态:

已解决

1

问题的标题:

eclipse 如何导入项目?

1

问题的内容:

eclipse 如何导入项目?

1

2

3

4

5

问题的标签列表:

Java基础

1

2

3

右下角的更多信息:

风继续吹 12浏览 13分钟前

1

2

3

显示图片:

1

完成后,先利用以上模拟的数据进行测试,也就是直接打开浏览器,观察运行效果与预期是否相符!

测试完成后,在my.js中,向服务器端发送请求获取真实的数据,并用于显示页面:

let questionsApp = new Vue({ el: '#questionsApp', data: { questions: [] }, methods: { loadMyQuestions: function () { $.ajax({ url: '/api/v1/questions/my', success: function (json) { // json.data.list let data = json.data; let questions = data.list; let statusTexts = ['未回复', '未解决', '已解决']; let statusClasses = ['badge-warning', 'badge-info', 'badge-success']; for (let i = 0; i < questions.length; i++) { questions[i].statusText = statusTexts[questions[i].status]; questions[i].statusClass = statusClasses[questions[i].status]; questions[i].tagImage = '/img/tags/' + questions[i].tags[0].id + '.jpg'; questions[i].createdTimeText = "未知时间"; } questionsApp.questions = questions; } }); } }, created: function () { this.loadMyQuestions(); } });

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

关于将时间显示为“刚刚” / “xx分钟前”等格式的代码:

let now = new Date().getTime(); let pastTime = (now - new Date(questions[i].createdTime).getTime()) / 1000; let createdTimeText = "未知时间"; if (pastTime < 60) { // 不足1分钟 createdTimeText = "刚刚"; } else if (pastTime < 60 * 60) { // 不足1小时 createdTimeText = parseInt(pastTime / 60) + "分钟前"; } else if (pastTime < 60 * 60 * 24) { createdTimeText = parseInt(pastTime / 60 / 60) + "小时前"; } else { createdTimeText = parseInt(pastTime / 60 / 60 / 24) + "天前"; } questions[i].createdTimeText = createdTimeText;

1

2

3

4

5

6

7

8

9

10

11

12

13

14

最后,关于显示分页,首先要使得以前加载数据的函数是支持页码参数的:

loadMyQuestions: function (page) { if (!page || page < 1) { page = 1; } $.ajax({ url: '/api/v1/questions/my', data: 'page=' + page, // 省略后续代码 }); }

1

2

3

4

5

6

7

8

9

10

关于分页的页面部分的代码:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

在my.js中,在属性中声明pageInfo:

data: { questions: [], pageInfo: null }

1

2

3

4

当获取数据后,添加:

questionsApp.pageInfo = data;

1

至此,页面的显示已完成,关于my.js的完整代码:

let questionsApp = new Vue({ el: '#questionsApp', data: { questions: [ { statusText: '已解决', statusClass: 'badge-success', title: '这是第1个问题', content: '很严肃的提出了第1个问题', tags: [ { id: 8, name: 'Java基础' }, { id: 12, name: 'Spring' }, { id: 15, name: 'SpringBoot' } ], userNickName: '天下第一', hits: '826', createdTimeText: '8小时之前', tagImage: '/img/tags/8.jpg' }, { statusText: '未回复', statusClass: 'badge-warning', title: '这是第2个问题', content: '我也不告诉你是什么问题……', tags: [ { id: 10, name: 'MySQL' }, { id: 17, name: 'SpringSecurity' } ], userNickName: '天下第一', hits: '537', createdTimeText: '15小时之前', tagImage: '/img/tags/10.jpg' } ], pageInfo: null }, methods: { loadMyQuestions: function (page) { if (!page || page < 1) { page = 1; } $.ajax({ url: '/api/v1/questions/my', data: 'page=' + page, success: function (json) { // json.data.list let data = json.data; let questions = data.list; let statusTexts = ['未回复', '未解决', '已解决']; let statusClasses = ['badge-warning', 'badge-info', 'badge-success']; for (let i = 0; i < questions.length; i++) { questions[i].statusText = statusTexts[questions[i].status]; questions[i].statusClass = statusClasses[questions[i].status]; questions[i].tagImage = '/img/tags/' + questions[i].tags[0].id + '.jpg'; let now = new Date().getTime(); let pastTime = (now - new Date(questions[i].createdTime).getTime()) / 1000; let createdTimeText = "未知时间"; if (pastTime < 60) { // 不足1分钟 createdTimeText = "刚刚"; } else if (pastTime < 60 * 60) { // 不足1小时 createdTimeText = parseInt(pastTime / 60) + "分钟前"; } else if (pastTime < 60 * 60 * 24) { createdTimeText = parseInt(pastTime / 60 / 60) + "小时前"; } else { createdTimeText = parseInt(pastTime / 60 / 60 / 24) + "天前"; } questions[i].createdTimeText = createdTimeText; } questionsApp.questions = questions; questionsApp.pageInfo = data; } }); } }, created: function () { this.loadMyQuestions(); } });

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

29

30

31

32

33

34

35

36

37

38

39

40

41

42

43

44

45

46

47

48

49

50

51

52

53

54

55

56

57

58

59

60

61

62

63

64

65

66

67

68

69

70

71

72

73

74

75

76

77

78

79

80

46. 关于Summernote的图片处理

使用Summernote富文本编辑器时,当需要处理图片时,会自动将图片转换为Base64编码,当提交问题时,图片的Base64编码会作为“问题正文”的一部分提交到服务器端,最终,会被存储到数据库中!使用这种做法,会急剧增加数据库所占用的存储空间,对数据库的检索性能也会产生影响,不利于数据库的管理和维护,同时,由于图片已经转换为Base64编码作为正文的一部分数据,也不利于管理图片!

Summernote允许在配置Summernote富文本编辑器时自定义回调函数,该函数会在用户填写正文时选择图片会自动调用,则开发人员可以配置这个回调函数,当用户选择图片后,将图片以文件的形式直接上传到服务器端,当上传成功后,再将图片的路径返回到客户端,插入到Summernote中即可!

最后,在Summernote组织的“问题正文”中,关于图片可能就只是一段例如 上传图片

上传图片

请选择您要上传的文件:

1

2

3

4

5

6

7

8

9

10

11

12

13

14

SpringMVC框架处理文件上传是基于commons-fileupload的,如果使用SpringMVC框架,需要自行添加这个依赖,如果使用SpringBoot框架则不需要,已经内置添加了。

在SpringMVC框架中,在控制器端会使用MultipartFile接口类型的参数来接收客户端提交的上传数据,在处理请求的方法中,直接声明这个接口类型的参数即可,参数名应该与客户端提交请求时的名称保持一致!在处理请求的过程中,调用MutlipartFile接口对象的void transferTo(File dest)方法就可以将图片保持到参数dest对应的文件位置。

可以在服务器端创建控制器处理上传请求:

@RestController public class FileUploadController { @RequestMapping("/upload") public String upload(MultipartFile image) throws IOException { image.transferTo(new File("d:/1.jpg")); return "OK"; } }

1

2

3

4

5

6

7

8

9

10

注意:SpringBoot默认限制了上传文件的大小为1M / 10M(根据SpringBoot版本不同存在差异)。

关于文件名的处理:

文件名必须保证唯一,不要出现“覆盖上传”的现象(即使你认为原有的文件没有用了,也不要覆盖);

扩展名应该与原始扩展名(文件在客户端设备中的名称)保持一致,注意:如果某个文件全名中只有第1位是小数点,并没有更多的小数点,是表示该文件在Linux / MacOS中是隐藏文件,小数点右侧的并不是扩展名!

示例代码:

@RequestMapping("/upload") public String upload(MultipartFile image) throws IOException { String parent = "d:/"; // 处理文件名 // 关于文件名的策略:时间 + 随机数 // 无论当前上传功能是用于哪个用途,文件名必须唯一 String filename = UUID.randomUUID().toString(); // 处理扩展名 // 获取原始文件名 String originalFilename = image.getOriginalFilename(); System.out.println("originalFilename=" + originalFilename); // 暂定扩展名空字符串 String suffix = ""; // 如果原始文件名中存在有效的扩展名,则截取 int beginIndex = originalFilename.lastIndexOf("."); if (beginIndex > 0) { suffix = originalFilename.substring(beginIndex); } // 得到完整的文件名 String child = filename + suffix; // 保存上传的文件 image.transferTo(new File(parent, child)); return "OK"; }

1

2

3

4

5

6

7

8

9

10

11

12

13

14

15

16

17

18

19

20

21

22

23

24

25

26

27

28

关于保存文件的路径,首先,所有的上传都是为了下载的,所以,必须保证上传的文件夹是可以被访问到的文件夹,例如将文件上传到Tomcat的部署文件夹中,对于使用SpringBoot开发项目来说,也可以理解为“需要将文件上传到static文件夹或webapp文件夹下”!

可选的解决方案有:

String parent = request.getServletContext().getRealPath("20200725"); System.out.println("parent=" + parent); File parentFile = new File(parent); if (!parentFile.exists()) { parentFile.mkdirs(); }

1

2

3

4

5

6

以上做法是将文件直接上传到项目的webapp文件夹中,这样做不便于管理文件,因为项目文件和上传的文件都在同个文件夹之下!

SpringMVC / SpringBoot可以自定义“资源目录”,当某个文件夹被设置为“资源目录”时,该目录下的内容是可以直接通过HTTP协议进行访问的!相当于static或webapp文件夹。

在SpringBoot项目的application.properties文件中进行配置:

spring.resources.static-locations=file:d:/upload

1

则d:/upload就变成了“资源目录”,如果在这个文件夹中添加文件,是可以直接通过HTTP协议访问的!

然后,在application.properties中添加自定义配置,并将自定义配置值用于配置“资源目录”,并且,由于自定义了资源目录,原本static就不再是资源目录了,需要显式的指定:

project.upload-location=d:/upload spring.resources.static-locations[0]=classpath:/static spring.resources.static-locations[1]=file:${project.upload-location}

1

2

3

4

在控制器中,可以直接读取到以上配置:

@Value("${project.upload-location}") private String parent;

1

2

后续,使用以上parent作为上传的文件夹即可。

在处理上传时,关于MultipartFile的常用API有:

boolean isEmpty():判断上传的文件是否为空,如果在表单中没有选择文件,或选择的文件是0字节的,即为空;

long getSize():获取文件大小,以字节为单位;

String contentType:获取文档的MIME类型;

String getOriginalFilename():获取上传的文件的原始文件名;

void transferTo(File dest):保存上传的文件。

图像处理 数据库

版权声明:本文内容由网络用户投稿,版权归原作者所有,本站不拥有其著作权,亦不承担相应法律责任。如果您发现本站中有涉嫌抄袭或描述失实的内容,请联系我们jiasou666@gmail.com 处理,核实后本网站将在24小时内删除侵权内容。

上一篇:测试效能平台最佳实践 | 解决用户痛点,比堆叠功能更重要!
下一篇:Centos7 Apache配置虚拟主机的三种方式
相关文章