SpringBoot文件上传和下载

这里介绍下SpringBoot中如何接收前台页面上传的文件,以及如何下载文件。

1、单个文件上传

1.1 单个文件上传实现步骤

首先写一个文件上传的Controller。
com.space.mysql.connect.controller.FileOpController

package com.space.mysql.connect.controller;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;


/**
 * Created by chengxia on 2021/12/12.
 * 这里需要尤为注意,Controller注解 ,必须用Controller,不能用原来的RestController!
 */
@Controller
@RequestMapping("/file")
public class FileOpController {

    public final static String IMG_PATH_PREFIX = "static/upload";

    @RequestMapping("/upload")
    public String uploadPage(HttpServletRequest request){
        return "fileUploadPage";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receive", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File uploaded success!";
    }
}

这个controller中:
public String uploadPage(HttpServletRequest request)方法返回一个模板名fileUploadPage,绑定到/file/upload。这样,当用户访问http://localhost:8080/file/upload时会跳转到fileUploadPage.html这个模板。
public String receiveFile(@RequestParam("file")MultipartFile file, HttpServletRequest request)方法中,上传的文件可以通过函数参数中的file参数获得,在这个函数中将接收到的文件写入到了resources目录下的static/upload子目录。处理完成之后,返回给用户一个文件上传成功的提示。

然后,写一个文件上传的模板,就是前面提到的fileUploadPage.html
templates/fileUploadPage.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="/file/receive">
    <div>
        File Choose:
        <input type="file" name="file"></div>
    <div>
        <input type="submit" value="Submit"/>
    </div>
</form>
</body>
</html>

到这里就完成了。目录结构如下:

image.png

运行启动之后,浏览器中访问http://localhost:8080/file/upload
image.png

选中文件之后:
image.png

上传文件之后:
image.png

这时候,查看下工程的目录,会发现文件已经上传到target目录的指定子目录下:
image.png

1.2 需要注意的问题

1.2.1 文件上传目录的获得

这里是通过Resource applicationProperties = new ClassPathResource("application.properties")先获得了配置文件application.properties的目录,然后通过父目录拼接得到了存放上传文件的目录。这里为什么直接用ClassPathResource获取一个空目录呢?因为会报错,看样子ClassPathResource只支持一个已存在文件的获取,空目录是不行的。
代码中也列出了其他几种获取目录的方式:

//如下随便尝试几种获取module运行目录的几种方式
//直接取相对目录的话,获取到的是project中新建的第一个module中的路径
String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
File directRelativeDir = new File(directRelativePath);
System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

//通过System.getProperty("user.dir")获取module路径
String userDirPath = System.getProperty("user.dir");
File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

//request.getServletContext().getRealPath("/")获取运行时的路径
String requestServletContextPath = request.getServletContext().getRealPath("/");
File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());

对应的输出如下,可以供参考:

直接写相对目录获取的路径, src/main/resources/static/upload: /Users/chengxia/Developer/Java/projects/Test/src/main/resources/static/upload
通过系统属性user.dir获取的路径, System.getProperty("user.dir") + /static/upload: /Users/chengxia/Developer/Java/projects/Test/static/upload
通过ServletContext获取的路径, request.getServletContext().getRealPath("/") + /static/upload: /private/var/folders/jv/_wl4thx51kd9fszq10m2sqlr0000gn/T/tomcat-docbase.1230195161197134728.8080/static/upload
上传文件的存放目录:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload

1.2.2 org.springframework.web.multipart.MultipartFile#transferTo方法

org.springframework.web.multipart.MultipartFile#transferTo方法在使用时需要注意,只能调用一次,如果有第二次调用,会报错。因为http post文件流只可以接收读取一次,传输完毕则关闭流。如果需要将文件同时保存到两个地方,第二次需要做一次文件复制,不能连续调两遍org.springframework.web.multipart.MultipartFile#transferTo

2、上传文件的时候,同时提交参数

这里只需要修改前面的Controller和模板。
com.space.mysql.connect.controller.FileOpController

package com.space.mysql.connect.controller;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;


/**
 * Created by chengxia on 2021/12/12.
 * 这里需要尤为注意,Controller注解 ,必须用Controller,不能用原来的RestController!
 */
@Controller
@RequestMapping("/file")
public class FileOpController {

    public final static String IMG_PATH_PREFIX = "static/upload";

    @RequestMapping("/upload")
    public String uploadPage(HttpServletRequest request){
        return "fileUploadPage";
    }


    @RequestMapping("/uploadParam")
    public String uploadPageParam(HttpServletRequest request){
        return "fileUploadPageParam";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receive", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileParam", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, @RequestParam() String fileName, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File name, " + fileName + " uploaded success!";
    }
}

