天天看點

實作一個簡單的檔案下載下傳伺服器

作者:程式設計實踐
實作一個簡單的檔案下載下傳伺服器

前面的文章《使用SpringBoot實作靜态網站》,用SpringBoot實作了一個古詩詞網。其實,在這個伺服器的static目錄中放入其他的檔案,譬如readme.txt檔案,通過浏覽器可以直接檢視,通過迅雷,也可以直接下載下傳:

實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器

但是,把這個功能當成檔案下載下傳功能,就太粗糙了。個人認為,即使最簡單的檔案下載下傳伺服器,也至少具備下面兩個要點:

(1)通過浏覽器輸入要下載下傳的資源的URL後,浏覽器可以儲存附件;

(2)軟體工程師可以根據需要,添加控制檔案下載下傳權限的代碼。

下面,我們基于SpringBoot,使用IDEA開發工具,開發一個簡單的檔案下載下傳伺服器吧。

第1步:使用IDEA建立一個空項目fc_prj,然後在fc_prj項目中添加一個SpringBoot項目file_center,加入Lombok、Spring Web、Spring Data JPA、MySQL Driver四個依賴。操作完成後項目視圖如下:

實作一個簡單的檔案下載下傳伺服器

第2步:在mysql中執行下面的SQL語句,建立本項目使用的資料庫db_fc:

create database db_fc default character set utf8;           
實作一個簡單的檔案下載下傳伺服器

第3步:在db_fc資料庫中建立t_user表,用于進行權限控制,為了讓程式顯得簡單,這裡t_user表隻包含使用者名、密碼、備注三個字段,建立t_user表的SQL語句如下:

use db_fc;
create table t_user(
user_name varchar(32) primary key not null,
password varchar(32) not null,
comment varchar(64)
)engine=InnoDB default charset=utf8;           
實作一個簡單的檔案下載下傳伺服器

第4步:我們在t_user表中插入張三、李四、王五三個使用者的資訊,SQL語句如下:

insert into t_user(user_name, password, comment) values('zhangsan', 'zhang', '使用者張三');
insert into t_user(user_name, password, comment) values('lisa', 'li', '使用者李四');
insert into t_user(user_name, password, comment) values('wangwu', 'wang', '使用者王五');
commit;           
實作一個簡單的檔案下載下傳伺服器

第5步:在db_fc資料庫中建立t_file表,用于記錄可以下載下傳的檔案資訊,該表有檔案辨別符、檔案完整路徑兩個字段,建立t_file表的SQL語句如下:

use db_fc;
create table t_file(
file_id varchar(32) primary key not null,
file_path varchar(512) not null
)engine=InnoDB default charset=utf8;           
實作一個簡單的檔案下載下傳伺服器

第6步:我們向t_file表中插入一些可以下載下傳的檔案資訊,SQL語句如下:

insert into t_file(file_id, file_path) values('flower', '/root/files/flower.jpg');
insert into t_file(file_id, file_path) values('hello.txt', '/root/files/hello.txt');
commit;           
實作一個簡單的檔案下載下傳伺服器

第7步:建立entity包;

實作一個簡單的檔案下載下傳伺服器

第8步:在entity包中建立UserEntity類,一個UserEntity執行個體對應t_user表的一條記錄。UserEntity的代碼如下:

package com.flying.file_center.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Table(name="t_user")
@Entity
public class UserEntity {
@Id
private String userName;
private String password;
private String comment;
}           

第9步:在entity包中建立FileEntity類,一個FileEntity執行個體對應t_file表的一條記錄。FileEntity的代碼如下:

package com.flying.file_center.entity;
import lombok.Data;
import javax.persistence.Entity;
import javax.persistence.Id;
import javax.persistence.Table;
@Data
@Table(name="t_file")
@Entity
public class FileEntity {
@Id
private String fileId;
private String filePath;
}           

第10步:建立repository包;

實作一個簡單的檔案下載下傳伺服器

第11步:在repository包中建立UserRepository接口,用于操作t_user表,代碼如下:

package com.flying.file_center.repository;
import com.flying.file_center.entity.UserEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface UserRepository extends JpaRepository<UserEntity, String>, JpaSpecificationExecutor {
List<UserEntity> queryUserEntitiesByUserNameAndPassword(String userName, String password);
}           

第12步:在repository包中建立FileRepository接口,用于操作t_file表,代碼如下:

package com.flying.file_center.repository;
import com.flying.file_center.entity.FileEntity;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.data.jpa.repository.JpaSpecificationExecutor;
import org.springframework.stereotype.Repository;
import java.util.List;
@Repository
public interface FileRepository extends JpaRepository<FileEntity, String>, JpaSpecificationExecutor {
List<FileEntity> queryFileEntitiesByFileId(String fileId);
}           

第13步:建立controller包和service包:

實作一個簡單的檔案下載下傳伺服器

第14步:在service包中建立FileService類,用于實作處理HTTP下載下傳請求,代碼如下:

