laitimes

Implement a simple file download server

author:Programming practices
Implement a simple file download server

The previous article, "Implementing a Static Website with SpringBoot", implements an ancient poetry network with SpringBoot. In fact, put other files in the static directory of this server, such as readme.txt files, which can be viewed directly through the browser, through Xunlei, or directly downloaded:

Implement a simple file download server
Implement a simple file download server
Implement a simple file download server
Implement a simple file download server
Implement a simple file download server
Implement a simple file download server
Implement a simple file download server

However, treating this function as a file download function is too crude. Personally, I believe that even the simplest file download server has at least the following two points:

(1) After entering the URL of the resource to be downloaded through the browser, the browser can save the attachment;

(2) Software engineers can add code that controls file download permissions as needed.

Next, we will develop a simple file download server based on SpringBoot using the IDEA development tool.

Step 1: Create an empty project fc_prj using IDEA, then add a SpringBoot project file_center to the fc_prj project, and add four dependencies: Lombok, Spring Web, Spring Data JPA, and MySQL Driver. After the operation is completed, the project view is as follows:

Implement a simple file download server

Step 2: Run the following SQL statement in mySQL to create the database db_fc used in this project:

create database db_fc default character set utf8;           
Implement a simple file download server

Step 3: Create a t_user table in the db_fc database for permission control, in order to make the program simple, the t_user table here only contains three fields of user name, password, and remarks, and the SQL statement to create a t_user table is as follows:

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;           
Implement a simple file download server

Step 4: We insert the information of Zhang San, Li 4, and Wang Wu into the t_user table, and the SQL statement is as follows:

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;           
Implement a simple file download server

Step 5: Create a t_file table in the db_fc database to record the file information that can be downloaded, the table has two fields of file identifier and file full path, and the SQL statement to create the t_file table is as follows:

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;           
Implement a simple file download server

Step 6: We insert some downloadable file information into the t_file table, the SQL statement is as follows:

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;           
Implement a simple file download server

Step 7: Create a new entity package;

Implement a simple file download server

Step 8: Create a UserEntity class in the entity package, and a UserEntity instance corresponds to a record of the t_user table. The code for UserEntity is as follows:

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;
}           

Step 9: Create a FileEntity class in the entity package, and a FileEntity instance corresponds to a record of the t_file table. The code for FileEntity is as follows:

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;
}           

Step 10: Create a new repository package;

Implement a simple file download server

Step 11: Create a UserRepository interface in the repository package for manipulating t_user tables, the code is as follows:

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);
}           

Step 12: Create a FileRepository interface in the repository package for manipulating the t_file table, the code is as follows:

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);
}           

Step 13: Create a new controller package and service package:

Implement a simple file download server

Step 14: Create a FileService class in the service package to handle HTTP download requests, with the following code:

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);
}
}
}           

Step 15: Create the FileController class in the controller package to receive HTTP download requests from browsers or other clients, with the following code:

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);
}
}           

Step 16: Modify the application.properties file, add the configuration information of the server, here only configure the port and database connection information used by the server, the content of the application.properties file is as follows:

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           

Step 17: Modify the POM .xml file, add compilation and packaging information, the content of the modified POM .xml file is as follows:

<?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>           

Step 18: Enter the mvn clean package -DskipTests command in the Terminal window of IDEA to complete the compilation and packaging of the program to obtain fc .jar and related files:

Implement a simple file download server
Implement a simple file download server
Implement a simple file download server

Step 19: Copy fc .jar files and dependent library files and resource files to the Linux runtime environment:

Implement a simple file download server

Step 20: Create a new files directory in the /root directory of Linux, and then pass two files into the directory flower.jpg and hello.txt:

Implement a simple file download server

Step 21: Execute the java -jar fc .jar command to start the file download server:

Implement a simple file download server

Step 22: When we enter http:// in the browser: <服务器IP>9201/download/zhangsan/zhang/flower, the browser will automatically download the file flower.jpg, and the generated file name is the FileId we specified, which is flower:

Implement a simple file download server

Step 23: When we deliberately enter the wrong username or password in the browser, for example, enter <服务器IP>http://: 9201/download/zhangsan/zhang2/flower, we will get an HTTP 401 error:

Implement a simple file download server

Step 24: When we deliberately enter the wrong file ID in the browser, such as entering http://: <服务器IP>9201/download/zhangsan/zhang/flower.jpg, we will get an HTTP 404 error:

Implement a simple file download server

Development complete!