主要是添加了一个public String uploadPageParam(HttpServletRequest request)方法,返回一个新的文件上传带form参数的模板。添加了一个public String receiveFile(@RequestParam("file")MultipartFile file, @RequestParam() String fileName, HttpServletRequest request)方法,在参数中,除了接收文件之外,也接收一个表单参数。
templates/fileUploadPageParam.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="/file/receiveFileParam">
    <div>
        File Choose:
        <input type="file" name="file"/>
        <br/>
        File Alias Name:
        <input type="text" name="fileName"/>
    </div>
    <div>
        <input type="submit" value="Submit"/>
    </div>
</form>
</body>
</html>

目录结构如下:

image.png

启动服务器之后,访问http://localhost:8080/file/uploadParam
image.png

选中文件,填写表单之后:
image.png

提交之后:
image.png

3、多个文件上传

这里多个文件上传,实际上分为两种情况:一种是多个文件上传的form项,每一个都传了一个文件;另一种是在一个form项中同时传了多个文件。下面在一个例子中演示。
com.space.mysql.connect.controller.FileOpController

package com.space.mysql.connect.controller;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import java.io.File;


/**
 * Created by chengxia on 2021/12/12.
 * 这里需要尤为注意,Controller注解 ,必须用Controller,不能用原来的RestController!
 */
@Controller
@RequestMapping("/file")
public class FileOpController {

    public final static String IMG_PATH_PREFIX = "static/upload";

    @RequestMapping("/upload")
    public String uploadPage(HttpServletRequest request){
        return "fileUploadPage";
    }


    @RequestMapping("/uploadParam")
    public String uploadPageParam(HttpServletRequest request){
        return "fileUploadPageParam";
    }


    @RequestMapping("/uploadMulti")
    public String uploadPageMulti(HttpServletRequest request){
        return "fileUploadPageMulti";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receive", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileParam", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, @RequestParam() String fileName, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File name, " + fileName + " uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileMulti1", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file1")MultipartFile file1, @RequestParam("file2")MultipartFile file2, @RequestParam() String filesMark, HttpServletRequest request) throws Exception{
        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        if(file1.isEmpty()){
            return "file1没有选中任何文件!";
        }
        // 拿到文件名
        String filename = file1.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file1.transferTo(uploadFile);


        if(file2.isEmpty()){
            return "file2没有选中任何文件!";
        }
        // 拿到文件名
        filename = file2.getOriginalFilename();
        // 构建真实的文件路径
        uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file2.transferTo(uploadFile);


        return "File name, " + filesMark + " uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileMulti2", method = RequestMethod.POST)
    public String receiveFile(@RequestParam() MultipartFile[] files, @RequestParam() String filesMark, HttpServletRequest request) throws Exception{
        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        for(int i = 0; i < files.length; i++) {
            MultipartFile file = files[I];
            if(file == null){
                continue;
            }
            // 拿到文件名
            String filename = file.getOriginalFilename();
            // 构建真实的文件路径
            File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
            System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
            //文件写到服务器,
            file.transferTo(uploadFile);
        }
        return "File name, " + filesMark + " uploaded success!";
    }
}

templates/fileUploadPageMulti.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
<form enctype="multipart/form-data" method="post" action="/file/receiveFileMulti1">
    <div>
        多个文件分别提交,File Choose:
        <br/>
        <input type="file" name="file1"/>
        <br/>
        <input type="file" name="file2"/>
        <br/>
        Files Mark:
        <input type="text" name="filesMark"/>
    </div>
    <div>
        <input type="submit" value="Submit"/>
    </div>
</form>
<br/>
<br/>
<br/>
<form enctype="multipart/form-data" method="post" action="/file/receiveFileMulti2">
    <div>
        在一个文件form中提交多个文件,Files Choose:
        <br/>
        <input type="file" multiple name="files"/>
        <br/>
        <br/>
        Files Mark:
        <input type="text" name="filesMark"/>
    </div>
    <div>
        <input type="submit" value="Submit"/>
    </div>
</form>
</body>
</html>

目录结构:

image.png

启动服务器之后,访问http://localhost:8080/file/uploadMulti
image.png

分别选中两个文件并填写form后:
image.png

点击上面的提交:
image.png

控制台输出:

上传文件的存放目录:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload
文件上传到:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload/f1.txt
文件上传到:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload/f2.txt

重新访问http://localhost:8080/file/uploadMulti,并在下面的表单中同时选中多个文件:

image.png

这时候点击下面的提交:
image.png

这时候控制台输出:

