1. 程式人生 > 實用技巧 >springboot專案實現圖片和檔案上傳,上傳之後在前端頁面進行展示以及下載(Linux+Windows)

springboot專案實現圖片和檔案上傳,上傳之後在前端頁面進行展示以及下載(Linux+Windows)

介紹:本次案例使用springboot專案實現了檔案的上傳及下載,並且在windows環境和Linux環境下都適用。

一、功能預覽:

二、上傳功能實現:

2.1、先建立一個表,用來儲存上傳檔案的一些基本資訊。

2.2、建立一個springboot專案,並且匯入下列依賴:

<dependencies>
    <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.projectlombok</groupId>
        <artifactId>lombok</artifactId>
        <optional>true</optional>
    </dependency>
    <dependency>
        <groupId>mysql</groupId>
        <artifactId>mysql-connector-java</artifactId>
    </dependency>
    <dependency>
        <groupId>org.mybatis.spring.boot</groupId>
        <artifactId>mybatis-spring-boot-starter</artifactId>
        <version>2.1.1</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-test</artifactId>
        <scope>test</scope>
        <exclusions>
            <exclusion>
                <groupId>org.junit.vintage</groupId>
                <artifactId>junit-vintage-engine</artifactId>
            </exclusion>
        </exclusions>
    </dependency>
</dependencies>

2.3、修改yml檔案:

建立三個yml檔案:

application-dev.yml:該配置檔案是在windows環境下使用的

spring:
  application:
    name: file_upload_download
  servlet:
    multipart:
      #上傳最大檔案大小。值可以使用字尾“MB”或“KB”。指示兆位元組或千位元組大小。
      max-file-size: 100MB
      #最大請求大小可以是mb也可以是kb
      max-request-size: 100MB
  mvc:
    static-path-pattern: /**
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8099

application-prod.yml:該配置檔案是在Linux環境下使用的

spring:
  application:
    name: file_upload_download
  servlet:
    multipart:
      max-file-size: 100MB
      max-request-size: 100MB
  mvc:
    static-path-pattern: /**
  datasource:
    url: jdbc:mysql://localhost:3306/test?useUnicode=true&characterEncoding=utf8&allowMultiQueries=true&serverTimezone=GMT%2B8
    username: root
    password: 123456
    driver-class-name: com.mysql.cj.jdbc.Driver
server:
  port: 8099

使用那個配置檔案直接在application.yml中進行切換即可:

spring:
  profiles:
    active: prod #dev

2.4、實體類:

package com.xct.file_upload_uownload.entity;

import lombok.Data;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-11-19 11:39
 */
@Data
public class FileInformation {

    private Integer id;

    private String title;

    private String uploadDate;

    private String imageName;

    private String fileName;
}

2.5、dao層:mapper

建立一個FileMapper,提供將檔案資訊新增到資料庫,以及查詢資料庫中所有檔案資訊方法。

package com.xct.file_upload_uownload.dao;

import com.xct.file_upload_uownload.entity.FileInformation;
import org.apache.ibatis.annotations.Insert;
import org.apache.ibatis.annotations.Mapper;
import org.apache.ibatis.annotations.Param;
import org.apache.ibatis.annotations.Select;

import java.util.List;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-11-19 11:42
 */
@Mapper
public interface FileMapper {

    /**
     * 新增
     * @author xct
     * @date 2020-11-20 16:17
     * @param title
     * @param uploadDate
     * @param imageName
     * @param fileName
     * @return int
     */
    @Insert("INSERT INTO file_information (title,upload_date,image_name,file_name) VALUES(#{title},#{upload_date},#{image_name},#{file_name})")
    public int insert(@Param("title")String title,@Param("upload_date")String uploadDate,@Param("image_name")String imageName,@Param("file_name")String fileName);

    //查詢
    @Select("SELECT id,title,upload_date uploadDate,image_name imageName,file_name fileName from file_information")
    public List<FileInformation> findAllFile();
}

2.6、服務層:上傳實現

建立一個FileService介面,FileServiceImpl實現FileService介面,提供檔案上傳實現和查詢所有檔案方法。

package com.xct.file_upload_uownload.service.impl;

import com.xct.file_upload_uownload.dao.FileMapper;
import com.xct.file_upload_uownload.entity.FileInformation;
import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ResourceUtils;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.List;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:30
 */
