本文是 “文件上传怎么做?” 教程的下篇, 主要讲解服务器端的实现.
基础理论和客户端实现方法看这里: 文件上传怎么做 (上)
3. 服务器端实现
如前所述, 服务器端可以使用不同的技术来实现, 只要和前端接口对上就行, 下面我们分不同的技术来举例说明.
3.1 使用 Servlet 实现
(1) 下载第 3 方工具包
这里, 我们会使用到 Apache 基金会的子项目提供的工具包: commons-fileupload , 而它又依赖于 commons-io
所以, 我们得先把这两个工具包准备好. 如果懒得找, 这里提供两个链接:
commons-fileupload-1.4.jar , commons-io-2.6.jar
(2) 将第 3 方工具包添加到项目 Class Path
将下载到的两个 jar 文件拷贝到项目的 WEB-INF/lib 文件夹下 ( 如果没有 lib 文件夹, 自己新建即可 )
右键单击 lib 图标 → Add as Library … , 名称随意, 点 “OK”.
(3) 新建 UploadServlet.java
在 src / servlets 下新建一个名为 UploadServlet 的 Java 类文件 ( 自己新建 servlets 包 )
完成上述步骤后, 项目结构大概应该是这样子滴:
若上述第 (2) 步操作有误, 那么 commons-fileupload-1.4.jar, commons-io-2.6.jar 前面不会有 “小三角”, 也就是说, 你没有成功把它们添加到 Class Path.
若使用 Eclipse, 亦需要把第 3 方库添加到 Class Path, 操作方法差不多.
(4) 编写 UploadServlet.java 的代码
Code 3.1: UploadServlet.java
1 | package servlets; |
哎哟~ 妈呀~ 这么长! 别慌, 我们慢慢来…
代码中有大量注释, 同学们细品. 下面只重点提几个点:
(1) 第 30 ~ 47 行:
对使用到的第 3 方工具进行初始化和必要的配置.
其中, 第 37 行解释一下: 当文件上传到服务器端时会将文件数据暂存于内存中, 但如果上传的文件比较大, 可能会上传很长时间, 这样一直将已上传的文件数据放在内存中并不妥, 因此设置了一个临时存储位置, 当读取到的文件数据超过了我们设定的阀值就写入磁盘.
(2) 第 49 ~ 59 行:
第 50 行拼接了一个服务器端文件的保存路径. 这里我们拟将上传的文件保存在项目根目录的子文件夹 ( portrait ) 中. 这样做只是为了便于后面演示如何在前端展示上传成功的文件. 实际的项目中, 不建议将客户上传的文件直接放在前端可以直接访问到的位置, 这样会可能会成为一个坏人攻击的入口. 更好的做法是把客户上传的文件放在诸如 WEB-INF 的下级目录中, 这样前端无法直接访问, 你可以在需要把文件从服务器端传给前端时, 进行鉴权后, 将文件数据从服务器端磁盘读取后输出给前端. ( 呵呵, 看不懂, 是吧~ 暂时别管它, 长大后你就明白了… )
第 53 ~ 59 行, 判断用来存储文件的文件夹是否存在, 若不存在则创建它.
若有大量文件会上传到服务器端, 建议按月/日, 或其它的方式把文件归类存放到不同的文件夹. 而不是像本例一样堆在一个文件夹下.
(3) 第 61 ~ 98 行:
重点来啦 !
这一大段即是处理上传的文件及其它表单字段数据的核心部分, 使用一个 try … catch … 的结构包住, 意思是如果中间出问题了, 那就抛出异常.
第 63 行, 使用第 3 方工具解析出 HTTP 数据包中的内容. 注意, 解析出的东西是一个 List
, 也就是说其中可能包含了多个表单字段的值, 以及多个文件的数据 ( 支持多文件上传 ). 所以, 在第 64 行判定 List 不为空之后, 第 66 行开始的 for 循环开始对每一个表单字段/文件进行依次处理.
刚才说了, 数据包中可能有表单字段值, 也可能包含了文件数据, 所以, 第 67 行的 if … else … 语句进行了分情况处理. 第 67 ~ 69 行处理普通的表单字段, 这很简单, 表单字段嘛, 就一个参数名 和 一个值而已. 看最终的控制台输出就明白什么意思了.
无论前端填写的是什么类型 ( String / int / float / bool / … ) 的值, 来到服务器端就都成了 String . 若有必要, 须自己写代码转换, 如: Integer age = Integer.parseInt( strAage );
第 76 ~ 77 行, 使用 UUID + 客户端文件扩展名 的方式拼接出一个新的文件名, 用来命名上传到服务器端的文件.
所谓 UUID
是36个字符组成的一个字符串, 理论上来说, 不会重复. 这里我们使用 Java 自带的工具来生成.
因为我们无法预见客户上传的文件是否会重名, 也就是说, 如果一个客户或多个客户上传了相同名称的文件, 这样把文件保存到服务器端的同一个文件夹下就会导致 要么相互覆盖, 要么保存不成功. 所以, 我们这里使用 UUID 作为文件名, 而扩展名部分取客户端文件的扩展名.
拼接出来大概是这样子: 2f111142-09e5-42f2-851b-dcb369de082d.jpeg
第 80 行拼接出了文件的完整路径 ( 包含存储路径和文件名 ), 然后在 83 行写入磁盘.
第 85 ~ 91行看文字就知道是什么意思了 ( 不明白的话, 看后台的截图 ).
注意: 在实际的项目中, 85 ~ 91 输出的这些信息可能需要存储到数据库中, 以便未来客户需要下载 ( 或向前端呈现 ) 的时候你知道文件放在哪里, 原本的文件名叫什么…
最后, 第 95 行给前端一个反馈.
以下是测试时服务器端控制台的输出:
因为我测试使用的是 MacOS, 所以那个文件的物理存储路径看上去有点 “诡异”, 在 Windows 下, 它应该就是类似 C:\xxx\xxx\xxx.jpg 的样子
3.2 使用 Node.js 实现
本节我们使用 Node.js 来实现服务器端, 配合第 2 节的前端代码, 实现文件上传. ( 功能与 3.1 节相同 )
Node.js 环境的搭建与简单的 Web 应用程序开发参看: 使用 Node.js 开发 Web 应用程序
Node.js 本身是一个 JavaScript 的运行时环境 ( Runtime Environment ), 具体功能的实现往往需要借助第 3 方库.
本例我们使用大家常用的 Express 构建 Web 服务器端, 文件上传接口使用 multer 实现.
OK, 开始吧~
打开控制台窗口… 找一个你喜欢的位置, 执行如下命令:
1 | $ mkdir upload_example |
第 1 行, 创建文件夹 upload_example 作为项目文件夹
第 2 行, 进入 upload_example 文件夹
第 3 行, 初始化项目. 其间会问你一些问题, 直接暂时一路回车到底即可.
第 4 行, 安装 express 和 multer, 并将安装配置保存于 package.json
OK, 构建好了项目框架, 下面接着在 upload_example 文件夹中创建程序入口文件: index.js , 代码如下:
Code 3.2: index.js
1 | const express = require('express'); |
简单解释一下…
第 9 ~ 30 行, 对 multer 进行配置, 包含 2 个方面:
- 第 11 ~ 13 行: 文件的存储位置. 这里统一将文件上传到 upload_example/portrait
- 第 16 ~ 28 行: 文件在服务器端的名称. 这里我们使用 “当前时间 + 随机数” 的方式构成文件在服务器端的存储名称, 以避免同名文件冲突. 关注第 20 ~ 24 行在控制台输出的内容, 这些信息可能你需要存储到数据库中.
第 34 ~ 37 行, 监听前端文件上传请求, 注意第 34 行 app.post() 方法的第 1 个参数 ( /upload ) 应与前端代码中的配置一致, 而第 2 个参数 ( portrait ) 是前端文件字段名, 同样应与前端代码中的配置一致.
本例为单个文件上传的示例, 若需要支持多文件上传也并不复杂, 请参看 multer 文档.
最后, 我们来测试一下吧~
(1) 把 2.1 或 2.2 节的前端代码复制到 upload_example/public/ 下
(2) 控制台窗口中, upload_example 文件夹下, 执行 node index.js
启动程序
(3) 打开浏览器, http://localhost:8080/, 快试试吧~
App , 微信小程序 … 自己测试吧!
4. 小结
本教程展示了 4 种客户端实现方式, 2 种服务器端实现方式.
希望你已经体会到了一点: 客户端与服务器端是可以完全”分离”的, 它们之间通过通信协议 ( 如: HTTP / HTTPS ) 联系在一起, 只要双方相互配合, “接口”对接上了就能正常工作. 今后若使用其它技术, 依葫芦画瓢即可.
现在, 建议再回头看下 1.2 节.
Revised on 2020/05/27 03:54:08 by Bailey
-
Next Post文件上传怎么做 (上)
-
Previous Post使用 Node.js 开发 Web 应用程序