概述
本文是在我由于缺乏IM工具时候需要传软件给别人的时候萌生出来的一个想法,就是希望在没有合适的工具的情况下也能实现将本地文件分享给别人,因此做了这么一件“有趣”的事情。
先来看下实现的效果吧:
点击文件可以直接下载,点击文件夹可以进入文件夹查看该文件夹中的文件列表:
并且为了方便多人维护这个文件服务器,实现资源的共享,开发了身份验证+多文件上传的功能:
这时候只要将网址中的“localhost”替换为部署的服务器IP就能实现简易的轻量级文件服务器。
实现步骤
主要针对java web新手,下面将给出主要的详细实现步骤:
新建一个java web项目
我这里以springboot项目为例,可以在https://start.spring.io/
网站上快速构建一个springboot项目,填入项目相关信息,Dependencies中搜索并添加“Web”、“Freemarker”、“security”依赖即可;点击确定后下载初始化的项目到本地,导入到你的IDE中,如Eclipse、IDEA等。springboot启动文件DemoApplication
@SpringBootApplication
public class DemoApplication {
public static void main(String[] args) {
SpringApplication.run(DemoApplication.class, args);
}
}
该文件是springboot启动的入口类,新建springboot项目时会自动生成。
- 依赖配置文件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 http://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.1.5.RELEASE</version>
<relativePath/> <!-- lookup parent from repository -->
</parent>
<groupId>com.yuhuan</groupId>
<artifactId>demo</artifactId>
<version>0.0.1-SNAPSHOT</version>
<name>demo</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-freemarker</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-starter-security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
</project>
- 新建配置文件application.yml
在项目的src\main\resources目录下新建application.yml文件(或者application.xml),本文以yaml文件为例:
server:
port: 8090
spring:
servlet:
multipart:
enabled: true
file-size-threshold: 0
max-file-size: 4096MB
max-request-size: 8192MB
freemarker:
request-context-attribute: req
suffix: .html
content-type: text/html
enabled: true
cache: false
template-loader-path: classpath:/templates/
charset: UTF-8
#配置共享文件夹的路径
share:
path: D:\\java soft
#配置上传文件的账户
system:
user:
name: admin
pwd: admin@123
其中,share.path配置的就是你要共享的本地文件夹的路径。
- 编写前台页面fileList.html
<!DOCTYPE html>
<head>
<meta charset="UTF-8">
<title>共享文件列表</title>
<script src="/static/js/jquery-3.4.1.min.js" type="text/javascript"></script>
<script type="text/javascript">
function uploadFile(){
var files = $("#files")[0].files;
console.log(files);
if(undefined == files || files.length == 0) {
alert('请选择上传文件!');
return false;
}
var formData = new FormData();
for(var i = 0; i < files.length; i++){
formData.append("files", files[i]);
}
formData.append("currentPath", $('#currentPath').val());
$.ajax({
type: 'post',
url: '/file/upload',
data: formData,
contentType: false,
processData: false,
success:function(res){
console.log(res);
if(res["code"]=="200"){
//refresh
window.location.reload();
alert(res.msg);
}else if(res["code"] == 500){
console.log(res);
alert(res.msg);
}
else{
alert('此操作需要权限,请先验证身份!');
window.location = "/login";
}
}
});
}
</script>
</head>
<input type="hidden" id="currentPath" name="currentPath" value="${currentPath}"/>
<#if root??>
<div style="font-size:18px;width:200px;text-align:left;"><a href="${root!''}"><img src="/static/img/home.png" width="20" height="20"/>返回根目录</a></div></br>
</#if>
<#if parent??>
<div style="font-size:18px;width:200px;text-align:left;"><a href="${parent!''}"><img src="/static/img/back.png" width="20" height="20"/>返回上级目录</a></div></br>
</#if>
<#list files as file>
<#if file.isDirectory()>
<img src="/static/img/package.png" width="20" height="20"/>
<#else>
<img src="/static/img/file.png" width="20" height="20"/>
</#if>
<a href="${file.url}">${file.name}</a></br>
</#list>
<hr/>
<div>
<input type="file" id="files" name="files" multiple="multiple" />
<input type="button" id="uploadBtn" onclick="uploadFile()" value="上传"/>
</div>
</body>
</html>
- 下载图片资源,并保存在src\main\resources\static\img文件夹下
back.png
file.png
home.png
package.png
- 新建包和mvc配置文件
在src\main\java中新建包:com.yuhuan.demo,再在这个文件夹底下新建五个包:common、config、controller、entity、service,看下项目的目录结构(忽略mapper及mapping):
- 在config包下新建WebMvcConfig类
这个配置文件主要是作用是建立网络请求地址url和服务器文件夹的映射关系:
@Configuration
public class WebMvcConfig implements WebMvcConfigurer {
@Override
public void addResourceHandlers(ResourceHandlerRegistry registry) {
registry.addResourceHandler("/static/**")
.addResourceLocations("classpath:/static/");
registry.addResourceHandler(GlobalConstants.DOWNLOAD_URL_PREFIX + "/**")
.addResourceLocations("file:"+ GlobalConstants.sharePath+"/");
}
}
- 在common包下新建GlobalConstants类
这个类主要是提供读取配置文件属性值及全局变量使用:
@Configuration
public class GlobalConstants {
//共享文件夹路径
public static String sharePath;
public static final String BROWSE_URL_PREFIX = "/file/list";
public static final String DOWNLOAD_URL_PREFIX = "/file/download";
public static final String UPLOAD_URL_PREFIX = "/file/upload";
public static String systemUserName;
public static String systemUserPwd;
@Value("${system.user.name}")
private void setSystemUserName(String systemUserName) {
GlobalConstants.systemUserName = systemUserName;
}
@Value("${system.user.pwd}")
private void setSystemUserPwd(String systemUserPwd) {
GlobalConstants.systemUserPwd = systemUserPwd;
}
@Value("${share.path}")
private void setSharePath(String sharePath) {
GlobalConstants.sharePath = sharePath;
}
public static String convertUrl(String originPath){
String strs[] = originPath.split(sharePath);
if(strs.length > 1) {
return BROWSE_URL_PREFIX + "?path=" + originPath.split(sharePath)[1].replaceAll("\\\\", "/");
}
return BROWSE_URL_PREFIX;
}
}
-
在common包下新建ResultData类
该类用于统一返回给前端的数据格式,在本项目中作用不大:
public class ResultData <T> {
private String msg;
private Integer code;
private T data;
public static <T> ResultData setResultCode(ResultCode resultCode){
return new ResultData()
.setCode(resultCode.getCode())
.setMsg(resultCode.getMsg());
}
public static ResultData ok(){
return setResultCode(ResultCode.OK);
}
public static <T> ResultData failed(){
return setResultCode(ResultCode.FAILED);
}
public String getMsg() {
return msg;
}
public ResultData setMsg(String msg) {
this.msg = msg;
return this;
}
public Integer getCode() {
return code;
}
public ResultData setCode(Integer code) {
this.code = code;
return this;
}
public T getData() {
return data;
}
public ResultData setData(T data) {
this.data = data;
return this;
}
enum ResultCode{
OK(200, "请求成功!"),
FAILED(500, "请求失败!");
private Integer code;
private String msg;
ResultCode(Integer code, String msg){
this.code = code;
this.msg = msg;
}
public Integer getCode() {
return code;
}
public void setCode(Integer code) {
this.code = code;
}
public String getMsg() {
return msg;
}
public void setMsg(String msg) {
this.msg = msg;
}
}
}
- 在entity包下新建CustomerFile类
public class CustomerFile {
private String name;
private String url;
private boolean isDirectory;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public boolean isDirectory() {
return isDirectory;
}
public void isDirectory(boolean directory) {
isDirectory = directory;
}
}
- 在controller包下新建FileSystemController类
@Controller
@RequestMapping("/file")
public class FileSystemController {
@Autowired
private FileService fileService;
@RequestMapping("/list")
public String browseFile(Model model, @RequestParam(name="path", required = false,
defaultValue = "") String path){
File file;
if(("").equals(path)){
file = new File(GlobalConstants.sharePath);
}else {
file = new File(GlobalConstants.sharePath + path);
model.addAttribute("parent", GlobalConstants.convertUrl(file.getParent()));
model.addAttribute("root", GlobalConstants.BROWSE_URL_PREFIX);
}
model.addAttribute("currentPath", GlobalConstants.sharePath + path);
model.addAttribute("files", fileService.getFileList(file));
return "fileList";
}
@RequestMapping("/upload")
@ResponseBody
public ResultData uploadFile(@RequestParam("files") MultipartFile[] files, @RequestParam(name=
"currentPath", required = false, defaultValue = "") String currentPath){
String msg;
try {
msg = fileService.uploadFiles(files, currentPath);
} catch (IOException e) {
e.printStackTrace();
return ResultData.failed().setMsg("上传失败!");
}
return ResultData.ok().setMsg(msg);
}
}
- 在service包下新建FileService接口
FileService
public interface FileService {
List<CustomerFile> getFileList(File file);
String uploadFiles(MultipartFile[] files, String currentPath) throws IOException;
}
- 在service包下新建impl子包,并在impl包下创建FileServiceImpl类
@Service
public class FileServiceImpl implements FileService {
@Override
public List<CustomerFile> getFileList(File file) {
List<File> files = new ArrayList<>();
if(file.isDirectory()){
File[] subFiles = file.listFiles();
files = Arrays.asList(subFiles);
}
sortFiles(files);
List<CustomerFile> customerFiles = files.stream().map(f->{
CustomerFile customerFile;
String url;
if(f.isDirectory()){
url = GlobalConstants.convertUrl(f.getPath());
}else{
url = f.getPath().replaceAll(GlobalConstants.sharePath, GlobalConstants.DOWNLOAD_URL_PREFIX);
}
customerFile = new CustomerFile();
customerFile.setName(f.getName());
customerFile.setUrl(url);
customerFile.isDirectory(f.isDirectory());
return customerFile;
}).collect(Collectors.toList());
return customerFiles;
}
@Override
public String uploadFiles(MultipartFile[] files, String currentPath) throws IOException {
if(null == files || files.length == 0){
return "请选择上传文件!";
}
File destFile;
for(MultipartFile file : files){
destFile = new File(currentPath+File.separator+file.getOriginalFilename());
//此处很重要,避免别人覆盖已有的本地文件
if(destFile.exists()){
return String.format("文件:%s已存在,不能上传,请联系管理员删除后再上传!", file.getOriginalFilename());
}
file.transferTo(destFile);
}
return "上传成功!";
}
/**
*对文件进行排序,使得在展示文件列表时,文件夹始终在前面
**/
private void sortFiles(List<File> files) {
Collections.sort(files, (f1, f2) -> {
if (f1.isDirectory()) {
return -1;
}else if (f2.isDirectory()) {
return 1;
}
return 0; //相等为0
});
}
}
部署使用
至此,我们可以来编译并启动这个项目了,可以借助IDEA、Eclipse等IDE来编译启动,直接运行DemoApplication类即可;也可以编译项目后用java -jar命令启动生成的jar包。
启动后,用浏览器访问:http://localhost:8090/file/list
即可看到你配置的共享文件夹的文件列表了,点击文件可以下载,点击文件夹可以进入该文件夹继续浏览文件。
上传文件时会要求用户首先登陆,验证成功后才能上传文件,避免恶意文件的上传导致磁盘空间吃紧!同时,为了避免上传文件时将本地已有文件覆盖(有可能是恶意的),在上传时要验证上传的文件不能将本地文件覆盖!
总结
本文主要是临时萌发的一个想法并把它用熟悉的编程语言实现了,个人觉得还比较有趣。这个轻量级的文件服务器包含了共享文件夹位置的配置、文件浏览与下载功能、用户验证与文件上传等功能,对于想学习java web的同学来说也起到了抛砖引玉的作用!