codecamp

文件上传

关于文件上传

大多数的 Web 应用都不可避免的,会涉及到文件上传。文件上传,不过是一种适配 HTTP 输入流的方式。 为此,Nutz.Mvc 内置了一个专门处理文件上传的适配器 org.nutz.mvc.upload.UploadAdaptor

这个适配器专门解析形式为

<form target="hideWin" enctype="multipart/form-data" method="post">
    <input type="file">
    ...

的 HTML 提交表单

这个适配器的工作细节是这样的:

  • 它一次将 HTTP 输入流中所有的文件读入,保存在临时文件目录
  • 表单项目会保存在内存里
  • 在上传的过程中,它会向当前 Session 中设置一个对象: org.nutz.mvc.upload.UploadInfo
    • 属性名为 "org.nutz.mvc.upload.UploadInfo"
    • 通过静态函数 Uploads.getInfo(HttpServletRequest req) 可以很方便的获取当前会话的 UploadInfo
  • 不断的读入输入流的过程,会记录在 UploadInfo 里面。
    • UploadInfo 字段 sum 是当前 HTTP 请求的 ContentLength,表示 HTTP 输入流总长度为多少字节
    • UploadInfo 字段 current 是当前会话已经上传了多少字节

如何使用

如果你读过 适配器 一节,我想你已经知道怎么使用文件上传了,这里还需要多一点说明

通过 @AdaptBy 声明

