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

xiaoxiao2021-03-01  37

目录

开发之前的一些注意点domain设计:JdbcUtils设计dao层设计services层设计web层设计


开发之前的一些注意点

创建数据库的时候,指定码表,避免本地正常,发布到服务器上的时候出现乱码 ;

创建表的时候,id选用UUID,是为了以后合并表的时候,id不会冲突;(主要考虑是否有合并的可能性,没有合并的可能性,选择自增长的int)

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

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

数据库建库 规定字符集为UTF-8:

CREATE DATABASE test CHARACTER SET utf8 COLLATE utf8_general_ci;

建表 规定字符集为UTF-8 :

ENGINE=InnoDB DEFAULT CHARSET=utf8;

从request中获取参数的时候,如果Javabean对应的属性,没有提交过来的参数。Beautil 会为对应的属性赋予默认值 ;

建表语句(实例):

最后加上规定字符集的语句 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设计:

对文件的描述:

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

分页的设计

封装查询信息:

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连接池,注意配置文件的存放位置,在包的根目录下面;构造器不写参数,就是读取默认配置)

使用 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(); } } }

将request中的数据封装到Javabean中;

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

ShowUtils

将request中的查询信息封装到QueryInfo中,便于分页;,写一个工具类,就避免了我们为QueryInfo对象的每一个属性,一一赋值; 然后通过services获取到PageBean,传给显示的JSP;

将上传的文件保存到服务器硬盘上,并且返回一个bean对象;

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

计算文件分配路径

通过文件名的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("\\+"," "); } // 文件名有空格。火狐则会截断文件名,需要将文件名用字符串包起来 // 告诉浏览器下载方式打开., response.addHeader("Content-Disposition", "attachment;filename=\"" + simpleName+"\""); ·删除文件 通过客户端传来的UUID,调用services层删除对应的文件;删除的时候注意判断下,文件是否存在; ·更新文件 通过UUID获取到文件信息,将文件信息回显到表格中,便于修改信息;
转载请注明原文地址: https://www.6miu.com/read-4150083.html

最新回复(0)