概述
? 本文是在我由于缺乏IM工具時候需要傳軟件給別人的時候萌生出來的一個想法草添,就是希望在沒有合適的工具的情況下也能實現(xiàn)將本地文件分享給別人,因此做了這么一件“有趣”的事情。
? 先來看下實現(xiàn)的效果吧:
點擊文件可以直接下載,點擊文件夾可以進入文件夾查看該文件夾中的文件列表:
? 并且為了方便多人維護這個文件服務器焰宣,實現(xiàn)資源的共享七蜘,開發(fā)了身份驗證+多文件上傳的功能:
? 這時候只要將網(wǎng)址中的“l(fā)ocalhost”替換為部署的服務器IP就能實現(xiàn)簡易的輕量級文件服務器谭溉。
實現(xiàn)步驟
? 主要針對java web新手,下面將給出主要的詳細實現(xiàn)步驟:
新建一個java web項目
我這里以springboot項目為例橡卤,可以在https://start.spring.io/
網(wǎng)站上快速構(gòu)建一個springboot項目扮念,填入項目相關(guān)信息,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('此操作需要權(quán)限芙贫,請先驗證身份!');
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,看下項目的目錄結(jié)構(gòu)(忽略mapper及mapping):
- 在config包下新建WebMvcConfig類
這個配置文件主要是作用是建立網(wǎng)絡請求地址url和服務器文件夾的映射關(guān)系:
@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類
該類用于統(tǒng)一返回給前端的數(shù)據(jù)格式菠劝,在本項目中作用不大:
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包下創(chuàng)建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已存在锄码,不能上傳夺英,請聯(lián)系管理員刪除后再上傳!", 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
即可看到你配置的共享文件夾的文件列表了,點擊文件可以下載帘靡,點擊文件夾可以進入該文件夾繼續(xù)瀏覽文件知给。
? 上傳文件時會要求用戶首先登陸,驗證成功后才能上傳文件描姚,避免惡意文件的上傳導致磁盤空間吃緊涩赢!同時,為了避免上傳文件時將本地已有文件覆蓋(有可能是惡意的)轩勘,在上傳時要驗證上傳的文件不能將本地文件覆蓋筒扒!
總結(jié)
? 本文主要是臨時萌發(fā)的一個想法并把它用熟悉的編程語言實現(xiàn)了,個人覺得還比較有趣绊寻。這個輕量級的文件服務器包含了共享文件夾位置的配置、文件瀏覽與下載功能深纲、用戶驗證與文件上傳等功能痴奏,對于想學習java web的同學來說也起到了拋磚引玉的作用!