@Service
public class FileServiceImpl implements FileService {

    private static final Logger LOGGER= LoggerFactory.getLogger(FileService.class);

    @Autowired
    FileMapper fileMapper;


    /**
     * 檔案上傳的實現方法
     * @author xct
     * @date 2020-11-20 16:41
     * @param file
     * @param image
     * @param title
     * @return java.lang.String
     */
    @Override
    public String uploadFile(MultipartFile file, MultipartFile image,String title) throws Exception {
        String os = System.getProperty("os.name");
        File imagePath;  //封面圖片存放地址
        File fileRealPath;   //檔案存放地址
        if (os.toLowerCase().startsWith("win")) {  //windows系統
            String path = System.getProperty("user.dir");  //獲取專案相對路徑
            fileRealPath = new File(path+"/src//main/resources/file");
            imagePath = new File(path+"/src//main/resources/static/images");
        }else{//linux系統
            //獲取根目錄
            //如果是在本地windows環境下,目錄為專案的target\classes下
            //如果是linux環境下,目錄為jar包同級目錄
            File rootPath = new File(ResourceUtils.getURL("classpath:").getPath());
            if(!rootPath.exists()){
                rootPath = new File("");
            }
            fileRealPath = new File(rootPath.getAbsolutePath()+"/file/");
            imagePath = new File(rootPath.getAbsolutePath()+"/images");
        }
        //判斷資料夾是否存在
        if(!fileRealPath.exists()){
            //不存在,建立
            fileRealPath.mkdirs();
        }
        if(!imagePath.exists()){
            //不存在,建立
            imagePath.mkdirs();
        }
        //獲取檔名稱
        String fileName = file.getOriginalFilename();
        String imageName = image.getOriginalFilename();
        //建立檔案存放地址
        File resultPath = new File(fileRealPath+"/"+fileName);
        if (resultPath.exists()){
            LOGGER.warn("檔案已經存在!");
            return "false!";
        }
        //建立圖片存放地址
        File imageResultPath = new File(imagePath+"/"+imageName);
        if(imageResultPath.exists()){
            LOGGER.warn("圖片已經存在!");
            return "false!";
        }
        SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd");
        file.transferTo(resultPath);
        image.transferTo(imageResultPath);
        fileMapper.insert(title, sdf.format(new Date()), imageName, fileName);
        System.out.println("absolutePath:"+fileRealPath.getCanonicalPath());
        System.out.println("resultPath:"+resultPath.getCanonicalPath());
        System.out.println("imageResultPath:"+imageResultPath.getCanonicalPath());
        return "true!";
    }

    /**
     * 查詢資料庫中所有檔案資訊的方法
     * @author xct
     * @date 2020-11-20 16:42
     * @param
     * @return java.util.List<com.xct.file_upload_uownload.entity.FileInformation>
     */
    @Override
    public List<FileInformation> getAllFile() {
        return fileMapper.findAllFile();
    }
}

2.7、控制器:

建立一個FileController,提供檔案上傳的控制器和一個查詢所有檔案後跳轉前端頁面的控制器。

package com.xct.file_upload_uownload.controller;

import com.xct.file_upload_uownload.entity.FileInformation;
import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.util.List;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:55
 */
@Controller
public class FileController {
    private static final Logger LOGGER= LoggerFactory.getLogger(FileController.class);

    @Autowired
    private FileService fileService;

    /**
     * 檔案上傳
     * @author xct
     * @date 2020-11-20 16:50
     * @param file    檔案
     * @param fileImage  用於做封面的圖片
     * @param title   標題
     * @return java.lang.String
     */
    @PostMapping("/uploadFile")
    public String uploadFile(@RequestParam("file")MultipartFile file,@RequestParam("fileImage")MultipartFile fileImage,@RequestParam("title")String title){
        if(file.isEmpty()){
            LOGGER.error("上傳失敗,請選擇檔案!");
            return "redirect:/getAllFile";
        }
        try {
            String result = fileService.uploadFile(file,fileImage,title);
            LOGGER.info(result);
            return "redirect:/getAllFile";
        } catch (Exception e) {
            e.printStackTrace();
            LOGGER.error("檔案上傳失敗!");
            return "redirect:/getAllFile";
        }
    }

