• Struts2 文件上传


    上传单个文件

    表单:

    <s:form action="HandlerAction" method="POST" enctype="multipart/form-data">
        <s:file name="profile" label="头像" />
    <%-- <s:file name="profile" label="头像" accept="image/jpeg" size="102400" /> --%>
        <s:submit value="提交" />
    </s:form>
    • 需要设置 method="POST"    enctype="multipart/form-data"  。
    • 可以使用struts2的表单标签,也可以使用html的表单标签。html表单标签支持的属性要多一些,比如说html标签支持multiple属性。
    • accept、size属性并不能限制上传文件的类型、大小。accept只是在文件选择器中指定文件类型,仍然可以切换为*(所有类型)。size属性无效。

    Action:

    public class HandlerAction extends ActionSupport {
        private File profile;
        private String profileFileContentType;
        private String profileFileName;
    
        public File getProfile() {
            return profile;
        }
    
        public void setProfile(File profile) {
            this.profile = profile;
        }
    
        public String getProfileFileContentType() {
            return profileFileContentType;
        }
    
        public void setProfileFileContentType(String profileFileContentType) {
            this.profileFileContentType = profileFileContentType;
        }
    
        public String getProfileFileName() {
            return profileFileName;
        }
    
        public void setProfileFileName(String profileFileName) {
            this.profileFileName = profileFileName;
        }
    
        @Override
        public String execute() throws Exception {
            System.out.println("开始上传");
    
            //取session中取出user,保存在user目录下。getRealPath()取的是绝对路径。
            String user = (String) ServletActionContext.getRequest().getSession().getAttribute("user");
            String dirPath = ServletActionContext.getServletContext().getRealPath("/upload") + "/" + user;
    
            File dir = new File(dirPath);
            if (!dir.exists()){
                dir.mkdirs();
            }
    
            File file = new File(dirPath + "/" + profileFileName);
            //将临时文件保存到指定文件中(正式文件)
            profile.renameTo(file);
            //虽然gc每隔一段时间会自动清理,但使用完就删除是最好的。
            profile.delete();
    
            System.out.println("上传成功");
            return "success";
        }
    }
    • 需提供一个File类型的成员变量,用于保存临时文件。
    • 还需要提供2个对应的成员变量:FileContextType、FileName,用于保存文件类型、文件名,约定命名方式为:File变量名+FileContextType | FileName。
    • 需要给这三个成员变量提供getter、setter方法。

    此种方式只能上传单个文件,不能一次上传多个文件。

    此种方式不能上传较大的文件,超过4M的文件就上传不了。


    限制上传文件的类型、大小

    在struts.xml中给处理文件上传的action配置拦截器:

    <action name="HandlerAction" class="action.HandlerAction">
          <interceptor-ref name="fileUpload">
               <param name="allowedTypes">image/jpeg,image/png,image/gif</param>
               <!-- <param name="allowedExtensions">jpg,png,gif</param> -->
               <param name="maximumSize">102400</param>
          </interceptor-ref>
          <interceptor-ref name="defaultStack"></interceptor-ref>
    
         <result name="success">/success.jsp</result>
    </action>

     注意:fileUpload拦截器的引用必须位于默认拦截器栈之前,否则能上传文件,但起不到限制文件类型、大小的作用。

    原理分析

     鼠标点击 <interceptor-ref name="fileUpload"> 中的fileUpload,Ctrl+B查看此拦截器的定义:

    public class FileUploadInterceptor extends AbstractInterceptor {
        private static final long serialVersionUID = -4764627478894962478L;
        protected static final Logger LOG = LogManager.getLogger(FileUploadInterceptor.class);
        protected Long maximumSize;
        protected Set<String> allowedTypesSet = Collections.emptySet();
        protected Set<String> allowedExtensionsSet = Collections.emptySet();
        private ContentTypeMatcher matcher;
        private Container container;
    
        public FileUploadInterceptor() {
        }

     我们在struts.xml中使用<param>传入的文件类型、扩展名、最大尺寸这些参数,执行时会调用此拦截器类的setter方法,赋给对应的成员变量,再根据这些成员变量的值来检测上传文件是否符合要求。

     上面有一个成员变量 private ContentTypeMatcher matcher; 可用于检测上传文件的内容,比如检测上传的视频是否包含色情画面。

    为什么fileUpload拦截器的引用必须位于默认拦截器栈之前?

    此拦截器类中有一个static、final类型的成员变量:

    private static final long serialVersionUID = -4764627478894962478L;

    这个UID是此拦截器类共有的,且初次赋值后不能再被修改,用于检测此拦截器是否已执行。

    上传一个文件时,会先调用我们配置的fileUpload拦截器来检测文件是否符合要求,然后再调用默认的拦截器栈。默认的拦截器栈中有fileUpload拦截器。

    JVM先if判断UID是不是long型的默认初始值:

    • 是:说明此拦截器尚未被调用,则调用此拦截器
    • 不是:说明此拦截器已被调用过,则不再调用此拦截器

    如果默认拦截器栈在前,里面的fileUplaod是未配置参数的,使用默认值,默认有大小限制,类型、扩展名可以是任意的。

    执行我们自己配置的fileUplaod拦截器时,JVM一检测UID,认为此拦截器执行过了,直接跳过(就像过安检,按流程走一次就ok,不会让你再来一次),我们自己配置的fileUpload是没有起到作用的。

    上传多个文件时,上传的每个文件都会被fileUpload拦截器拦截(进站的每个人都需要过安检)。

    如果不用限制上传文件的类型、大小,则不必自己配置fileUpload拦截器,使用默认的拦截器栈就ok。

    fileUpload拦截器是有默认参数的,如果自己没有配置fileUpload拦截器的参数,默认上传文件的最大尺寸为4M,超过4M直接进入服务器报错页面。


    设置上传文件在服务器上的保存位置

    把上传文件在服务器上的保存位置写死在代码中,后续维护时如果要改保存位置,运维看不懂代码,还得让你来改,很麻烦。

    通常的做法是:

    • 在处理上传文件的action中设置一个成员变量,比如savePath,用来表示上传文件的保存位置,并提供getter、setter方法
    • 在struts.xml配置action时,使用<param>传入文件的保存位置
    <action name="HandlerAction" class="action.HandlerAction">
         <!-- 注意路径中的要写成\或者/ --> <param name="savePath">D:/upload</param>
    <interceptor-ref name="fileUpload"> <param name="allowedTypes">image/jpeg,image/png,image/gif</param> <param name="maximumSize">102400</param> </interceptor-ref> <interceptor-ref name="defaultStack"></interceptor-ref>
    <result name="success">/success.jsp</result> </action>

    同时上传多个文件

    表单

    • 同时指的是表单一次提交多个文件,可以用多个<s:file />或<input type="file" />:
    <s:form action="HandlerAction" method="POST" enctype="multipart/form-data">
        <s:file name="upload" label="上传文件一" />
        <s:file name="upload" label="上传文件二" />
        <s:submit value="提交" />
    </s:form>

    是把多个文件保存到一个数组中,一个文件即一个数组元素。注意name相同,都是action中的数组名。

    • 也可以在一个文件选择器中Ctrl选择多个文件:
    <input type="file" name="upload" multiple="multiple">

    <s:file />没有multiple属性,一个<s:file />只能选择1个文件,要一次性选择多个文件,需要使用<input />。

    • 以上两种方式可以一起使用。

    action

    public class HandlerAction extends ActionSupport {
        private File[] upload;
        private String[] uploadFileContentType;
        private String[] uploadFileName;
    
        public File[] getUpload() {
            return upload;
        }
    
        public void setUpload(File[] upload) {
            this.upload = upload;
        }
    
        public String[] getUploadFileContentType() {
            return uploadFileContentType;
        }
    
        public void setUploadFileContentType(String[] uploadFileContentType) {
            this.uploadFileContentType = uploadFileContentType;
        }
    
        public String[] getUploadFileName() {
            return uploadFileName;
        }
    
        public void setUploadFileName(String[] uploadFileName) {
            this.uploadFileName = uploadFileName;
        }
    
        private String savePath;
    
        public String getSavePath() {
            return savePath;
        }
    
        public void setSavePath(String savePath) {
            this.savePath = savePath;
        }
    
    
        @Override
        public String execute() throws Exception {
            File dir = new File(savePath);
            if (!dir.exists()){
                dir.mkdirs();
            }
    
            if(upload!=null) {
                for (int i = 0; i < upload.length; i++) {
                    File file = new File(savePath + "/" + uploadFileName[i]);
                    //将临时文件保存到指定文件中(正式文件)
                    upload[i].renameTo(file);
                    //删除临时文件
                    upload[i].delete();
                    System.out.println(uploadFileName[i]+"上传成功");
    
                }
            }
    
            return "success";
        }
    
    }

    换成数组罢了,处理时遍历数组即可。

    struts.xml

    <action name="HandlerAction" class="action.HandlerAction">
                <param name="savePath">D:/upload</param>
                
                <interceptor-ref name="fileUpload">
                    <param name="maximumSize">102400000</param>
                </interceptor-ref>
                <interceptor-ref name="defaultStack"></interceptor-ref>
                
                <result name="success">/success.jsp</result>
           <result name="input">/upload.jsp</result> </action>

    使用input指定表单页面,上传出错时回显到表单页面,并显示错误信息。


    防止上传文件重名

    常用的有2种方式

    • 在文件名中加时间戳(毫秒)。当并发量很大时,依然可能重名。
    • 使用UUID,即在原文件名上加UUID.randomUUID().toString() 

    UUID即通用唯一识别码,能唯一标识某个东西。

    UUID产生的这个字符串包含32个十六进制数,用4根连词线-分为5段,示例: bd95572b-7fcf-46b2-ae3e-6087d66db40f ,8-4-4-4-12的形式

  • 相关阅读:
    对象布局已知时 C++ 对象指针的转换时地址调整
    采用栈数据结构的二叉树非递归遍历
    ZOJ 3481. Expand Tab
    “金山杯2007逆向分析挑战赛”第一阶段第二题
    “金山杯2007逆向分析挑战赛”第一阶段第一题分析
    对《神奇的C语言》文中例子 5 代码的分析讨论
    对"QQGame-大家来找茬"的辅助工具的改进
    memset 的实现分析
    ZOJ 1958. Friends
    HBase中MVCC的实现机制及应用情况
  • 原文地址:https://www.cnblogs.com/chy18883701161/p/11487730.html
Copyright © 2020-2023  润新知