...
@AdaptBy(type = UploadAdaptor.class, args = { "${app.root}/WEB-INF/tmp" })
public void uploadPhoto(@Param("id") int id, @Param("photo") File f){
    ...

Nutz.Mvc 会为你的入口函数创建一个上传适配器,上传适配器的会将临时文件存放在 WEB-INF/tmp 目录下。

更细腻的控制

更多的时候,你想控制

  • 上传文件的临时文件数量
  • 可以上传的单个文件最大尺寸
  • 文件的类型
  • 空文件跳过不保存

那么你需要把上传适配器交由 Ioc 容器来管理,首先你的入口函数需要这么写:

...
@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
public void uploadPhoto(@Param("id") int id, @Param("photo") File f){
    ...

请注意这里的 "ioc:myUpload",Nutz.Mvc 发现你的参数为此形式时,它会从 Ioc 容器里获取你的 上传适配器的实例(名字为myUpload),而不是直接 new 出来, 而且必须为singleton=false。

然后你需要在你的 Ioc 配置文件里 (以 Json 配置为例子)配置你的 UploadAdaptor, 你需要增加 下面这三个对象:

...
tmpFilePool : {
    type : 'org.nutz.filepool.NutFilePool',
    // 临时文件最大个数为 1000 个
    args : [ "~/nutz/blog/upload/tmps", 1000 ]   
},
uploadFileContext : {
    type : 'org.nutz.mvc.upload.UploadingContext',
    singleton : false,
    args : [ { refer : 'tmpFilePool' } ],
    fields : {
        // 是否忽略空文件, 默认为 false
        ignoreNull : true,
        // 单个文件最大尺寸(大约的值,单位为字节,即 1048576 为 1M)
        maxFileSize : 1048576,
        // 正则表达式匹配可以支持的文件名
        nameFilter : '^(.+[.])(gif|jpg|png)$' 
    } 
},
myUpload : {
    type : 'org.nutz.mvc.upload.UploadAdaptor',
    singleton : false,
    args : [ { refer : 'uploadFileContext' } ] 
}
...

这样,在配置文件中,你就可以随心所欲的进行控制了。而且这么写仅仅多写了不到 20 行的配置文件, 到也没有特别麻烦,对吗?(相关详细规则描述:http_adaptor.man#通过_Ioc_容器获得适配器

特别注意 有的同学使用文件上传遇到了 诡异的问题 如果你要用 Ioc 来管理 UploadAdaptor,你必须为不同的入口函数分配不同的实例。 当然这些实例可以引用同样的 uploadFileContext 和 tmpFilePool。 否则我们不能保证适配的结果正确。

看到这里,有些同学会问了,为什么你用注解的方式可以这么写:

@AdaptBy(type = UploadAdaptor.class, args = { "${app.root}/WEB-INF/tmp" })

'`{app.root}' 用起来多方便啊,那么在 Ioc 容器来,我的临时目录还想放在这里,有没有 什么办法啊?

办法有。但是因为 Ioc 容器原来设计的是同 Mvc 的 Web 容器环境不相干的,所以要想绕过了这点 限制,会稍微的有一点麻烦。

首先,因为 Ioc 容器设计的良好扩展性,所有实现了 'org.nutz.ioc.Ioc2' 这个接口的 Ioc 容器(当然,Nutz 自带的 Ioc 容器 - NutzIoc,很好的实现了 Ioc2 这个接口)都可以被 Nutz.Mvc 注入一种新的获取值的方式(详情请参看 with_ioc.man#在容器对象里获得_ServletContext), 我想你只需要实现一个简单的 Java 对象即可:

package com.abc.myapp;

import javax.servlet.ServletContext;

public class MyUtils {

    private ServletContext sc;

    public String getPath(String path) {
        return sc.getRealPath(path);
    }
}

然后,你可以在 Ioc 容器的配置文件里,增一个对象,并修改 'tmpFilePool' 对象的配置信息:

utils : {
    type : 'com.abc.myapp.MyUtils',
    fields : {
        sc : {app:'$servlet'}   // 将 ServletContext 对象注入 MyUtils
    }
},

tmpFilePool : {
    type : 'org.nutz.filepool.NutFilePool',
    args : [ {java:'$utils.getPath("/WEB-INF/tmp")'}, 1000 ]   // 调用 MyUtils.getPath 函数
},

在入口函数的参数里获得上传的文件

因为 FORM 表单里的 input type=file 项都已经被 UploadAdaptor 解析并存入服务器的临时目录 (你声明的那个目录,比如上例是 /WEB-INF/tmp),你在入口函数里可以直接获取:

@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
public void uploadPhoto( @Param("photo") File f){
    ...

你的参数 f 将获取到表单项中:

<form target="hideWin" enctype="multipart/form-data" method="post">
    <input type="file" name="photo">
    ...

中的文件内容。你直接打开一个 InputStream 就能从这个 File 中读取客户端上传的文件内容了。

但是,如果你也想知道这个文件在客户端原本的名字,怎么办呢?你可以:

@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
public void uploadPhoto( @Param("photo") TempFile tf){
    File f = tf.getFile();                       // 这个是保存的临时文件
    FieldMeta meta = tf.getMeta();               // 这个原本的文件信息
    String oldName = meta.getFileLocalName();    // 这个时原本的文件名称
    // TODO do what you wanna to do here ...
}

一次上传多个同名文件

相信很多同学都会有这样的用法,就是,希望一次上传的表单为:

<form target="hideWin" enctype="multipart/form-data" method="post">
    <input type="file" name="photo">
    <input type="file" name="photo">
    <input type="file" name="photo">
    ...

看,一个上传表单中带有多个同名文件,那么怎么办呢? 你在入口函数里可以这么声明:

@AdaptBy(type = UploadAdaptor.class, args = { "ioc:myUpload" })
public void uploadPhoto( @Param("photo") TempFile[] tfs){
    ...
}

是的,它们会被变成一个数组。顺序同 <form> 中的顺序。

但是这里,有一点限制

  • 你只能用 `TempFile[]`

也就是说,如果你用了 File[] 将会出错。原因不解释。

Html5流式上传(实验性)

1.b.44及之后的版本,同时兼容Html5流式上传. 但,请注意以下事项:

  • 上传的Content-Type必须是application/octet-stream
  • 必须带Content-Length
  • 默认情况下,对于的表单属性是filedata,除非在Content-Disposition另外声明
  • 不支持Content-Type过滤,因为上传数据中通常不携带这种信息
  • 支持文件名过滤,但请留意默认文件名nutz.jpg

可用的参数类型

  • TempFile 推荐
  • File 已废弃
  • FileMeta 已废弃
  • InputStream 不推荐
  • Reader 不推荐
  • Map 用于得到全部参数
过滤器
请求范围的模块
温馨提示
下载编程狮App,免费阅读超1000+编程语言教程
取消
确定
目录

表达式引擎

maplist结构

图像处理小军刀

关闭

MIP.setData({ 'pageTheme' : getCookie('pageTheme') || {'day':true, 'night':false}, 'pageFontSize' : getCookie('pageFontSize') || 20 }); MIP.watch('pageTheme', function(newValue){ setCookie('pageTheme', JSON.stringify(newValue)) }); MIP.watch('pageFontSize', function(newValue){ setCookie('pageFontSize', newValue) }); function setCookie(name, value){ var days = 1; var exp = new Date(); exp.setTime(exp.getTime() + days*24*60*60*1000); document.cookie = name + '=' + value + ';expires=' + exp.toUTCString(); } function getCookie(name){ var reg = new RegExp('(^| )' + name + '=([^;]*)(;|$)'); return document.cookie.match(reg) ? JSON.parse(document.cookie.match(reg)[2]) : null; }