    /**
     * 查詢所有的檔案資訊
     * @author xct
     * @date 2020-11-19 16:28
     * @param
     * @return java.lang.String
     */
    @RequestMapping("/getAllFile")
    public String getAllFile(HttpServletRequest request){
        List<FileInformation> allFile = fileService.getAllFile();
        request.setAttribute("fileList", allFile);
        return "fileDownload";
    }
}

2.8、前端頁面:

建立一個前端頁面,使用表單將使用者輸入的檔案資訊傳輸到controller。

<!DOCTYPE html>
<html lang="en" xmlns:th="https://www.thymeleaf.org">
<head>
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">
    <!-- 上述3個meta標籤*必須*放在最前面,任何其他內容都*必須*跟隨其後! -->
    <title>Bootstrap 101 Template</title>

    <!-- Bootstrap -->
    <link href="https://cdn.jsdelivr.net/npm/[email protected]/dist/css/bootstrap.min.css" rel="stylesheet">

    <style>
        html,body{
            height: 100%;
            width: 100%;
            margin: 0 auto;
        }
        .titleClass{
            margin-top:8px;
            color: white;
            font-weight: bolder;
        }
        .timeClass{
            margin-top: 25px;
            margin-bottom: 10px;
            color: grey;
            font-size: 14px;
        }
        .contentTd{
            padding-left: 10px;
            padding-right: 10px;
            width: 150px!important;
            height: 150px;
        }
        tr{
            margin-top: 10px;
            margin-bottom: 60px;
            display: block;
        }
        .buttonP{
            padding-top: 20px;
        }
        .imageTd{
            width: 267px!important;
            height: 150px;
        }
        .imageTd img{
            width: 100%;
            height: 100%;
        }
    </style>
</head>
<body>
<!-- jQuery (Bootstrap 的所有 JavaScript 外掛都依賴 jQuery,所以必須放在前邊) -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/jquery.min.js"></script>
<!-- 載入 Bootstrap 的所有 JavaScript 外掛。你也可以根據需要只加載單個外掛。 -->
<script src="https://cdn.jsdelivr.net/npm/[email protected]/dist/js/bootstrap.min.js"></script>
<div style="width: 100%;height: 100%;background-color: #0B656D">
    <table align="center" style="width: 85%;">
        <th:block th:each="usr,status:${fileList}">
            <p th:remove="tag" th:utext="${(status.index+1)%3==1 ? '&lt;tr&gt;':''}"/>
            <td class="imageTd">
                <img th:src="@{/images/{imageName}(imageName=${usr.imageName})}">
            </td>
            <td class="contentTd">
                <p class="titleClass"><span th:text="${usr.title}"></span></p>
                <p class="timeClass"><span th:text="${usr.uploadDate}"></span></p>
                <p class="buttonP">
                    <!--<a href="/download/2018年度中國城市活力研究報告.pdf" download>-->
                    <a th:href="@{/download/{fileName}(fileName=${usr.fileName})}" download>
                        <button type="button" class="btn btn-primary">下載</button>
                    </a>
                </p>
            </td>
            <p th:remove="tag" th:utext="${(status.index+1)%5==0 ? '&lt;/tr&gt;':''}"/>
        </th:block>
        <tr>
            <td>
                <button type="button" class="btn btn-primary" data-toggle="modal" data-target="#myModal">
                上傳
                </button>
            </td>
        </tr>
    </table>
    <div class="modal fade" id="myModal" tabindex="-1" role="dialog" aria-labelledby="myModalLabel">
        <div class="modal-dialog" role="document">
            <div class="modal-content">
                <div class="modal-header">
                    <button type="button" class="close" data-dismiss="modal" aria-label="Close"><span aria-hidden="true">&times;</span></button>
                    <h4 class="modal-title" id="myModalLabel">檔案上傳</h4>
                </div>
                <div class="modal-body">
                    <form enctype="multipart/form-data" method="post" action="/uploadFile">
                        <div class="form-group">
                            <label for="exampleInputEmail1">檔案標題</label>
                            <input type="text" class="form-control" id="exampleInputEmail1" placeholder="檔案標題" name="title">
                        </div>
                        <div class="form-group">
                            <label for="exampleInputFile">檔案</label>
                            <input type="file" id="exampleInputFile" name="file">
                            <p class="help-block">上傳檔案</p>
                        </div>
                        <div class="form-group">
                            <label for="exampleInputFile">檔案封面</label>
                            <input type="file" id="fileImage" name="fileImage">
                            <p class="help-block">上傳檔案封面</p>
                        </div>
                        <div class="modal-footer">
                            <button type="button" class="btn btn-default" data-dismiss="modal">關閉</button>
                            <button type="submit" class="btn btn-primary">提交</button>
                        </div>
                    </form>
                </div>

            </div>
        </div>
    </div>
