• (二十七)文件上传下载开发流程(含部分代码)


    目录


    开发之前的一些注意点

    1. 创建数据库的时候,指定码表,避免本地正常,发布到服务器上的时候出现乱码 ;
    2. 创建表的时候,id选用UUID,是为了以后合并表的时候,id不会冲突;(主要考虑是否有合并的可能性,没有合并的可能性,选择自增长的int

    3. table打开边框的时候,使用 frame="boder" 打开,这样即使没数据也会显示空的单元格;

    4. 删除文件的时候,将删除数据记录和删除硬盘文件放在一个事务里面;并且先删除数据库记录,后删除文件 ;假如先删除文件,后删除记录,再删除记录的时候,抛出异常,则数据库回滚,这时候文件已经被删除了,是不能被回滚的,就是操作数据库会出现异常,要保证异常发生时,2者统一;
    5. 数据库建库 规定字符集为UTF-8

      CREATE DATABASE test CHARACTER SET utf8 COLLATE
      utf8_general_ci;
    6. 建表 规定字符集为UTF-8

      ENGINE=InnoDB DEFAULT CHARSET=utf8;
    7. request中获取参数的时候,如果Javabean对应的属性,没有提交过来的参数。Beautil 会为对应的属性赋予默认值

    8. 建表语句(实例):

      最后加上规定字符集的语句
      
      create table file
      (
       uuid varchar(40) unique ,
       simpleName varchar(20) not null,
       uploaderName varchar(10) not null,
       uploadDate date not null,
       description varchar(40) not null,
       savePath varchar(100) not null 
      ) ENGINE=InnoDB DEFAULT CHARSET=utf8 ;

    domain设计:

    1. 对文件的描述:

      File类 : 属性包括:上传者姓名上传时间文件名文件UUID文件保存路径文件描述

    2. 分页的设计

      封装查询信息:

      QueryInfo 对象 : 属性包括:页面数据量查询的页码数据库查询起始下标

      其中页面数据量和起始下标有个默认值,不指定就以该默认值为准;

      public class QueryInfo {
      private int startIndex ;
      private int pageSize = 5 ;
      private int currentPage = 1 ;
      //   起始坐标,通过当前页和页面显示数据量计算得出
      public int getStartIndex() {
          startIndex = (currentPage - 1) * pageSize ;
          return startIndex;
      }
      ....
      
      ....
      
      }

      QueryResult 对象 :属性包括:保存查询到的文件对象的集合、数据库数据总量

      PageBean 对象 :(属性就是页面要显示的数据)

      1、当前页面(也就是QueryInfo的查询的页码),为了显示当前在第几页;  
      
      2、页面显示的数据量(就是QueryInfo里面的页码数据量),    
      
      为了显示当前页面显示多少数据量,也为了翻页的pageSize的属性自动赋值 ;
      
      3、总页数(根据总条数除以页面显示数量,计算得到)
      
      // 计算总页数
      public int getTotalPage() {
          totalPage = totalRecord % pageSize == 0 ? totalRecord / pageSize : totalRecord / pageSize + 1;
          return totalPage;
      }
      4、页码条
      
              先设置一个开始坐标和一个结束坐标;初始值都是0;
      
              然后判断页码总长度是否大于我们设定的页码条的长度;
      
              如果不大于,则页码条就是当前的页面数,无需做特殊处理;
      
              如果大于,则开始坐标是当前页面坐标减去页码条长度的一半,
      
              结束坐标是当前页面坐标加上页码条长度的一半;  
      
              然后判断,开始坐标是否小于1,如果小于,则令开始坐标为1,结束坐标为页码条的长度;
      
              结束坐标是否大于总页面数,如果大于,则令结束坐标为页面总长度,开始坐标为页面总长度减去页码条长度;
      
      // 计算页码条
      public int[] getPageBar() {
          int startIndex = 0;
          int endIndex = 0;
      //        默认显示 5 个页码条
          if (getTotalPage() < 5) {
              pageBar = new int[getTotalPage()];
              startIndex = 1;
              endIndex = getTotalPage();
          } else {
              pageBar = new int[5];
              startIndex = currentPage - 2;
              endIndex = currentPage + 2;
      //          判断是否越界
              if (startIndex < 1) {
                  startIndex = 1;
                  endIndex = startIndex + 4;
              }
      
              if (endIndex > getTotalPage()) {
                  endIndex = getTotalPage();
                  startIndex = getTotalPage() - 4;
              }
      
          }
      
          int index = 0;
          for (int i = startIndex; i <= endIndex; i++) {
              pageBar[index++] = i;
          }
      
          return pageBar;
      }
      5、页面显示的文件对象集合
      
      6、上一页、下一页
      
      7、总记录数。(计算总页数需要用到)
      

    JdbcUtils设计

    (使用C3P0连接池,注意配置文件的存放位置,在包的根目录下面;构造器不写参数,就是读取默认配置)
    
    1. 使用 ThreadLocal 类,实现事务;

              getDataSource() :获取连接池
      
              getConnecetion() : 获取连接 ,获取之前,先判断当前线程上是否有绑定的连接  ;
      
              stratTransaction() : 开启事务,先获取连接,然后绑定到当前线程上,然后开启事务 ;
      
              commitTranscation(): 提交事务,获取当前线程上的连接,然后提交事务 ;
      
              closeConnecetion() :  关闭连接,已经移除ThreadLocal里面的连接  ;
      
    
    public class JdbcUtils {
    
        private static ThreadLocal<Connection> threadLocal = new ThreadLocal<>();
        private static ComboPooledDataSource dataSource;
    
        static {
            try {
                //        没有参数,则使用默认的配置,创建数据库连接池
                dataSource = new ComboPooledDataSource();
            } catch (Exception e) {
    //            抛出初始化异常
                throw new ExceptionInInitializerError(e);
            }
        }
    
        /**
         * 获取数据库连接池
         *
         * @return
         */
        public static DataSource getDataSource() {
            return dataSource;
        }
    
    
        /**
         * 获取连接,底层也是从数据库里面获取连接,
         * <p>
         * 判断当前线程上有没有连接,便于开启事务
         *
         * @return
         * @throws SQLException
         */
        public static Connection getConnection() {
            Connection connection = threadLocal.get();
            try {
                if (connection == null) {
                    connection = dataSource.getConnection();
                }
                return connection;
            } catch (SQLException e) {
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 为当前线程开启事务操作
         */
        public static void startTransaction() {
            Connection connection = threadLocal.get();
            try {
                if (connection == null) {
                    connection = dataSource.getConnection();
                    threadLocal.set(connection);
                }
                connection.setAutoCommit(false);
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e);
            }
        }
    
        /**
         * 提交事务
         */
        public static void commitTransaction(){
            Connection connection = threadLocal.get() ;
            if (connection == null) {
                return;
            }
            try {
                connection.commit();
            } catch (SQLException e) {
                e.printStackTrace();
                throw new RuntimeException(e) ;
            }
        }
    
        /**
         * 使用了连接池以后,不需要我们去关注连接的关闭问题了。
         *
         * 但是使用了事务的连接,还是需要我们自己去关闭的 ;
         */
        public static void closeConnection(){
            Connection connection = threadLocal.get() ;
            try {
                if (connection != null) {
                    connection.close();
                }
            }catch (SQLException e){
                e.printStackTrace();
                throw new RuntimeException(e) ;
            }finally {
                threadLocal.remove();
            }
    
        }
    
    
    }
    1. request中的数据封装到Javabean中;

              使用Beanutils;
      
              先将request中的参数,封装到map集合中:request.getParameterMap() ;
      
              再调用Beanutils的方法(BeanUtils.populate(bean,map)),将map中的元素,封装到Javabean中;
      
              注意,如果request没有对应的属性,则属性被赋予默认值;
      
    2. ShowUtils

              将request中的查询信息封装到QueryInfo中,便于分页;,写一个工具类,就避免了我们为QueryInfo对象的每一个属性,一一赋值;
      
              然后通过services获取到PageBean,传给显示的JSP;
      
    3. 将上传的文件保存到服务器硬盘上,并且返回一个bean对象;

              首先通过文件的item,获取文件的名字,如果为空串,则表示没有上传文件;
      
              检查文件后缀名是否允许上传;合法,就将其保存到文件对象的属性中,不合法则抛出自定义的后缀名异常;
      
              然后通过文件名字,进行分配文件保存路径;还是保存到文件对象的Javabean上;
      
              获取UUID、日期,都保存到文件对象的Javabean上;
      
              最后将文件写到服务器硬盘上,注意的是,数据库里面仅仅保存文件保存的路径,而不真正的保存文件;
      
              写文件,通过文件的item,获取输入流,然后new FileOutputStream 参数是 路径_文件名 ; 跟java中IO一样,这里参数写上绝对路径,假如写上相对路径,
      
                  在JSP、servlet中写上相对路径,相对的是tomcat的bin路径,因为这是在J2E项目中;
      
    4. 计算文件分配路径

              通过文件名的hashcode,分配文件路径;跟之前讲的分配算法是一样的思路;
      
     /**
         * 最多可以保存8层,这样的算法,这里仅取两层
         *
         * @param name
         * @return WEB-INF/upload/5/6
         */
        public static String getPath(String path, String name) {
    
            int hashcode = name.hashCode();
    
            int one = hashcode & 0xf;
            int two = (hashcode >> 4) & 0xf;
    
            File file = new File(path + File.separator + one + File.separator + two);
    
            if (!file.exists()) {
                file.mkdirs();
            }
    
            return file.getPath();
        }

    dao层设计

            add(File) : 往数据库添加文件 ;
    
            delete(UUID); 根据uuid删除数据库记录,已经本地磁盘保存的文件;
    
                        用事务操作,以保证操作的原子性,并且对本地文件的操作,需要放在操作数据库之前,
    
                        因为一旦数据库出现问题,可以回滚 ,而本地文件一旦执行操作是无法回滚的,
    
                        因此,先执行数据库操作,错误发生,代码停止执行,这样避免执行对本地的操作;
    
            update(File) : 更新数据库记录,以及本地磁盘文件的文件名;
    
                        如果需要的话,也需要开启事务 ,改名字使用 原文件.renameTo(想要给成的样子的文件);
    
            getFiles(QueryInfo) ; 返回一个QueryResult, 根据起始下标,查询数据,一页显示的数据,总记录数;
    
                        总记录用ScalarHandler<T>() 类,它将查询的结果以泛型的类型返回,注意的是count(*)返回的是long类型 ;
    
            find(UUID): 根据UUID 查询对应的file对象 ;
    
            // 查询总记录数
            sql = "select count(*) from file";
            int count = runner.query(sql, new ScalarHandler<Long>()).intValue();

    services层设计

            增删改查,直接调用dao层方法即可,注意删除和更改的时候,需要开启事务;
    
            pageBean对象,从pageResult对象中获取四个属性,剩下的属性,通过计算得出;
    
            总记录数,页面数据、当前页,页面显示数量,从pageResult中获取;上一页、下一页、总页数、页码条,通过计算得出;
    

    web层设计

            ·上传文件
    
                先利用ServletFileUpload判断表单类型;
    
                然后获取工厂、解析器、设置解析器的码表,这里的码表设定,仅仅能解决字段名字的乱码,字段内容的乱码,等下还需要设定码表;
    
                解析客户端传来的流,判断是普通字段还是文件,对文件进行保存;这里使用2次for循环,第一次将普通字段保存下来,第二次将文件保存下来;
    
    
    
    
            ·显示文件
    
                获取到PageBean 对象,在JSP中显示,其中点击函数的参数可以这样写:onclick="go2Page(document.getElementById('goPage').value)" ;通过获取页面的节点值;
    
                控制浏览器URI地址 : window.location.href = '${pageContext.request.contextPath}/show?currentPage=' + currentPage + '&pageSize=' + value; 
    
                显示的时候,超链接后面挂的都是文件的UUID,便于在数据可中检索文件;
    
    
    
            ·下载文件
    
                通过UUID获取到文件,判断是否为空,为空则文件已经被删除了,告知来晚了,文件已被删除!
    
                通过数据库查询出来的File对象,获取其path属性,得到文件保存的实际位置;
    
                对文件的名字,进行处理,避免文件名乱码;
    
            // 下载文件名乱码解决方法
            String path = file.getSavePath();
                String simpleName = file.getSimpleName();
    
    //            浏览器分为 火狐 和 非火狐
                if (request.getHeader("USER-AGENT").toLowerCase().indexOf("firefox") >= 0) {
                    // 火狐头大,需要独特设置一下
                    simpleName = new String(simpleName.getBytes("UTF-8"), "iso-8859-1");
                } else {
                    simpleName = URLEncoder.encode(simpleName, "UTF-8");
    //                IE 文件名有空格会被加号代替。需要自己替换回去
                    simpleName = simpleName.replaceAll("\+","%20");
                }
    //            文件名有空格。火狐则会截断文件名,需要将文件名用字符串包起来
                //        告诉浏览器下载方式打开.,
                response.addHeader("Content-Disposition", "attachment;filename="" + simpleName+""");
            ·删除文件
    
                通过客户端传来的UUID,调用services层删除对应的文件;删除的时候注意判断下,文件是否存在;
    
    
            ·更新文件
    
                通过UUID获取到文件信息,将文件信息回显到表格中,便于修改信息;
    
  • 相关阅读:
    社区运营一点事
    从拉动APP下载谈运营
    c#基础学习(0702)之面向对象和方法重写概述
    c#基础学习(0706)之使用虚方法实现多态
    c#基础学习(0703)之string.Format格式化日期
    c#基础学习(0701)之一些简单的方法练习
    c#基础学习(0630)之面向对象总习
    c#基础学习(0629)之导出Excel方法
    c#基础学习(0628)之使用进程打开指定的文件、模拟磁盘打开文件
    c#基础学习(0627)之类型转换、算数运算符++、--
  • 原文地址:https://www.cnblogs.com/young-youth/p/11665694.html
Copyright © 2020-2023  润新知