一、File类
java.io.File是 Java 标准库中用于操作文件和目录路径的类。它提供了很多方法,用于创建、删除、重命名、判断文件是否存在、获取文件信息等操作。
获取文件信息
-
boolean exists()
: 判断文件或目录是否存在。 -
boolean isFile()
: 判断是否是文件。 -
boolean isDirectory()
: 判断是否是目录。 -
String getName()
: 获取文件或目录的名称。 -
String getPath()
: 获取文件或目录的路径。 -
String getAbsolutePath()
: 获取文件或目录的绝对路径。 -
long length()
: 获取文件的大小(字节数)。
文件和目录操作
-
boolean createNewFile()
: 创建新文件。如果文件已存在,则不创建,返回 false。 -
boolean mkdir()
: 创建新目录。如果目录已存在,则不创建,返回 false。 -
boolean mkdirs()
: 创建新目录及其父目录,如果不存在的话。 -
boolean delete()
: 删除文件或目录。
文件路径操作
-
boolean renameTo(File dest)
: 重命名文件或目录。如果成功,返回 true;否则,返回 false。 -
String[] list()
: 返回目录下的文件和目录名数组。 -
File[] listFiles()
: 返回目录下的文件和目录的 File 对象数组。
文件过滤
-
String[] list(FilenameFilter filter)
: 返回目录下满足指定过滤器条件的文件和目录名数组。 -
File[] listFiles(FileFilter filter)
: 返回目录下满足指定过滤器条件的文件和目录的 File 对象数组。
二、MultipartFile接口
MultipartFile是 Spring 框架提供的一个接口,用于表示处理文件上传的对象。
它通常用于处理multipart/form-data
类型的请求,例如处理文件上传的表单。
首先我们依旧可以通过源码的学习来进一步了解这个接口。
2.1 源码和方法功能
public interface MultipartFile extends InputStreamSource { String getName(); @Nullable String getOriginalFilename(); @Nullable String getContentType(); boolean isEmpty(); long getSize(); byte[] getBytes() throws IOException; InputStream getInputStream() throws IOException; default Resource getResource() { return new MultipartFileResource(this); } void transferTo(File dest) throws IOException, IllegalStateException; default void transferTo(Path dest) throws IOException, IllegalStateException { FileCopyUtils.copy(this.getInputStream(), Files.newOutputStream(dest)); } }
-
String getName()
:获取上传文件的表单字段名称 -
String getOriginalFilename()
:获取上传文件的原始文件名 -
String getContentType()
:获取上传文件的内容类型 -
boolean isEmpty()
:判断上传文件是否为空 -
long getSize()
:获取上传文件的大小,单位是字节 -
byte[] getBytes() throws IOException
:获取上传文件的字节数组表示 -
InputStream getInputStream() throws IOException
:获取上传文件的输入流 -
default Resource getResource()
:将 MultipartFile 封装成了 Resource 对象,从而可以使用 Resource 接口提供的方法来操作上传文件的内容。 -
void transferTo(File dest) throws IOException
,IllegalStateException
:将上传文件保存到指定的文件; -
default void transferTo(Path dest) throws IOException
,IllegalStateException
:将上传文件保存在指定的路径下;
2.2 void transferTo(File dest)
前面我们已经介绍了该方法是Spring中提供的将上传文件保存到指定的文件中的抽象方法,溯源源码我们可以看到这个接口方法被三个实现类实现了,分别是CommonsMultipartFile、MockMultipartFile 和 StandardMultipartHttpServletRequest。
CommonsMultipartFile中的方法体
我们可以看到CommonsMultipartFile中的方法体主要是通过检测传进来的文件是否可用、是否存在,并在检测完成就执行写入的操作
public void transferTo(File dest) throws IOException, IllegalStateException { if (!this.isAvailable()) { throw new IllegalStateException("File has already been moved - cannot be transferred again"); } else if (dest.exists() && !dest.delete()) { throw new IOException("Destination file [" + dest.getAbsolutePath() + "] already exists and could not be deleted"); } else { try { this.fileItem.write(dest); LogFormatUtils.traceDebug(logger, (traceOn) -> { String action = "transferred"; if (!this.fileItem.isInMemory()) { action = this.isAvailable() ? "copied" : "moved"; } return "Part '" + this.getName() + "', filename '" + this.getOriginalFilename() + "'" + (traceOn ? ", stored " + this.getStorageDescription() : "") + ": " + action + " to [" + dest.getAbsolutePath() + "]"; }); } catch (FileUploadException var3) { throw new IllegalStateException(var3.getMessage(), var3); } catch (IOException | IllegalStateException var4) { throw var4; } catch (Exception var5) { throw new IOException("File transfer failed", var5); } } }
上面这段demo中可能对于this.isAvailable()有疑问,我们知晓这里的this其实是该类的实例化对象,但是这里的this.isAvailable()就是拿来判断目的文件是否可用,调用的就是类的内部方法,判断是否可用的条件就是该目标文件是否被加载进内存中!
这里给出一个使用该方法的例子,需要注意的是这时候方法要求的是一个目标File对象,我们需要在调用该目标方法的时候就根据目标路径创建了目标的File对象。
// 获取上传文件的原始文件名 String originalFilename = StringUtils.cleanPath(file.getOriginalFilename()); // 构建目标文件对象 File destFile = new File("/path/to/destination/directory", originalFilename); try { // 将上传文件保存到目标文件 file.transferTo(destFile); return "File uploaded successfully!"; } catch (IOException e) { e.printStackTrace(); return "Failed to upload the file."; }
StandardMultipartHttpServletRequest实现类
而另一个实现类StandardMultipartHttpServletRequest和CommonsMultipartFile的区别就在于使用StandardMultipartHttpServletRequest直接上传文件的话可能会出现目录跳跃的问题,而CommonsMultipartFile不会,这是因为其对路劲分隔符了相关的限制。
default void transferTo(Path dest)
该默认方法在实现类中被重写了,但主要的功能还是不变,就是将上传的文件写入到指定路径的Path对象中实现文件上传的功能。
这里给出使用的示例代码:
// 构建目标文件路径 String uploadDirectory = "/path/to/destination/directory"; String originalFilename = file.getOriginalFilename(); Path filePath = Paths.get(uploadDirectory, originalFilename); try { // 将上传文件保存到目标文件 file.transferTo(filePath); return "File uploaded successfully!"; } catch (IOException e) { e.printStackTrace(); return "Failed to upload the file."; }
实际项目使用文件上传和下载
@PostMapping("/file/upload") public Result uploadFile(MultipartFile file) { String originalFilename = file.getOriginalFilename(); if (StrUtil.isBlank(originalFilename)) { return Result.error("文件上传失败"); } long flag = System.currentTimeMillis(); String filePath = BASE_FILE_PATH + flag + "_" + originalFilename; try { FileUtil.mkParentDirs(filePath); // 创建父级目录 file.transferTo(FileUtil.file(filePath)); Admin currentAdmin = TokenUtils.getCurrentAdmin(); String token = TokenUtils.genToken(currentAdmin.getId().toString(), currentAdmin.getPassword(), 15); String url = "http://localhost:9090/api/book/file/download/" + flag + "?&token=" + token; if (originalFilename.endsWith("png") || originalFilename.endsWith("jpg") || originalFilename.endsWith("pdf")) { url += "&play=1"; } return Result.success(url); } catch (Exception e) { log.info("文件上传失败", e); } return Result.error("文件上传失败"); } @GetMapping("/file/download/{flag}") public void download(@PathVariable String flag, @RequestParam(required = false) String play, HttpServletResponse response) { OutputStream os; List fileNames = FileUtil.listFileNames(BASE_FILE_PATH); String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(""); // System.currentTimeMillis() + originalFilename try { if (StrUtil.isNotEmpty(fileName)) { String realName = fileName.substring(fileName.indexOf("_") + 1); if ("1".equals(play)) { response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(realName, "UTF-8")); } else { response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realName, "UTF-8")); } byte[] bytes = FileUtil.readBytes(BASE_FILE_PATH + fileName); os = response.getOutputStream(); os.write(bytes); os.flush(); os.close(); } } catch (Exception e) { log.error("文件下载失败", e); } }
文件上传接口
1.接口设置
- @postMapping(“file/upload”):这个注解表明这个方法处理HTTP POST请求,请求路径是/file/upload。
- Public Result uploadFile(Multipartfile file):Result是一个自定义的响应对象,包含了操作成功或操作失败的状态,以及相关数据。
2.文件处理
- String originalFilename = file.getOriginalFilename():获取上传文件的原始文件名。
- if (StrUtil.isBlank(originalFilename)):检查文件名是否为空,如果为空,返回错误响应(Result.error)。
- long flag = System.currentTimeMillis():获取当前时间戳(毫秒),用于创建唯一的文件名。
- String filePath = BASE_FILE_PATH + flag + “_” + originalFilename:构建完整的文件保存路径。BASE_FILE_PATH指存储文件的根目录,文件名由时间戳和原始文件名拼接而成。
3.文件保存
- FileUtil.mkParentDirs(filePath):确保所有父目录都存在,如果不存在,则创建它们。
- file.transferTo(FileUtil.file(filePath)):这行关键代码将上传的文件内容保存到服务器上的filePath路径。
4.令牌生成(可选)
- Admin currentAdmin = TokenUtils.getCurrentAdmin():获取当前登录的管理员用户,这意味着实现了身份验证。
- String token = TokenUtils.genToken(currentAdmin.getId().toString(), currentAdmin.getPassword(), 15):根据管理员ID、密码和15分钟的过期时间生成一个jwt令牌。
5.URL构建
- String url = “http://localhost:9090/api/book/file/download/” + flag + “?&token=” + token:构建一个指向下载接口(/api/book/file/download/)的URL,包含时间戳(flag)和生成的令牌。
- if(originalFilename.endsWith(“png”)||originalFilename.endsWith(“jpg”) || originalFilename.endsWith(“pdf”)):如果是图片(png、jpg)或pdf文件,则在URL后面添加&play=1,这表示服务器可能提供文件预览功能。
6.响应
- return Result.success(url):上传成功后,返回包含下载的Result.success(url)响应
- catch (Exception e):处理过程中可能出现的异常,记录错误日志并返回一个通用的Result.error响应。
文件下载接口
1.注解
- (1)@GetMapping(“/file/download/{flag}”):使用GetingMapping注解处理Get请求,路径包含一个变量{flag},将被捕获并传递给方法。
- (2)@PathVariable String flag:此注解将{flag}路径变量的值绑定到方法的flag参数。
- (3)@RequestParam(required = false) String play:此注解将可选查询参数”play”的值绑定到方法的play参数。required=false使此参数成为可选参数。
2.方法参数:
- (1)String flag:此参数将包含作为URL中{flag}传递的值。它用于标识要下载的文件。
- (2)String play:此可选参数(来自查询字符串)可用于控制下载行为。如果其值为“1”,则文件可能会直接在浏览器中打开(内联)。否则,它将被视为常规下载。
- (3)HttpServletResponse response:此对象用于操作发送回客户端的HTTP响应。
3.文件处理
- (1)List fileNames = FileUtil.listFileNames(BASE_FILE_PATH):此行假设存在一个FileUtil类,其中包含一个listFileNames方法,该方法从BASE_FILE_PATH指定的目录中检索文件名列表。
- (2)String fileName = fileNames.stream().filter(name -> name.contains(flag)).findAny().orElse(“”):此行使用Java流查找包含提供的flag的文件名。
- (3)String realName = fileName.substring(fileName.indexOf(“_”) + 1):通过删除找到的文件名中的任何前缀(第一个”_”之前)来提取实际文件名。
响应处理
if ("1".equals(play)) { response.addHeader("Content-Disposition", "inline;filename=" + URLEncoder.encode(realName, "UTF-8")); } else { response.addHeader("Content-Disposition", "attachment;filename=" + URLEncoder.encode(realName, "UTF-8")); }
检查play参数的值。如果play为“1”,则将Content-Disposition标头设置为”inline;filename=” + URLEncoder.encode(realName, “UTF-8”),这通常告诉浏览器尝试直接打开文件。否则,他会将标头设置为 “attachment;filename=” + URLEncoder.encode(realName, “UTF-8”),这通常会强制浏览器下载文件。
- URLEncoder.encode(realName, “UTF-8”)使用UTF-8编码对文件名进行编码,以正确处理文件名中的特殊字符和空格。
- byte[] bytes = FileUtil.readBytes(BASE_FILE_PATH + fileName);使用FileUtil类中的另一个假设方法将文件内容读入字节数组。
- os = response.getOutputStream():从response对象获取输出流,允许代码将数据直接写入HTTP响应体。
- os.write(bytes);os.flush(); os.close();:将文件内容(字节数组)写入输出流,刷新流以确保发送所有数据,然后关闭流。
- catch (Exception e) { log.error(“文件下载失败”, e);}:此catch块捕获在文件下载过程中抛出的任何异常,并记录错误消息。
总结
以上为个人经验,希望能给大家一个参考,也希望大家多多支持IT俱乐部。