上传文件的存放目录:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload
文件上传到:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload/a.txt
文件上传到:/Users/chengxia/Developer/Java/projects/Test/connnect/target/classes/static/upload/b.txt

注意上传文件不能太大,否则会报错(控制台):

org.apache.tomcat.util.http.fileupload.FileUploadBase$FileSizeLimitExceededException: The field files exceeds its maximum permitted size of 1048576 bytes.
    at org.apache.tomcat.util.http.fileupload.FileUploadBase$FileItemIteratorImpl$FileItemStreamImpl$1.raiseError(FileUploadBase.java:630) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.checkLimit(LimitedInputStream.java:76) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.http.fileupload.util.LimitedInputStream.read(LimitedInputStream.java:135) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at java.io.FilterInputStream.read(FilterInputStream.java:107) ~[na:1.8.0_181]
    at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:98) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.http.fileupload.util.Streams.copy(Streams.java:68) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.http.fileupload.FileUploadBase.parseRequest(FileUploadBase.java:293) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.connector.Request.parseParts(Request.java:2869) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.connector.Request.parseParameters(Request.java:3216) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.connector.Request.getParameter(Request.java:1137) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.connector.RequestFacade.getParameter(RequestFacade.java:381) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.springframework.web.filter.HiddenHttpMethodFilter.doFilterInternal(HiddenHttpMethodFilter.java:75) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:200) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107) ~[spring-web-5.0.4.RELEASE.jar:5.0.4.RELEASE]
    at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:193) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:166) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:199) ~[tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:96) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:496) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:140) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:81) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:87) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:342) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:803) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:66) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:790) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1459) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1149) [na:1.8.0_181]
    at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:624) [na:1.8.0_181]
    at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61) [tomcat-embed-core-8.5.28.jar:8.5.28]
    at java.lang.Thread.run(Thread.java:748) [na:1.8.0_181]

4、文件下载

相比上传,文件下载要容易得多。
com.space.mysql.connect.controller.FileOpController

package com.space.mysql.connect.controller;

import org.springframework.core.io.ClassPathResource;
import org.springframework.core.io.Resource;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.File;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URLEncoder;


/**
 * Created by chengxia on 2021/12/12.
 * 这里需要尤为注意,Controller注解 ,必须用Controller,不能用原来的RestController!
 */
@Controller
@RequestMapping("/file")
public class FileOpController {

    public final static String IMG_PATH_PREFIX = "static/upload";

    @RequestMapping("/upload")
    public String uploadPage(HttpServletRequest request){
        return "fileUploadPage";
    }

    @RequestMapping("/download")
    public String downloadPage(HttpServletRequest request){
        return "fileDownloadPage";
    }


    @RequestMapping("/uploadParam")
    public String uploadPageParam(HttpServletRequest request){
        return "fileUploadPageParam";
    }