</div>
</body>
</html>

我這裡使用了Bootstrp的模態框作為表單彈出,以及做了一點樣式。

這段程式碼是當一行(<tr>)裡面有三個單元格<td>之後就會新增一個<tr>,也就是下圖的效果:

參考部落格:https://blog.csdn.net/iteye_19045/article/details/97809707

2.9、配置虛擬路徑對映:

其實到這裡,上傳就基本已經實現了,但是會發現上傳成功之後,圖片不會馬上在頁面上顯示出來,必須重啟專案之後才會顯示,這是因為對伺服器的保護措施導致的,伺服器不能對外部暴露真實的資源路徑,需要配置虛擬路徑對映訪問。

建立一個ResourceConfigAdapter:

package com.xct.file_upload_uownload.config;

import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.ResourceHandlerRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurerAdapter;
/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-11-20 11:03
 */
@Configuration
public class ResourceConfigAdapter extends WebMvcConfigurerAdapter {
    @Override
    public void addResourceHandlers(ResourceHandlerRegistry registry) {
        //在windows環境下的圖片存放資源路徑
        String winPath = System.getProperty("user.dir")+"\\src\\main\\resources\\static\\images\\";
        //在Linux環境下的圖片存放資源路徑
        String linuxPath = "/usr/local/my_project/images/";
        String os = System.getProperty("os.name");
        if (os.toLowerCase().startsWith("win")) {  //windows系統
            //第一個方法設定訪問路徑字首,第二個方法設定資源路徑
            registry.addResourceHandler("/images/**").
                    addResourceLocations("file:"+winPath);
        }else{//linux系統
            registry.addResourceHandler("/images/**").
                    addResourceLocations("file:"+linuxPath);
        }
        super.addResourceHandlers(registry);
    }
}

addResourceHandler()裡配置需要對映的資料夾,此處代表對映資料夾images下的所有資源。

addResourceLocations()配置資源在系統中的路徑,使用絕對路徑,格式為“file:你的路徑”

到此,檔案上傳的功能就已經完成了。

三、下載功能實現:

參考自:https://www.bilibili.com/read/cv5604214/

修改yml檔案

在application-dev.yml新增如下配置:

file:
  doc-dir: src/main/resources/file/

在application-prod.yml新增:

file:
  doc-dir: file/

該配置就是待下載檔案存放在伺服器上的目錄,為相對路徑,dev配置檔案中表示為當前專案的src/main/resources/file/下:

prod配置檔案則表示與當前專案(jar包)同級:

將屬性與 pojo 類自動繫結

springboot 中的註解 @ConfigurationProperties 可以將 application 中定義的屬性與 pojo 類自動繫結。所以,我們需要定義一個 pojo 類來做 application 中 file.doc-dir=doc/ 的配置繫結:

package com.xct.file_upload_uownload.entity;

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

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:33
 */
@ConfigurationProperties(prefix = "file")
@Data
public class FileProperties {
    private String docDir;
}

註解 @ConfigurationProperties(prefix = "file") 在 springboot 應用啟動時將 file 為字首的屬性與 pojo 類繫結,也就是將 application.yml 中的file.doc-dir與 FileProperties 中的欄位 docDir 做了繫結。

啟用配置屬性

在啟動類或其他配置類(@Configuration註解標記)上加入 @EnableConfigurationProperties 即可讓 ConfigurationProperties 特性生效。

package com.xct.file_upload_uownload;

import com.xct.file_upload_uownload.entity.FileProperties;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.boot.context.properties.EnableConfigurationProperties;

@SpringBootApplication
@EnableConfigurationProperties({FileProperties.class})
public class FileUploadUownloadApplication {
    public static void main(String[] args) {
        SpringApplication.run(FileUploadUownloadApplication.class, args);
    }
}

自定義異常

在服務層,我們丟擲自定義的檔案異常 FileException.

package com.xct.file_upload_uownload.exception;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:51
 */
