angularjs + springmvc 多文件上传和下载

需求:可以选择多文件,并展示出来,可以进行前端删除,选择需要的进行上传;下载附件

<h1>多文件选择上传</h1>
    <input id="selectInputFile" name="selectInputFiles" multiple="multiple" type="file" style="display:none"/>
    <ol>
        <li ng-repeat="file in repeatFiles track by $index">
            <span>{{file.name}}</span>
            <a href="javascript:void(0);" ng-click="deleteRepeatFile($index)">删除</a>
        </li>
    </ol>
<button class="btn" type="submit" ng-click="selectFile()">选择文件</button>
<button class="btn" type="submit" ng-click="fileUpload()">上传</button>

隐藏一个input type="file" 去选择文件,当点击“选择文件”的时候,去触发
input type="file"的点击事件

//选择文件按钮触发 input type='file' 选择文件
$scope.selectFile = function(){
    $("#selectInputFile").click();
};

再监听选择文件的change事件,获取当前选择的文件push到已有的里面,更新
两种方式都可以获取文件的数组,一般情况下,currentTargetthis 是一样的。
添加后面选择的文件时,循环push的话,把一个个file加到 newRepeatFile 里面;如果是concat,直接添加一个数组,就会有一个 fileList 在数组里,页面 ng-repeat 的时候,会有一个空的显示在页面。

//监听 input type='file' 的change事件
$("#selectInputFile").on("change",function (e) {
    var selectFiles = document.querySelector('input[name="selectInputFiles"]').files;
    //或 e.currentTarget获取文件
    //var selectFiles2 = e.currentTarget.files;

    //使用concat直接添加数组,最后会加一个数组进去,页面循环会有问题
    var newRepeatFile = $scope.repeatFiles;
    for (var i=0; i<selectFiles.length; i++) {
        newRepeatFile.push(selectFiles[i]);
    }

    $scope.$apply(function () {
        $scope.repeatFiles = newRepeatFile;
    });
});

删除的点击事件

$scope.deleteRepeatFile = function(index){
    $scope.repeatFiles.splice(index,1);
};

上传按钮点击事件
FormData为序列化表以及创建与表单格式相同的数据(当然是用于XHR传输)提供便利。
直接append,同样的key,后面的value会变成一个数组
Angular 默认的 transformRequest 方法会尝试序列化我们的 FormData 对象,因此此处我们使用 angular.identity 函数来覆盖它;另外,angular 在发送 POST 请求的时候使用的默认 Content-Typeapplication/json,因此此处需要调整为 undefined,这时浏览器会自动的帮我们设置成 multipart/form-data 的编码方式,同时还会生成一个合适的 boundary,如果手动设置成 multipart/form-data 的话就不会生成 boundary 字段了。

$scope.fileUpload = function () {
    var form = new FormData();
    var selectFiles = $scope.repeatFiles;
     for (var i=0; i<selectFiles.length; i++) {
        form.append("file", selectFiles[i]);
      }

    var url = 'file/upload';
    $http({
        method: 'POST',
        url: Config.getApiRemoteUrl() + url,
        data: form,
        headers: {
            'Content-Type':undefined
        },
        transformRequest: angular.identity
    }).then(function successCallback(response) {
        if(response.status === 200){
            layer.msg("success");
        }else{
            layer.msg("fail");
        }
    }, function errorCallback(response) {
        layer.msg("error");
    });
};

后端上传接口,写到本地路径,localUploadDir 定义了一个本地文件夹,根据年月日时分秒新建文件夹存储文件

private static String localUploadDir;

@Value("${local.fileserver.dir:''}")
public void setLocalUploadDir(String uploadDir) {
    localUploadDir = uploadDir;
}

@PostMapping("/upload")
public void uploadFile(@RequestParam(value = "file") MultipartFile[]  files){
    if(files != null && files.length > 0){
        for(MultipartFile file : files){
            System.out.println("保存完成 : " + saveUploadFile(file));
        }
    }
}

private String saveUploadFile(MultipartFile multipartFile) {
    Calendar now = Calendar.getInstance();
    String fileName = multipartFile.getOriginalFilename();
    // 设定文件保存的目录,按年月日时分秒创建文件夹
    String path = localUploadDir +
        now.get(Calendar.YEAR) + "/" +
        (now.get(Calendar.MONTH) + 1) + "/" +
        now.get(Calendar.DAY_OF_MONTH) + "/" +
        now.get(Calendar.HOUR_OF_DAY) + "/" +
        now.get(Calendar.MINUTE) + "/" +
        now.get(Calendar.SECOND) + "/";
    File file = new File(path);
    if (!file.exists()){
        file.mkdirs();
    }
    try (
            FileOutputStream fileOutputStream = new FileOutputStream(path + fileName);
            InputStream inputStream = multipartFile.getInputStream();
            ) {
        if (!multipartFile.isEmpty()) {
            int len;
            byte[] buffer = new byte[1024];
            while ((len = inputStream.read(buffer)) > 0){
                fileOutputStream.write(buffer,0, len);
            }
        }
    }catch (Exception e){
        e.printStackTrace();
    }
    return path + fileName;
}

效果


文件上传

文件下载使用a标签,直接把url写在里面

<h1>a标签 文件下载</h1>
<a target="_self" id="download" ng-click="fileDownload()">下载</a>

api接口 @RequestMapping("/api/file")

后端下载接口
在服务器设置响应头,告诉浏览器以 utf-8 的编码显示数据,如果不写会出现中文乱码
设置内容类型为 application/octet-stream 即 .* 代表二进制流,不知道下载文件类型
Content-disposition 是 MIME 协议的扩展,MIME 协议指示 MIME 用户代理如何显示附加的文件。Content-disposition其实可以控制用户请求所得的内容存为一个文件的时候提供一个默认的文件名,文件直接在浏览器上显示或者在访问时弹出文件下载对话框。
文件名为 new String(fileName.getBytes("GB2312"), "ISO8859-1")
ISO8859-1是页面上数据传输的格式
GB2312是你java项目格式(根据实际项目变更),目的是为了将中文文件名正确显示在页面上。
防止中文乱码

@GetMapping("/download")
public void downloadFile(HttpServletResponse response) {
    String filePath = localUploadDir + "2019/6/6/11/2/28/测试上传1.txt";
    String fileName = filePath.substring(filePath.lastIndexOf('/') + 1);
    response.setCharacterEncoding ("utf-8");
    //二进制流
    response.setContentType("application/octet-stream");
    try (
        FileInputStream fileInputStream = new FileInputStream(filePath);
        ServletOutputStream servletOutputStream = response.getOutputStream();
        ){
        //附件
        response.setHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("GB2312"), "ISO8859-1"));
        int len;
        byte[] buffer = new byte[1024];
        while ((len = fileInputStream.read(buffer)) > 0){
            servletOutputStream.write(buffer,0, len);
        }
    }catch (IOException e){
        e.printStackTrace();
    }
}
$scope.fileDownload = function () {
    $('#download').attr('href', './api/file/download');
};

效果


文件下载

application.yml配置文件

server:
  servlet:
    context-path: /file
  port: 8123

spring:
  thymeleaf:
    cache: false
    encoding: UTF-8
  servlet:
    multipart:
      #上传文件最大大小
      max-file-size: 100MB
      max-request-size: 1000MB

debug: true

local:
  fileserver:
    dir: D:/uploadfile/
    path: /uploadfile/**

参考:
百度知道
header中Content-Disposition的作用与使用方法
文件上传与 Angular

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

推荐阅读更多精彩内容