基于Java Web實現(xiàn)一個輕量級的文件服務器

概述

? 本文是在我由于缺乏IM工具時候需要傳軟件給別人的時候萌生出來的一個想法草添,就是希望在沒有合適的工具的情況下也能實現(xiàn)將本地文件分享給別人,因此做了這么一件“有趣”的事情。
? 先來看下實現(xiàn)的效果吧:


1561258028118.png

點擊文件可以直接下載,點擊文件夾可以進入文件夾查看該文件夾中的文件列表:


1560990559376.png

? 并且為了方便多人維護這個文件服務器焰宣,實現(xiàn)資源的共享七蜘,開發(fā)了身份驗證+多文件上傳的功能:


1561259432651.png

? 這時候只要將網(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):


1561258287641.png
  • 在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的同學來說也起到了拋磚引玉的作用!

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末和泌,一起剝皮案震驚了整個濱河市,隨后出現(xiàn)的幾起案子祠肥,更是在濱河造成了極大的恐慌武氓,老刑警劉巖,帶你破解...
    沈念sama閱讀 206,311評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件搪柑,死亡現(xiàn)場離奇詭異聋丝,居然都是意外死亡,警方通過查閱死者的電腦和手機工碾,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,339評論 2 382
  • 文/潘曉璐 我一進店門弱睦,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人渊额,你說我怎么就攤上這事况木。” “怎么了旬迹?”我有些...
    開封第一講書人閱讀 152,671評論 0 342
  • 文/不壞的土叔 我叫張陵火惊,是天一觀的道長。 經(jīng)常有香客問我奔垦,道長屹耐,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 55,252評論 1 279
  • 正文 為了忘掉前任椿猎,我火速辦了婚禮惶岭,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘犯眠。我一直安慰自己按灶,他們只是感情好,可當我...
    茶點故事閱讀 64,253評論 5 371
  • 文/花漫 我一把揭開白布筐咧。 她就那樣靜靜地躺著鸯旁,像睡著了一般。 火紅的嫁衣襯著肌膚如雪量蕊。 梳的紋絲不亂的頭發(fā)上铺罢,一...
    開封第一講書人閱讀 49,031評論 1 285
  • 那天,我揣著相機與錄音危融,去河邊找鬼畏铆。 笑死,一個胖子當著我的面吹牛吉殃,可吹牛的內(nèi)容都是我干的辞居。 我是一名探鬼主播楷怒,決...
    沈念sama閱讀 38,340評論 3 399
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼瓦灶!你這毒婦竟也來了鸠删?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 36,973評論 0 259
  • 序言:老撾萬榮一對情侶失蹤贼陶,失蹤者是張志新(化名)和其女友劉穎刃泡,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體碉怔,經(jīng)...
    沈念sama閱讀 43,466評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡烘贴,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 35,937評論 2 323
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了撮胧。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片桨踪。...
    茶點故事閱讀 38,039評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖芹啥,靈堂內(nèi)的尸體忽然破棺而出锻离,到底是詐尸還是另有隱情,我是刑警寧澤墓怀,帶...
    沈念sama閱讀 33,701評論 4 323
  • 正文 年R本政府宣布汽纠,位于F島的核電站,受9級特大地震影響傀履,放射性物質(zhì)發(fā)生泄漏虱朵。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 39,254評論 3 307
  • 文/蒙蒙 一钓账、第九天 我趴在偏房一處隱蔽的房頂上張望卧秘。 院中可真熱鬧,春花似錦官扣、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,259評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至治专,卻和暖如春卖陵,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背张峰。 一陣腳步聲響...
    開封第一講書人閱讀 31,485評論 1 262
  • 我被黑心中介騙來泰國打工泪蔫, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人喘批。 一個月前我還...
    沈念sama閱讀 45,497評論 2 354
  • 正文 我出身青樓撩荣,卻偏偏與公主長得像铣揉,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子餐曹,可洞房花燭夜當晚...
    茶點故事閱讀 42,786評論 2 345

推薦閱讀更多精彩內(nèi)容