    @RequestMapping("/uploadMulti")
    public String uploadPageMulti(HttpServletRequest request){
        return "fileUploadPageMulti";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receive", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileParam", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file")MultipartFile file, @RequestParam() String fileName, HttpServletRequest request) throws Exception{
        if(file.isEmpty()){
            return "没有选中任何文件!";
        }
        //如下随便尝试几种获取module运行目录的几种方式
        //直接取相对目录的话,获取到的是project中新建的第一个module中的路径
        String directRelativePath = new String("src/main/resources" + File.separator + IMG_PATH_PREFIX);
        File directRelativeDir = new File(directRelativePath);
        System.out.println("直接写相对目录获取的路径, src/main/resources/static/upload: " + directRelativeDir.getAbsolutePath());

        //通过System.getProperty("user.dir")获取module路径
        String userDirPath = System.getProperty("user.dir");
        File userDirDir = new File(userDirPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过系统属性user.dir获取的路径, System.getProperty(\"user.dir\") + /static/upload: "+userDirDir.getAbsolutePath());

        //request.getServletContext().getRealPath("/")获取运行时的路径
        String requestServletContextPath = request.getServletContext().getRealPath("/");
        File requestServletContextDir = new File(requestServletContextPath + File.separator + IMG_PATH_PREFIX);
        System.out.println("通过ServletContext获取的路径, request.getServletContext().getRealPath(\"/\") + /static/upload: "+requestServletContextDir.getAbsolutePath());


        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        // 拿到文件名
        String filename = file.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file.transferTo(uploadFile);

        return "File name, " + fileName + " uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileMulti1", method = RequestMethod.POST)
    public String receiveFile(@RequestParam("file1")MultipartFile file1, @RequestParam("file2")MultipartFile file2, @RequestParam() String filesMark, HttpServletRequest request) throws Exception{
        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        if(file1.isEmpty()){
            return "file1没有选中任何文件!";
        }
        // 拿到文件名
        String filename = file1.getOriginalFilename();
        // 构建真实的文件路径
        File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file1.transferTo(uploadFile);


        if(file2.isEmpty()){
            return "file2没有选中任何文件!";
        }
        // 拿到文件名
        filename = file2.getOriginalFilename();
        // 构建真实的文件路径
        uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
        System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
        //文件写到服务器,
        file2.transferTo(uploadFile);


        return "File name, " + filesMark + " uploaded success!";
    }

    /**
     * ResponseBody注解的意思是,这里返回的不是一个thymeleaf的模板名,就是一个简单的请求体
     * */
    @ResponseBody
    @RequestMapping(value="/receiveFileMulti2", method = RequestMethod.POST)
    public String receiveFile(@RequestParam() MultipartFile[] files, @RequestParam() String filesMark, HttpServletRequest request) throws Exception{
        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File uploadFileSaveDir = new File(uploadFileSavePath);
        System.out.println("上传文件的存放目录:"+uploadFileSaveDir.getAbsolutePath());
        if(!uploadFileSaveDir.exists()){
            // 递归生成文件夹
            uploadFileSaveDir.mkdirs();
        }

        for(int i = 0; i < files.length; i++) {
            MultipartFile file = files[I];
            if(file == null){
                continue;
            }
            // 拿到文件名
            String filename = file.getOriginalFilename();
            // 构建真实的文件路径
            File uploadFile = new File(uploadFileSaveDir.getAbsolutePath() + File.separator + filename);
            System.out.println("文件上传到:" + uploadFile.getAbsolutePath());
            //文件写到服务器,
            file.transferTo(uploadFile);
        }
        return "File name, " + filesMark + " uploaded success!";
    }

    @ResponseBody
    @RequestMapping(value = "/fetch", method = RequestMethod.POST)
    public String fileDownload(@RequestParam() String fileName, HttpServletResponse response)throws Exception{
        //这里将文件放在resources目录的static/upload子目录下,通过ClassPathResource的方式先拿到application.properties文件的路径。
        //然后,取父目录得到resources目录。
        //先通过ClassPathResource获取application.properties的路径
        Resource applicationProperties = new ClassPathResource("application.properties");
        //然后通过取其父目录获得resources目录,设置上传文件的目录
        String uploadFileSavePath = applicationProperties.getFile().getParentFile().getAbsolutePath() + File.separator + "static/upload";
        File downloadFile = new File(uploadFileSavePath + File.separator + fileName);
        System.out.println("下载文件的完整路径:"+downloadFile.getAbsolutePath());
        if(!downloadFile.exists()){
            System.out.println("要下载的文件不存在:"+downloadFile.getAbsolutePath());
            return "File not exists, download fail!";
        }else {
            //第一步:设置响应类型
            response.setContentType("application/force-download");//应用程序强制下载
            //第二读取文件
            InputStream in = new FileInputStream(downloadFile);
            //设置响应头,对文件进行url编码
            fileName = URLEncoder.encode(fileName, "UTF-8");
            response.setHeader("Content-Disposition", "attachment;filename="+fileName);
            response.setContentLength(in.available());

            //第三步:读文件写入http响应
            OutputStream out = response.getOutputStream();
            byte[] b = new byte[1024];
            int len = 0;
            while((len = in.read(b))!=-1){
                out.write(b, 0, len);
            }
            out.flush();
            out.close();
            in.close();
            return "Download success!";
        }
    }
}

controller中多写了两个方法,一个返回下载文件的页面。一个方法处理文件的下载请求,页面传入文件名,程序从原来上传文件的存放目录查找文件,找到就作为下载附件返回,找不到就提示报错。
下载文件的页面模板也很简单。templates/fileDownloadPage.html

<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
    <meta charset="UTF-8">
    <title>File Upload</title>
</head>
<body>
<form action="/file/fetch" method="post">
    下载文件名:<input type="text" name="fileName" maxlength="500" style="width:400px;"><br/>

    <input type="submit" value="提交">
</form>
</body>
</html>

完成后目录结构如下:

image.png

启动程序之后,访问http://localhost:8080/file/download
image.png

输入下载的文件名:
image.png

点击提交,浏览器会弹出下载框,下载文件a.txt。如果输入不存在的文件名:
image.png

点击提交,就会提示文件不存在:
image.png

参考资料

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,258评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,335评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,225评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,126评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,140评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,098评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,018评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,857评论 0 273
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,298评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,518评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,678评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,400评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,993评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,638评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,801评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,661评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,558评论 2 352

推荐阅读更多精彩内容