package com.flying.file_center.service;
import com.flying.file_center.entity.FileEntity;
import com.flying.file_center.entity.UserEntity;
import com.flying.file_center.repository.FileRepository;
import com.flying.file_center.repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpServletResponse;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.OutputStream;
import java.util.List;
@Service
public class FileService {
@Autowired
private FileRepository fileRepository;
@Autowired
private UserRepository userRepository;
public void download(String userName, String password, String fileId, HttpServletResponse httpServletResponse){
List<UserEntity> userEntityList = userRepository.queryUserEntitiesByUserNameAndPassword(userName, password);
if (userEntityList.size() == 0){
httpServletResponse.setStatus(401);
return;
}
List<FileEntity> fileEntityList = fileRepository.queryFileEntitiesByFileId(fileId);
if (fileEntityList.size() == 0){
httpServletResponse.setStatus(404);
return;
}

File file = new File(fileEntityList.get(0).getFilePath());
if (file.exists()) {
httpServletResponse.setContentType("application/force-download");
httpServletResponse.addHeader("Content-Disposition",
"attachment;fileName=" + fileEntityList.get(0).getFileId());
byte[] bytesBuffer = new byte[1024];
try (FileInputStream fis = new FileInputStream(file);
BufferedInputStream bis = new BufferedInputStream(fis);
) {
OutputStream outputStream = httpServletResponse.getOutputStream();
int i = bis.read(bytesBuffer);
while (i != -1) {
outputStream.write(bytesBuffer, 0, i);
i = bis.read(bytesBuffer);
}
} catch (Exception e) {
e.printStackTrace();
}
}else{
httpServletResponse.setStatus(404);
}
}
}           

第15步:在controller包中建立FileController類,用于接收來自浏覽器或其他用戶端的HTTP下載下傳請求,代碼如下:

package com.flying.file_center.controller;
import com.flying.file_center.service.FileService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PathVariable;
import org.springframework.web.bind.annotation.RestController;
import javax.servlet.http.HttpServletResponse;
@RestController
@Slf4j
public class FileController {
@Autowired
private HttpServletResponse httpServletResponse;

@Autowired
private FileService fileService;
@GetMapping("/download/{userName}/{password}/{fileId}")
public void download(@PathVariable(value="userName") String userName,
@PathVariable(value="password") String password,
@PathVariable(value="fileId") String fileId){
fileService.download(userName, password, fileId, httpServletResponse);
}
}           

第16步:修改application.properties檔案,加入伺服器的配置資訊,這裡隻配置伺服器使用的端口和資料庫連接配接資訊,application.properties檔案的内容如下:

server.port=9201
spring.datasource.url=jdbc:mysql://127.0.0.1/db_fc?useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true
spring.datasource.username=root
spring.datasource.password=123456
spring.datasource.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.max-idle=10
spring.datasource.max-wait=10000
spring.datasource.min-idle=5
spring.datasource.initial-size=5           

第17步:修改pom.xml檔案,加入編譯和打包的資訊,修改後的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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.3.4.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.flying</groupId>
<artifactId>file_center</artifactId>
<version>1.0.1</version>
<name>file_center</name>
<description>Demo project for Spring Boot</description>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>mysql</groupId>
<artifactId>mysql-connector-java</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</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>
<build>
<finalName>fc</finalName>
<plugins>
<!--打包jar-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<!--不打包資源檔案,exclude的目錄不是src下面的,是以編譯結果classes為根目錄計算-->
<excludes>
<exclude>*.properties</exclude>
<exclude>*.txt</exclude>
<exclude>*.xml</exclude>
</excludes>
<archive>
<manifest>
<addClasspath>true</addClasspath>
<!--MANIFEST.MF 中 Class-Path 加入字首-->
<classpathPrefix>fc_lib/</classpathPrefix>
<!--jar包不包含唯一版本辨別-->
<useUniqueVersions>false</useUniqueVersions>
<!--指定入口類-->
<mainClass>com.flying.file_center.FileCenterApplication</mainClass>
</manifest>
<manifestEntries>
<!--MANIFEST.MF 中 Class-Path 加入資源檔案目錄-->
<Class-Path>./resources/</Class-Path>
</manifestEntries>
</archive>
<outputDirectory>${project.build.directory}</outputDirectory>
</configuration>
</plugin>
<!--拷貝依賴 copy-dependencies-->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-dependency-plugin</artifactId>
<executions>
<execution>
<id>copy-dependencies</id>
<phase>package</phase>
<goals>
<goal>copy-dependencies</goal>
</goals>
<configuration>
<outputDirectory>
${project.build.directory}/fc_lib/
</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
<!--拷貝資源檔案 copy-resources-->
<plugin>
<artifactId>maven-resources-plugin</artifactId>
<executions>
<execution>
<id>copy-resources</id>
<phase>package</phase>
<goals>
<goal>copy-resources</goal>
</goals>
<configuration>
<resources>
<resource>
<directory>src/main/resources</directory>
</resource>
</resources>
<outputDirectory>${project.build.directory}/resources</outputDirectory>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>           

第18步:在IDEA的Terminal視窗輸入mvn clean package -DskipTests指令,完成程式的編譯與打包,得到fc.jar和相關的檔案:

實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器
實作一個簡單的檔案下載下傳伺服器

第19步:将fc.jar檔案和依賴的庫檔案和資源檔案拷貝到Linux運作環境:

實作一個簡單的檔案下載下傳伺服器

第20步:在Linux的/root目錄中建立files目錄,然後向該目錄中傳入兩個檔案flower.jpg和hello.txt:

實作一個簡單的檔案下載下傳伺服器

第21步:執行java -jar fc.jar指令,啟動檔案下載下傳伺服器:

實作一個簡單的檔案下載下傳伺服器

第22步:當我們在浏覽器中輸入http://<伺服器IP>:9201/download/zhangsan/zhang/flower時,浏覽器會自動下載下傳檔案flower.jpg,并且生成的檔案名是我們指定的FileId,也就是flower:

實作一個簡單的檔案下載下傳伺服器

第23步:當我們在浏覽器中故意輸錯使用者名或密碼,例如輸入http://<伺服器IP>:9201/download/zhangsan/zhang2/flower時,将會得到HTTP 401錯誤:

實作一個簡單的檔案下載下傳伺服器

第24步:當我們在浏覽器中故意輸錯檔案ID,例如輸入http://<伺服器IP>:9201/download/zhangsan/zhang/flower.jpg時,将會得到HTTP 404錯誤:

實作一個簡單的檔案下載下傳伺服器

開發完成!