public class FileException extends RuntimeException {
    public FileException(String message) {
        super(message);
    }
    public FileException(String message, Throwable cause) {
        super(message, cause);
    }
}

服務層**

服務層的主要工作是把檔案作為 IO 資源載入。注意,在 Service 實現類的構造方法中要使用 @Autowired 注入前面定義好的屬性繫結類 FileProperties.

package com.xct.file_upload_uownload.service.impl;

import com.xct.file_upload_uownload.entity.FileProperties;
import com.xct.file_upload_uownload.exception.FileException;
import com.xct.file_upload_uownload.service.FileService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.Resource;
import org.springframework.core.io.UrlResource;
import org.springframework.stereotype.Service;
import java.net.MalformedURLException;
import java.nio.file.Path;
import java.nio.file.Paths;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:30
 */
@Service
public class FileServiceImpl implements FileService {

    private final Path filePath;

    @Autowired
    public FileServiceImpl(FileProperties fileProperties) {
        filePath = Paths.get(fileProperties.getDocDir()).toAbsolutePath().normalize();
    }


    @Override
    public Resource loadFileAsResource(String fileName){
        Path path = filePath.resolve(fileName).normalize();
        System.out.println(path+"---------------------------------------------------");
        try {
            UrlResource resource = new UrlResource(path.toUri());
            if (resource.exists()) {
                return resource;
            }
            throw new FileException("file " + fileName + " not found");
        } catch (MalformedURLException e) {
            throw new FileException("file " + fileName + " not found", e);
        }
    }

}

控制層**

在控制層我們將以 spring 框架的 ResponseEntity 類作為返回值傳給前端,其中泛型為 spring io 包的 Resource 類,這意味著返回內容為 io 流資源;並在返回體頭部新增附件,以便於前端頁面下載檔案。

package com.xct.file_upload_uownload.controller;

import com.xct.file_upload_uownload.service.FileService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.system.ApplicationHome;
import org.springframework.core.io.Resource;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.util.ResourceUtils;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;
import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.nio.file.Path;

/**
 * @author Xct1194542884
 * @title: xct
 * @projectName file_upload_uownload
 * @description: TODO
 * @date 2020-10-30 11:55
 */
@Controller
public class FileController {
    private static final Logger LOGGER= LoggerFactory.getLogger(FileController.class);

    @Autowired
    private FileService fileService;

    @RequestMapping("/downloading")
    public String downloading(){
        return "fileDownload";
    }


    /**
     * 下載
     * @author xct
     * @date 2020-10-30 17:26
     * @param fileName
     * @param request
     * @return org.springframework.http.ResponseEntity<org.springframework.core.io.Resource>
     */
    @GetMapping("/download/{fileName}")
    @ResponseBody
    public ResponseEntity<Resource> downloadFile(@PathVariable String fileName, HttpServletRequest request) throws UnsupportedEncodingException {
        System.out.println("====================================================");
        Resource resource = fileService.loadFileAsResource(fileName);
        String contentType = null;
        try {
            System.out.println("=================================="+resource.getFile().getAbsolutePath());
            contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
        } catch (IOException e) {
            LOGGER.error("無法獲取檔案型別", e);
        }
        if (contentType == null) {
            contentType = "application/octet-stream";
        }
        return ResponseEntity.ok()
                .contentType(MediaType.parseMediaType(contentType))
                .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new String(resource.getFilename().getBytes("UTF-8"),"iso-8859-1")  + "\"")
                .body(resource);
    }
}

前端

和上傳使用一個html頁面即可。

    String contentType = null;
    try {
        System.out.println("=================================="+resource.getFile().getAbsolutePath());
        contentType = request.getServletContext().getMimeType(resource.getFile().getAbsolutePath());
    } catch (IOException e) {
        LOGGER.error("無法獲取檔案型別", e);
    }
    if (contentType == null) {
        contentType = "application/octet-stream";
    }
    return ResponseEntity.ok()
            .contentType(MediaType.parseMediaType(contentType))
            .header(HttpHeaders.CONTENT_DISPOSITION, "attachment; filename=\"" + new String(resource.getFilename().getBytes("UTF-8"),"iso-8859-1")  + "\"")
            .body(resource);
}

}


#### **前端**

和上傳使用一個html頁面即可。

到此一個簡單的檔案上傳與下載的功能就實現了。