天天看点

实现一个简单的文件下载服务器

作者:编程实践
实现一个简单的文件下载服务器

前面的文章《使用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错误:

实现一个简单的文件下载服务器

开发完成!