From 63ce73eecc12ea7e7a476233219b980f0f39f08f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Wed, 22 Jun 2016 16:49:07 +0800 Subject: [PATCH 01/29] =?UTF-8?q?=E5=88=A0=E9=99=A4=E6=97=A0=E7=94=A8?= =?UTF-8?q?=E6=B3=A8=E9=87=8A=EF=BC=8C=E7=B2=BE=E7=AE=80=E4=BB=A3=E7=A0=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WEB-INF/views/BigFileUpload/Index.jsp | 60 ------------------- 1 file changed, 60 deletions(-) diff --git a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp index 04edb43..909700f 100644 --- a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp +++ b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp @@ -10,7 +10,6 @@ - <%-- + + + + + + + + + + +
+
+
+
+
+
+
+
+ 选择文件 + +
+
+ +
开始上传
+
+
+
+ + + diff --git a/src/main/webapp/WEB-INF/web.xml b/src/main/webapp/WEB-INF/web.xml index 1e2d979..9ee82f8 100644 --- a/src/main/webapp/WEB-INF/web.xml +++ b/src/main/webapp/WEB-INF/web.xml @@ -23,4 +23,21 @@ dispatcher / + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 308bb51..5c53dd9 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -7,7 +7,7 @@ FileUpload -文件上传 +文件上传 图片上传 大文件上传 From a1a985551b0b05fcf14a7375d7b6182c95689b65 Mon Sep 17 00:00:00 2001 From: Lemon2016 <18331503830@163.com> Date: Tue, 28 Jun 2016 16:17:24 +0800 Subject: [PATCH 07/29] =?UTF-8?q?=E4=BF=AE=E5=A4=8D=E4=BA=86=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E4=BF=9D=E5=AD=98=E5=A4=B1=E8=B4=A5=E7=9A=84=E9=97=AE?= =?UTF-8?q?=E9=A2=98?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileUpload/Java/Utils/SaveFile.java | 46 +++++++++++++------ 1 file changed, 32 insertions(+), 14 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 7c4780e..51f25a3 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -2,27 +2,45 @@ import org.springframework.web.multipart.MultipartFile; -import java.io.File; -import java.io.FileOutputStream; -import java.io.IOException; -import java.io.InputStream; +import java.io.*; public class SaveFile { - public static boolean saveFile(String localPath, String fileFullName, MultipartFile file) throws IOException { + public static boolean saveFile(String localPath, String fileFullName, MultipartFile file) throws Exception { + FileOutputStream outStream = null; try { - InputStream inputStream = file.getInputStream(); - int available = inputStream.available(); - byte[] inOutb = new byte[available]; - int read = inputStream.read(inOutb); - File outFile = new File(localPath + "/src/main/webapp/upload/" + fileFullName); - FileOutputStream outStream = new FileOutputStream(outFile); - outStream.write(read); - inputStream.close(); - outStream.close(); + byte[] data = readInputStream(file.getInputStream()); + //new一个文件对象用来保存图片,默认保存当前工程根目录 + File imageFile = new File(localPath + "/src/main/webapp/upload/" + fileFullName); + //创建输出流 + outStream = new FileOutputStream(imageFile); + //写入数据 + outStream.write(data); + outStream.flush(); + } catch (Exception e) { + e.printStackTrace(); throw e; + } finally { + outStream.close(); } return true; } + + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + //创建一个Buffer字符串 + byte[] buffer = new byte[1024]; + //每次读取的字符串长度,如果为-1,代表全部读取完毕 + int len = 0; + //使用一个输入流从buffer里把数据读取出来 + while ((len = inStream.read(buffer)) != -1) { + //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 + outStream.write(buffer, 0, len); + } + //关闭输入流 + inStream.close(); + //把outStream里的数据写入内存 + return outStream.toByteArray(); + } } From 9b8f42379935ba917ea71cbff81a1e7c4c1865b7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Fri, 1 Jul 2016 11:22:38 +0800 Subject: [PATCH 08/29] =?UTF-8?q?=E4=B8=8ESpringMVCSeedProject=E5=90=8C?= =?UTF-8?q?=E6=AD=A5?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Java/Controller/FileUploadController.java | 68 +- .../FileUpload/Java/Dao/BaseDao.java | 306 ++++--- .../FileUpload/Java/Dao/Query.java | 857 ++++++++++++++++++ .../FileUpload/Java/Service/BaseService.java | 163 ++-- src/main/resources/Spring.xml | 85 +- 5 files changed, 1184 insertions(+), 295 deletions(-) create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java index a07cdec..d56f7e5 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java @@ -20,36 +20,40 @@ @Controller @RequestMapping("/FileUpload") public class FileUploadController { - @Autowired - private FileService fileService; - - @RequestMapping(value = "/Index", method = RequestMethod.GET) - public String Index() { - return "FileUpload/Index"; - } - - @ResponseBody - @RequestMapping(value = "/FileUp", method = RequestMethod.POST) - public String fileUpload(@RequestParam("id") String id, - @RequestParam("name") String name, - @RequestParam("type") String type, - @RequestParam("lastModifiedDate") String lastModifiedDate, - @RequestParam("size") int size, - @RequestParam("file") MultipartFile file) { - String fileName = ""; - - try { - String path = FileUploadController.class.getResource("/").getFile(); - int build = path.indexOf("build"); - String realpath = path.substring(0, build); - String ext = name.substring(name.lastIndexOf(".")); - fileName = UUID.randomUUID().toString() + ext; - saveFile(realpath, fileName, file); - } catch (Exception ex) { - return "{\"error\":true}"; - } - fileService.save(new File(fileName, createMd5(file).toString(), new Date())); - - return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; - } + @Autowired + private FileService fileService; + + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Index() { + return "FileUpload/Index"; + } + + @ResponseBody + @RequestMapping(value = "/FileUp", method = RequestMethod.POST) + public String fileUpload(@RequestParam("id") String id, + @RequestParam("name") String name, + @RequestParam("type") String type, + @RequestParam("lastModifiedDate") String lastModifiedDate, + @RequestParam("size") int size, + @RequestParam("file") MultipartFile file) { + String fileName = ""; + + try { + String path = FileUploadController.class.getResource("/").getFile(); + int build = path.indexOf("build"); + String realpath = path.substring(0, build); + String ext = name.substring(name.lastIndexOf(".")); + fileName = UUID.randomUUID().toString() + ext; + saveFile(realpath, fileName, file); + } catch (Exception ex) { + return "{\"error\":true}"; + } + try { + fileService.save(new File(fileName, createMd5(file).toString(), new Date())); + } catch (Exception e) { + return "{\"error\":true}"; + } + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java index 6d55dbe..d06013a 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/BaseDao.java @@ -2,22 +2,14 @@ import com.zhangzhihao.FileUpload.Java.Utils.PageResults; -import org.hibernate.Criteria; -import org.hibernate.SQLQuery; -import org.hibernate.Session; -import org.hibernate.SessionFactory; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projection; -import org.hibernate.criterion.Projections; -import org.hibernate.transform.AliasToBeanResultTransformer; import org.jetbrains.annotations.NotNull; -import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.Primary; -import org.springframework.orm.hibernate5.HibernateTemplate; import org.springframework.stereotype.Repository; import org.springframework.transaction.annotation.Transactional; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; +import javax.persistence.TypedQuery; import java.io.Serializable; import java.util.List; @@ -27,48 +19,44 @@ * * @param 实体类型 */ -@SuppressWarnings({"rawtypes", "unchecked"}) -@Transactional +@SuppressWarnings({"unchecked"}) +@Transactional(timeout = 5) @Repository @Primary public class BaseDao { - private HibernateTemplate hibernateTemplate; + //获取到和当前事务关联的 EntityManager 对象 + //实际上是获得EntityManager的代理对象,是线程安全的 + @PersistenceContext + private EntityManager entityManager; - @Autowired - public void setSessionFactory(SessionFactory sessionFactory) { - this.hibernateTemplate = new HibernateTemplate(sessionFactory); - } /** - * 保存对象 + * 这个实体是否存在在数据库 * - * @param model 需要添加的对象 - * @return 是否添加成功 + * @param model 实体 + * @return 是否存在 */ - public Boolean save(@NotNull final T model) { - Serializable save = hibernateTemplate.save(model); - return save != null; + public boolean contains(@NotNull final T model) { + return entityManager.contains(model); } /** - * 添加并且返回Integer类型的ID + * 使实体变为不受管理的状态 * - * @param model 需要添加的对象 - * @return Integer类型的ID + * @param model 实体 */ - public Integer saveAndGetIntegerID(@NotNull final T model) { - return (Integer) hibernateTemplate.save(model); + public void detach(@NotNull final T model) { + entityManager.detach(model); } /** - * 添加并且返回String类型的ID + * 保存对象 * * @param model 需要添加的对象 - * @return String类型的ID */ - public String saveAndGetStringID(@NotNull final T model) { - return (String) hibernateTemplate.save(model); + public void save(@NotNull final T model) { + entityManager.persist(model); } /** @@ -78,7 +66,7 @@ public String saveAndGetStringID(@NotNull final T model) { * 失败会抛异常 */ public void saveAll(@NotNull final List modelList) { - modelList.stream().forEach(hibernateTemplate::save); + modelList.stream().forEach(entityManager::persist); } /** @@ -88,7 +76,7 @@ public void saveAll(@NotNull final List modelList) { * 失败会抛异常 */ public void delete(@NotNull final T model) { - hibernateTemplate.delete(model); + entityManager.remove(entityManager.contains(model) ? model : entityManager.merge(model)); } /** @@ -98,7 +86,7 @@ public void delete(@NotNull final T model) { * 失败会抛异常 */ public void deleteAll(@NotNull final List modelList) { - modelList.stream().forEach(hibernateTemplate::delete); + modelList.stream().forEach(this::delete); } /** @@ -108,38 +96,28 @@ public void deleteAll(@NotNull final List modelList) { * @param id 需要删除的对象的id * 失败抛出异常 */ - public void deleteById(final Class modelClass, @NotNull Serializable id) { - hibernateTemplate.delete(this.getById(modelClass, id)); + public void deleteById(final Class modelClass, @NotNull final Serializable id) { + this.delete(this.getById(modelClass, id)); } /** - * 更新对象 + * 更新或保存对象 * * @param model 需要更新的对象 * 失败会抛出异常 */ - public void update(@NotNull final T model) { - hibernateTemplate.update(model); + public void saveOrUpdate(@NotNull final T model) { + entityManager.merge(model); } /** - * 批量更新对象 + * 批量更新或保存对象 * - * @param modelList 需要更新的对象 + * @param modelList 需要更新或保存的对象 * 失败会抛出异常 */ - public void updateAll(@NotNull final List modelList) { - modelList.stream().forEach(hibernateTemplate::update); - } - - /** - * 添加或者更新 - * - * @param model 需要更新或添加的对象 - * 失败会抛出异常 - */ - public void saveOrUpdate(@NotNull final T model) { - hibernateTemplate.saveOrUpdate(model); + public void saveOrUpdateAll(@NotNull final List modelList) { + modelList.stream().forEach(entityManager::merge); } /** @@ -151,8 +129,7 @@ public void saveOrUpdate(@NotNull final T model) { */ @Transactional(readOnly = true) public T getById(Class modelClass, @NotNull final Serializable id) { - T t = hibernateTemplate.get(modelClass, id); - return hibernateTemplate.get(modelClass, id); + return entityManager.find(modelClass, id); } /** @@ -162,8 +139,9 @@ public T getById(Class modelClass, @NotNull final Serializable id) { * @return List */ @Transactional(readOnly = true) - public List loadAll(Class modelClass) { - return hibernateTemplate.loadAll(modelClass); + public List getAll(Class modelClass) { + Query query = new Query(modelClass, entityManager); + return entityManager.createQuery(query.createCriteriaQuery()).getResultList(); } @@ -182,130 +160,170 @@ public List getListByPage(Class modelClass, if (currentPageNumber <= 0 || pageSize <= 0) { return null; } - Criteria criteria = hibernateTemplate.getSessionFactory().getCurrentSession().createCriteria(modelClass); - criteria.setFirstResult((currentPageNumber - 1) * pageSize); - criteria.setMaxResults(pageSize); - return criteria.list(); - } - - /** - * 根据传来的参数生成 Criteria,是几个查询方法的封装 - * - * @param modelClass 类型,比如User.class - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param orders 查询后记录的排序条件,由Order对象生成 - * @param projections 分组和聚合查询条件,这里的条件只能是 Projections.projectionList().add(Property.forName("passWord").as("passWord")),详情参看测试用例 - * @return 查询结果 - */ - private Criteria makeCriteria(final Class modelClass, - @NotNull final Criterion[] criterions, - @NotNull final Order[] orders, - @NotNull final Projection[] projections) { - Criteria criteria = hibernateTemplate.getSessionFactory().getCurrentSession().createCriteria(modelClass); - //添加条件 - for (int i = 0; i < criterions.length; i++) { - criteria.add(criterions[i]); - } - //添加排序 - for (int i = 0; i < orders.length; i++) { - criteria.addOrder(orders[i]); - } - //添加分组统计 - for (int i = 0; i < projections.length; i++) { - criteria.setProjection(projections[i]); - } - return criteria; + Query query = new Query(modelClass, entityManager); + return entityManager.createQuery(query.createCriteriaQuery()) + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize) + .getResultList(); } /** - * 按条件分页,Criterion [URL]http://zzk.cnblogs.com/s?t=b&w=Criteria + * 按条件分页 * - * @param modelClass 类型,比如User.class * @param currentPageNumber 页码 * @param pageSize 每页数量 - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param orders 查询后记录的排序条件,由Order对象生成 - * @param projections 分组和聚合查询条件,这里的条件只能是 Projections.projectionList().add(Property.forName("passWord").as("passWord")),详情参看测试用例 + * @param query 封装的查询条件 * @return 查询结果 */ @Transactional(readOnly = true) - public PageResults getListByPageAndRule(Class modelClass, - @NotNull Integer currentPageNumber, - @NotNull Integer pageSize, - @NotNull final Criterion[] criterions, - @NotNull final Order[] orders, - @NotNull final Projection[] projections) { - Criteria criteria = makeCriteria(modelClass, criterions, orders, projections); - //参数验证 - int totalCount = getCountByRule(modelClass, criterions); + public PageResults getListByPageAndQuery(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull Query query) + throws Exception { + //获得符合条件的总数目 + int totalCount = getCountByQuery((Query) query.deepClone()); int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize : totalCount / pageSize + 1; + if (currentPageNumber > pageCount && pageCount != 0) { currentPageNumber = pageCount; } + TypedQuery typedQuery = query.createTypedQuery(); //查看是否要分页 - if (currentPageNumber >= 0 && pageSize >= 0) { - criteria.setFirstResult((currentPageNumber - 1) * pageSize); - criteria.setMaxResults(pageSize); - } - if (projections.length > 0) { - criteria.setResultTransformer(new AliasToBeanResultTransformer(modelClass)); + if (currentPageNumber > 0 && pageSize > 0) { + typedQuery + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize); } - List list = criteria.list(); - return new PageResults(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, list); + List list = typedQuery.getResultList(); + return new PageResults<>(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, list); } + /** + * 获得数量 利用Count(*)实现 + * + * @param modelClass 类型,比如User.class + * @return 数量 + */ + @Transactional(readOnly = true) + public int getCount(Class modelClass) { + Query query = new Query(modelClass, entityManager); + return getCountByQuery(query); + } /** * 获得符合对应条件的数量 利用Count(*)实现 * - * @param modelClass 类型,比如User.class - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; + * @param query 查询条件 * @return 数量 */ @Transactional(readOnly = true) - public int getCountByRule(Class modelClass, @NotNull final Criterion[] criterions) { - Criteria criteria = makeCriteria(modelClass, criterions, new Order[]{}, new Projection[]{Projections.rowCount()}); - long uniqueResult = 0; - try { - uniqueResult = (long) criteria.uniqueResult(); - } catch (Exception ex) { - uniqueResult = 0; + public int getCountByQuery(@NotNull final Query query) { + query.selectCount(); + return Integer.parseInt( + query.createTypedQuery() + .getSingleResult() + .toString() + ); + } + + /** + * 执行Sql语句 + * + * @param sql sql + * @param values 不定参数数组 + * @return 受影响的行数 + */ + public int executeSql(@NotNull String sql, @NotNull final Object... values) { + javax.persistence.Query nativeQuery = entityManager.createNativeQuery(sql); + for (int i = 0; i < values.length; i++) { + nativeQuery.setParameter(i, values[i]); + } + return nativeQuery.executeUpdate(); + } + + /** + * 通过jpql查询 + * + * @param jpql + * @param values + * @return + */ + @Transactional(readOnly = true) + public Object queryByJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); } - return (int) uniqueResult; + return query.getResultList(); } /** - * 获得统计结果 + * 获得符合对应条件的数量 利用Count(*)实现 * - * @param modelClass 类型,比如User.class - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param projections 分组和聚合查询条件 + * @param jpql jpql查询条件 * @return 数量 */ @Transactional(readOnly = true) - public List getStatisticsByRule(Class modelClass, - @NotNull final Criterion[] criterions, - @NotNull final Projection[] projections) { - Criteria criteria = makeCriteria(modelClass, criterions, new Order[]{}, projections); - return criteria.list(); + public int getCountByJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); + } + return query.getResultList().size(); } + /** + * 通过Jpql分页查询 + * + * @param currentPageNumber 当前页 + * @param pageSize 每页数量 + * @param jpql jpql语句 + * @param values jpql参数 + * @return 查询结果 + */ + @Transactional(readOnly = true) + public PageResults getListByPageAndJpql(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull final String jpql, + @NotNull final Object... values) { + //参数验证 + int totalCount = getCountByJpql(jpql, values); + int pageCount = totalCount % pageSize == 0 ? totalCount / pageSize + : totalCount / pageSize + 1; + + if (currentPageNumber > pageCount && pageCount != 0) { + currentPageNumber = pageCount; + } + + javax.persistence.Query query = entityManager.createQuery(jpql); + for (int i = 0; i < values.length; i++) { + query.setParameter(i, values[i]); + } + + //查看是否要分页 + if (currentPageNumber > 0 && pageSize > 0) { + query + .setFirstResult((currentPageNumber - 1) * pageSize) + .setMaxResults(pageSize); + } + List list = query.getResultList(); + return new PageResults<>(currentPageNumber + 1, currentPageNumber, pageSize, totalCount, pageCount, list); + } /** - * 执行Sql语句 + * 执行jpql语句 * - * @param sqlString sql - * @param values 不定参数数组 - * @return 受影响的行数 + * @param jpql + * @param values + * @return */ - public int executeSql(@NotNull String sqlString, @NotNull Object... values) { - Session session = hibernateTemplate.getSessionFactory().getCurrentSession(); - SQLQuery sqlQuery = session.createSQLQuery(sqlString); + public int executeJpql(@NotNull final String jpql, @NotNull final Object... values) { + javax.persistence.Query query = entityManager.createQuery(jpql); for (int i = 0; i < values.length; i++) { - sqlQuery.setParameter(i, values[i]); + query.setParameter(i, values[i]); } - return sqlQuery.executeUpdate(); + return query.executeUpdate(); } /** @@ -314,7 +332,9 @@ public int executeSql(@NotNull String sqlString, @NotNull Object... values) { * @param model 实体 */ public void refresh(@NotNull T model) { - hibernateTemplate.refresh(model); + entityManager.refresh(model); } + + } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java new file mode 100644 index 0000000..e2b62a4 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Dao/Query.java @@ -0,0 +1,857 @@ +package com.zhangzhihao.FileUpload.Java.Dao; + + +import org.jetbrains.annotations.NotNull; + +import javax.persistence.EntityManager; +import javax.persistence.TypedQuery; +import javax.persistence.criteria.*; +import javax.persistence.criteria.CriteriaBuilder.In; +import java.io.*; +import java.util.*; +import java.util.stream.Collectors; + +/** + * Query + * 封装JPA CriteriaBuilder查询条件 + */ +@SuppressWarnings({"unused", "unchecked", "rawtypes", "null", "hiding"}) +public class Query implements Serializable { + + private static final long serialVersionUID = 3366932251068926942L; + + private EntityManager entityManager; + + /** + * 要查询的实体类型 + */ + private Class clazz; + + /** + * 查询根,定义查询的From子句中能出现的类型。 + * Criteria查询的查询根定义了实体类型,能为将来导航获得想要的结果,它与SQL查询中的FROM子句类似。 + * Root实例也是类型化的,且定义了查询的FROM子句中能够出现的类型。 + * 查询根实例能通过传入一个实体类型给 AbstractQuery.from方法获得。 + * Criteria查询,可以有多个查询根。 + */ + private From from; + + /** + * 谓词,也就是过滤条件,用CriteriaBuilder生成。 + * 过滤条件应用到SQL语句的FROM子句中, + * 在Criteria 查询中,查询条件通过Predicate 或Expression 实例应用到CriteriaQuery 对象上。 + */ + private List predicates; + + /** + * 安全查询主语句 + */ + private CriteriaQuery criteriaQuery; + + /** + * 安全查询创建工厂。 + * CriteriaBuilder是一个工厂对象,安全查询的开始, + * 用于构建JPA安全查询,创建CriteriaQuery,创建查询具体具体条件Predicate 等。 + */ + private CriteriaBuilder criteriaBuilder; + + /** + * select条件,用于处理平均值之类的查询 + */ + private Selection selection; + + /** + * 排序方式列表 + */ + private List orders; + + /** + * 查询参数 + */ + private Map parameters; + + /** + * 子查询 + */ + private Map subQuery; + + /** + * 关联查询 + */ + private Map linkQuery; + + /** + * 子查询字段 + */ + private String projection; + + /** + * 或条件 + */ + private List orQuery; + + /** + * 分组条件 + */ + private String groupBy; + + private Query() { + } + + /** + * 创建查询条件 + * + * @param clazz 要查询的类型 + * @param entityManager EntityManager + */ + public Query(@NotNull Class clazz, @NotNull EntityManager entityManager) { + this.clazz = clazz; + this.entityManager = entityManager; + this.criteriaBuilder = this.entityManager.getCriteriaBuilder(); + this.criteriaQuery = criteriaBuilder.createQuery(this.clazz); + this.from = criteriaQuery.from(this.clazz); + this.predicates = new ArrayList(); + this.orders = new ArrayList(); + } + + /** + * 构造函数 + * + * @param entityManager EntityManager + */ + public Query(@NotNull EntityManager entityManager) { + this.entityManager = entityManager; + this.criteriaBuilder = this.entityManager.getCriteriaBuilder(); + this.predicates = new ArrayList(); + this.orders = new ArrayList(); + } + + /** + * FluentAPI 式构造函数 + * + * @param clazz 要查询的类型 + * @return 返回Query对象 + */ + public Query from(@NotNull Class clazz) { + this.clazz = clazz; + this.criteriaQuery = criteriaBuilder.createQuery(this.clazz); + this.from = criteriaQuery.from(this.clazz); + return this; + } + + /** + * 创建强类型查询,并且自动注入参数!!! + * 如果使用参数化自动注入的方法,必须调用此方法创建强类型查询 + * + * @return 返回强类型查询的实例 + */ + public TypedQuery createTypedQuery() { + TypedQuery typedQuery = entityManager.createQuery(this.createCriteriaQuery()); + if (parameters != null) { + for (ParameterExpression parameter : parameters.keySet()) { + typedQuery.setParameter(parameter, parameters.get(parameter)); + } + } + return typedQuery; + } + + /** + * 将ParameterExpression和对应的值添加进一个map里,最后在createTypedQuery时放入TypedQuery里面,完成参数化查询 + * + * @param parameter ParameterExpression参数 + * @param object 参数值 + */ + private void setParameter(@NotNull final ParameterExpression parameter, @NotNull final Object object) { + if (parameters == null) { + parameters = new HashMap<>(); + } + parameters.put(parameter, object); + } + + /** + * 自动创建ParameterExpression参数 + * + * @param value 参数值 + * @return 返回ParameterExpression参数 + */ + private ParameterExpression makeParameter(@NotNull final Object value) { + Class aClass = value.getClass(); + ParameterExpression parameter = this.createParameter(aClass); + setParameter(parameter, value); + return parameter; + } + + + /** + * 创建查询条件 + * + * @return JPA标准查询 + */ + public CriteriaQuery createCriteriaQuery() { + criteriaQuery.where(predicates.toArray(new Predicate[0])); + if (!isNullOrEmpty(groupBy)) { + criteriaQuery.groupBy(from.get(groupBy)); + } + if (orders != null) { + criteriaQuery.orderBy(orders); + } + if (selection != null) { + criteriaQuery.select(selection); + } + addLinkCondition(this); + return criteriaQuery; + } + + /** + * 参数化查询 + */ + public ParameterExpression createParameter(@NotNull Class clazz) { + return this.getCriteriaBuilder().parameter(clazz); + } + + /** + * 添加连接查询,在最后创建CriteriaQuery的时候调用 + * + * @param query query对象 + */ + private void addLinkCondition(@NotNull Query query) { + Map linkQuery = query.linkQuery; + if (linkQuery == null) + return; + for (Object o : linkQuery.keySet()) { + String key = o.toString(); + Query sub = (Query) linkQuery.get(key); + from.join(key); + criteriaQuery.where(sub.predicates.toArray(new Predicate[0])); + addLinkCondition(sub); + } + } + + /** + * 增关联查询 + */ + public void addLinkQuery(@NotNull final String propertyName, @NotNull Query query) { + if (this.linkQuery == null) + this.linkQuery = new HashMap(); + + this.linkQuery.put(propertyName, query); + } + + /** + * 增加子查询,必须设置子查询字段 projection + */ + private void addSubQuery(@NotNull final String propertyName, @NotNull Query query) { + if (this.subQuery == null) + this.subQuery = new HashMap(); + + if (query.projection == null) + throw new RuntimeException("子查询字段未设置"); + + this.subQuery.put(propertyName, query); + } + + /** + * 直接添加JPA内部的查询条件, + * 用于应付一些复杂查询的情况,例如 "或" + */ + public Query addCriterions(@NotNull Predicate predicate) { + this.predicates.add(predicate); + return this; + } + + + /** + * 增加升序还是降序排序,可以同时包含多个排序规则 + * + * @param propertyName 排序的属性名 + * @param order 排序方式 asc或desc + * @return 返回query实例 + */ + public Query addOrder(@NotNull final String propertyName, @NotNull final String order) { + if (this.orders == null) + this.orders = new ArrayList(); + + if (order.equalsIgnoreCase("asc")) + this.orders.add(criteriaBuilder.asc(from.get(propertyName))); + else if (order.equalsIgnoreCase("desc")) + this.orders.add(criteriaBuilder.desc(from.get(propertyName))); + return this; + } + + /** + * 设置升序还是降序排序,只能有一个排序规则 + * + * @param propertyName 排序的属性名 + * @param order 排序方式 asc或desc + * @return 返回query实例 + */ + public Query setOrder(@NotNull final String propertyName, @NotNull final String order) { + this.orders = null; + addOrder(propertyName, order); + return this; + } + + + /** + * 设置查询,此方法为高级用法,用于应付比较复杂的查询规则 + * 比如 Or Some Any + * + * @param selection 查询规则 + * @return query实例 + */ + public Query setSelect(Selection selection) { + this.selection = selection; + return this; + } + + /** + * 此方法只是为了读起来更有语义感,实际上不做任何工作,可以不用 + * + * @return query实例 + */ + public Query select() { + return this; + } + + /** + * 查询总数 + * + * @return query实例 + */ + public Query selectCount() { + Expression count = criteriaBuilder.count(from); + setSelect(count); + return this; + } + + /** + * 查询总数并去重 + * + * @return query实例 + */ + public Query selectCountDistinct() { + Expression count = criteriaBuilder.countDistinct(from); + setSelect(count); + return this; + } + + /** + * 查询特定属性的平均值 + * + * @param propertyName 要计算平均值的属性名 + * @return query实例 + */ + public Query selectAvg(@NotNull final String propertyName) { + Expression avg = criteriaBuilder.avg(from.get(propertyName)); + setSelect(avg); + return this; + } + + /** + * 查询特定属性的值之和 + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSum(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sum(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性值的和,并设置查询结果类型为Long + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSumAsLong(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sumAsLong(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性值的和,并设置查询结果类型为Double + * + * @param propertyName 要计算和的属性名 + * @return query实例 + */ + public Query selectSumAsDouble(@NotNull final String propertyName) { + setSelect(criteriaBuilder.sumAsDouble(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性的最大值 + * + * @param propertyName 要查询最大值的属性名 + * @return query实例 + */ + public Query selectMax(@NotNull final String propertyName) { + setSelect(criteriaBuilder.max(from.get(propertyName))); + return this; + } + + /** + * 查询特定属性的最小值 + * + * @param propertyName 要查询最小值的属性名 + * @return query实例 + */ + public Query selectMin(@NotNull final String propertyName) { + setSelect(criteriaBuilder.min(from.get(propertyName))); + return this; + } + + + /** + * 查找属性值等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.equal(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.equal(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值不等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereNotEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.notEqual(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值不等于特定值的实体 + * + * @param propertyName 要查询属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereNotEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.notEqual(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值小于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLessThanOrEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.le(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值小于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLessThanOrEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.le(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值小于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLessThan(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.lt(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值小于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLessThan(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.lt(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值大于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereGreaterThanOrEqual(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.ge(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值大于等于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereGreaterThanOrEqual(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.ge(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 查找属性值大于特定值的实体 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereGreaterThan(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.gt(from.get(propertyName), parameter)); + return this; + } + + /** + * 查找属性值大于特定值的实体 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereGreaterThan(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.gt(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 或者特定属性名等于特定值的实体 + * + * @param propertyName 要查询的属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereOr(@NotNull final List propertyName, @NotNull final ParameterExpression parameter) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(from.get(propertyName.get(0)), parameter)); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.equal(from.get(propertyName.get(i)), parameter)); + this.predicates.add(predicate); + return this; + } + + /** + * 或者特定属性名等于特定值的实体 + * + * @param propertyName 要查询的属性的属性名 + * @param value 属性值 + * @return query实例 + */ + public Query whereOr(@NotNull final List propertyName, @NotNull final Object value) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.equal(from.get(propertyName.get(0)), makeParameter(value))); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.equal(from.get(propertyName.get(i)), makeParameter(value))); + this.predicates.add(predicate); + return this; + } + + /** + * 模糊匹配 + * + * @param propertyName 属性名称 + * @param parameter 参数表达式 + */ + public Query whereLike(@NotNull final String propertyName, @NotNull final ParameterExpression parameter) { + this.predicates.add(criteriaBuilder.like(from.get(propertyName), parameter)); + return this; + } + + /** + * 模糊匹配 + * + * @param propertyName 属性名称 + * @param value 属性值 + */ + public Query whereLike(@NotNull final String propertyName, @NotNull final Object value) { + this.predicates.add(criteriaBuilder.like(from.get(propertyName), makeParameter(value))); + return this; + } + + /** + * 模糊查询,或者包含 + * + * @param propertyName 要查询的特定属性的属性名 + * @param parameter 参数表达式 + * @return query实例 + */ + public Query whereOrLike(@NotNull final List propertyName, @NotNull final ParameterExpression parameter) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.like(from.get(propertyName.get(0)), parameter)); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.like(from.get(propertyName.get(i)), parameter)); + this.predicates.add(predicate); + return this; + } + + /** + * 模糊查询,或者包含 + * + * @param propertyName 要查询的特定属性的属性名 + * @param value 参数值 + * @return query实例 + */ + public Query whereOrLike(@NotNull final List propertyName, @NotNull final Object value) { + Predicate predicate = criteriaBuilder.or(criteriaBuilder.like(from.get(propertyName.get(0)), makeParameter(value))); + for (int i = 1; i < propertyName.size(); i++) + predicate = criteriaBuilder.or(predicate, criteriaBuilder.like(from.get(propertyName.get(i)), makeParameter(value))); + this.predicates.add(predicate); + return this; + } + + /** + * 查找特定字段为空的实体 + * + * @param propertyName 特定属性的属性名 + * @return query实例 + */ + public Query whereIsNull(@NotNull final String propertyName) { + this.predicates.add(criteriaBuilder.isNull(from.get(propertyName))); + return this; + } + + /** + * 查找特定字段非空的实体 + * + * @param propertyName 特定属性的属性名 + * @return query实例 + */ + public Query whereIsNotNull(@NotNull final String propertyName) { + this.predicates.add(criteriaBuilder.isNotNull(from.get(propertyName))); + return this; + } + + + /** + * 时间区间查询 + * + * @param propertyName 属性名称 + * @param lo 属性起始值 + * @param go 属性结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final Date lo, @NotNull final Date go) { + this.predicates.add(criteriaBuilder.between(from.get(propertyName), lo, go)); + return this; + } + + /** + * 数字区间查询 + * + * @param propertyName 属性名称 + * @param lo 数字起始值 + * @param go 数字结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final ParameterExpression lo, @NotNull final ParameterExpression go) { + whereGreaterThanOrEqual(propertyName, lo) + .whereLessThanOrEqual(propertyName, go); + return this; + } + + /** + * 数字区间查询 + * + * @param propertyName 属性名称 + * @param lo 数字起始值 + * @param go 数字结束值 + */ + public Query whereBetween(@NotNull final String propertyName, @NotNull final Number lo, @NotNull final Number go) { + whereGreaterThanOrEqual(propertyName, lo) + .whereLessThanOrEqual(propertyName, go); + return this; + } + + + /** + * 包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream().forEach(in::value); + this.predicates.add(in); + return this; + } + + /** + * 包含 + * + * @param propertyName 属性名称 + * @param values 参数值集合 + */ + public Query whereValueIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream() + .map(this::makeParameter) + .collect(Collectors.toList()) + .stream() + .forEach(in::value); + this.predicates.add(in); + return this; + } + + /** + * 不包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereNotIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream().forEach(in::value); + this.predicates.add(criteriaBuilder.not(in)); + return this; + } + + /** + * 不包含 + * + * @param propertyName 属性名称 + * @param values 参数集合 + */ + public Query whereValueNotIn(@NotNull final String propertyName, @NotNull final List values) { + In in = criteriaBuilder.in(from.get(propertyName)); + values.stream() + .map(this::makeParameter) + .collect(Collectors.toList()) + .stream() + .forEach(in::value); + this.predicates.add(criteriaBuilder.not(in)); + return this; + } + + /** + * 设置分组方式 + * + * @param groupBy 分组查询的属性名 + */ + public Query groupBy(@NotNull final String groupBy) { + this.groupBy = groupBy; + return this; + } + + /** + * 工具方法 + */ + private boolean isNullOrEmpty(Object value) { + if (value instanceof String) { + return "".equals(value); + } + return value == null; + } + + /** + * 深拷贝 + * + * @return + */ + public Object deepClone() throws Exception { + // 序列化 + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + + oos.writeObject(this); + + // 反序列化 + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + + return ois.readObject(); + } + + public Class getModelClass() { + return this.clazz; + } + + public String getProjection() { + return this.projection; + } + + public void setProjection(String projection) { + this.projection = projection; + } + + public Class getClazz() { + return this.clazz; + } + + public List getOrders() { + return orders; + } + + public void setOrders(List orders) { + this.orders = orders; + } + + public EntityManager getEntityManager() { + return this.entityManager; + } + + public void setEntityManager(EntityManager em) { + this.entityManager = em; + } + + public From getFrom() { + return from; + } + + public List getPredicates() { + return predicates; + } + + public void setPredicates(List predicates) { + this.predicates = predicates; + } + + public CriteriaQuery getCriteriaQuery() { + return criteriaQuery; + } + + public CriteriaBuilder getCriteriaBuilder() { + return criteriaBuilder; + } + + public Selection getSelection() { + return selection; + } + + public void setSelection(Selection selection) { + this.selection = selection; + } + + public Map getParameters() { + return parameters; + } + + public void setParameters(Map parameters) { + this.parameters = parameters; + } + + public void setFetchModes(List fetchField, List fetchMode) { + + } + +} \ No newline at end of file diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java index d4c754b..07dff54 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/BaseService.java @@ -2,10 +2,8 @@ import com.zhangzhihao.FileUpload.Java.Dao.BaseDao; +import com.zhangzhihao.FileUpload.Java.Dao.Query; import com.zhangzhihao.FileUpload.Java.Utils.PageResults; -import org.hibernate.criterion.Criterion; -import org.hibernate.criterion.Order; -import org.hibernate.criterion.Projection; import org.jetbrains.annotations.NotNull; import org.springframework.beans.factory.annotation.Autowired; @@ -32,40 +30,18 @@ public BaseService() { * 保存对象 * * @param model 需要添加的对象 - * @return 是否添加成功 */ - public Boolean save(@NotNull T model) { - return baseDao.save(model); + public void save(@NotNull final T model) throws Exception { + baseDao.save(model); } - /** - * 添加并且返回Integer类型的ID - * - * @param model 需要添加的对象 - * @return Integer类型的ID - */ - public Integer saveAndGetIntegerID(@NotNull T model) { - return (Integer) baseDao.saveAndGetIntegerID(model); - } - - /** - * 添加并且返回String类型的ID - * - * @param model 需要添加的对象 - * @return String类型的ID - */ - public String saveAndGetStringID(@NotNull T model) { - return (String) baseDao.saveAndGetStringID(model); - } - - /** * 批量保存对象 * * @param modelList 需要增加的对象的集合 * 失败会抛异常 */ - public void saveAll(@NotNull final List modelList) { + public void saveAll(@NotNull final List modelList) throws Exception { baseDao.saveAll(modelList); } @@ -75,7 +51,7 @@ public void saveAll(@NotNull final List modelList) { * @param model 需要删除的对象 * 失败会抛异常 */ - public void delete(@NotNull final T model) { + public void delete(@NotNull final T model) throws Exception { baseDao.delete(model); } @@ -85,7 +61,7 @@ public void delete(@NotNull final T model) { * @param modelList 需要删除的对象的集合 * 失败会抛异常 */ - public void deleteAll(@NotNull final List modelList) { + public void deleteAll(@NotNull final List modelList) throws Exception { baseDao.deleteAll(modelList); } @@ -95,38 +71,28 @@ public void deleteAll(@NotNull final List modelList) { * @param id 需要删除的对象的id * 失败抛出异常 */ - public void deleteById(@NotNull Serializable id) { + public void deleteById(@NotNull final Serializable id) throws Exception { baseDao.deleteById(modelClass, id); } /** - * 更新对象 + * 更新或保存对象 * * @param model 需要更新的对象 * 失败会抛出异常 */ - public void update(@NotNull final T model) { - baseDao.update(model); + public void saveOrUpdate(@NotNull final T model) throws Exception { + baseDao.saveOrUpdate(model); } /** - * 批量更新对象 + * 批量更新或保存对象 * - * @param modelList 需要更新的对象 + * @param modelList 需要更新或保存的对象 * 失败会抛出异常 */ - public void updateAll(@NotNull final List modelList) { - baseDao.updateAll(modelList); - } - - /** - * 添加或者更新 - * - * @param model 需要更新或添加的对象 - * 失败会抛出异常 - */ - public void saveOrUpdate(@NotNull final T model) { - baseDao.saveOrUpdate(model); + public void saveOrUpdateAll(@NotNull final List modelList) throws Exception { + baseDao.saveOrUpdateAll(modelList); } /** @@ -135,7 +101,7 @@ public void saveOrUpdate(@NotNull final T model) { * @param id 主键(Serializable) * @return model */ - public T getById(@NotNull final Serializable id) { + public T getById(@NotNull final Serializable id) throws Exception { return baseDao.getById(modelClass, id); } @@ -144,8 +110,8 @@ public T getById(@NotNull final Serializable id) { * * @return List */ - public List loadAll() { - return baseDao.loadAll(modelClass); + public List getAll() throws Exception { + return baseDao.getAll(modelClass); } @@ -157,61 +123,104 @@ public List loadAll() { * @return 查询结果 */ public List getListByPage(@NotNull final Integer currentPageNumber, - @NotNull final Integer pageSize) { + @NotNull final Integer pageSize) + throws Exception { return baseDao.getListByPage(modelClass, currentPageNumber, pageSize); } /** - * 按条件分页,Criterion [URL]http://zzk.cnblogs.com/s?t=b&w=Criteria + * 按条件分页 * * @param currentPageNumber 页码 * @param pageSize 每页数量 - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param orders 查询后记录的排序条件,由Order对象生成 - * @param projections 分组和聚合查询条件,这里的条件只能是 Projections.projectionList().add(Property.forName("passWord").as("passWord")),详情参看测试用例 + * @param query 封装的查询条件 * @return 查询结果 */ - public PageResults getListByPageAndRule(@NotNull Integer currentPageNumber, - @NotNull Integer pageSize, - @NotNull final Criterion[] criterions, - @NotNull final Order[] orders, - @NotNull final Projection[] projections) { - return baseDao.getListByPageAndRule(modelClass, currentPageNumber, pageSize, criterions, orders, projections); + public PageResults getListByPageAndQuery(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull Query query) + throws Exception { + return baseDao.getListByPageAndQuery(currentPageNumber, pageSize, query); } + /** + * 获得数量 利用Count(*)实现 + * + * @return 数量 + */ + public int getCount() throws Exception { + return baseDao.getCount(modelClass); + } /** * 获得符合对应条件的数量 利用Count(*)实现 * - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; + * @param query 查询条件 * @return 数量 */ - public int getCountByRule(@NotNull final Criterion[] criterions) { - return baseDao.getCountByRule(modelClass, criterions); + public int getCountByQuery(@NotNull final Query query) throws Exception { + return baseDao.getCountByQuery(query); + } + + /** + * 执行Sql语句 + * + * @param sql sql + * @param values 不定参数数组 + * @return 受影响的行数 + */ + public int executeSql(@NotNull final String sql, @NotNull final Object... values) + throws Exception { + return baseDao.executeSql(sql, values); + } + + /** + * 通过jpql查询 + * + * @param jpql jpql语句 + * @param values 参数列表 + * @return 受影响的行数 + */ + public Object queryByJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.queryByJpql(jpql, values); } /** - * 获得统计结果 + * 获得符合对应条件的数量 利用Count(*)实现 * - * @param criterions 查询条件数组,由Restrictions对象生成,如Restrictions.like("name","%x%")等; - * @param projections 分组和聚合查询条件 + * @param jpql jpql查询条件 * @return 数量 */ - public List getStatisticsByRule(@NotNull final Criterion[] criterions, - @NotNull final Projection[] projections) { - return baseDao.getStatisticsByRule(modelClass, criterions, projections); + public int getCountByJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.getCountByJpql(jpql, values); } /** - * 执行Sql语句 + * 通过Jpql分页查询 + * + * @param currentPageNumber 当前页 + * @param pageSize 每页数量 + * @param jpql jpql语句 + * @param values jpql参数 + * @return 查询结果 + */ + public PageResults getListByPageAndJpql(@NotNull Integer currentPageNumber, + @NotNull Integer pageSize, + @NotNull final String jpql, + @NotNull Object... values) { + return baseDao.getListByPageAndJpql(currentPageNumber, pageSize, jpql, values); + } + + /** + * 执行jpql语句 * - * @param sqlString sql - * @param values 不定参数数组 + * @param jpql jpql语句 + * @param values 参数列表 * @return 受影响的行数 */ - public int executeSql(@NotNull String sqlString, @NotNull Object... values) { - return baseDao.executeSql(sqlString, values); + public int executeJpql(@NotNull final String jpql, @NotNull final Object... values) { + return baseDao.executeJpql(jpql, values); } /** @@ -219,7 +228,7 @@ public int executeSql(@NotNull String sqlString, @NotNull Object... values) { * * @param model 实体 */ - public void refresh(@NotNull T model) { + public void refresh(@NotNull T model) throws Exception { baseDao.refresh(model); } } diff --git a/src/main/resources/Spring.xml b/src/main/resources/Spring.xml index 8b86b3b..d916dd0 100644 --- a/src/main/resources/Spring.xml +++ b/src/main/resources/Spring.xml @@ -2,15 +2,13 @@ + http://www.springframework.org/schema/tx/spring-tx.xsd"> - - - + + + + - - - - + - org.hibernate.dialect.MySQL5InnoDBDialect + org.hibernate.dialect.MySQL5Dialect + true true update + true + org.hibernate.cache.SingletonEhCacheRegionFactory + + true + 2 + true + 20 + 1 + 2 + + 2000 + 2000 + + 10 + + 100 + + 30 - --> - - - - - - com.zhangzhihao.FileUpload.Java.Model - + + + - - - - + + + - - - - - - - - - - - - - + + - - - \ No newline at end of file From 7ecd759f8e5a2e11b3064dce371bb6b443eaa77c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Fri, 1 Jul 2016 20:33:53 +0800 Subject: [PATCH 09/29] =?UTF-8?q?=E8=B7=AF=E5=BE=84=E4=B8=8D=E5=AD=98?= =?UTF-8?q?=E5=9C=A8=E4=BC=9A=E8=87=AA=E5=8A=A8=E5=88=9B=E5=BB=BA=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=A4=B9=EF=BC=8C=E4=BD=BF=E7=94=A8try=E8=87=AA?= =?UTF-8?q?=E5=8A=A8=E7=AE=A1=E7=90=86FileOutputStream?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileUpload/Java/Utils/SaveFile.java | 82 ++++++++++--------- 1 file changed, 44 insertions(+), 38 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 51f25a3..76973ce 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -2,45 +2,51 @@ import org.springframework.web.multipart.MultipartFile; -import java.io.*; +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.FileOutputStream; +import java.io.InputStream; public class SaveFile { - public static boolean saveFile(String localPath, String fileFullName, MultipartFile file) throws Exception { - FileOutputStream outStream = null; - try { - byte[] data = readInputStream(file.getInputStream()); - //new一个文件对象用来保存图片,默认保存当前工程根目录 - File imageFile = new File(localPath + "/src/main/webapp/upload/" + fileFullName); - //创建输出流 - outStream = new FileOutputStream(imageFile); - //写入数据 - outStream.write(data); - outStream.flush(); - - } catch (Exception e) { - e.printStackTrace(); - throw e; - } finally { - outStream.close(); - } - return true; - } - - public static byte[] readInputStream(InputStream inStream) throws Exception { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - //创建一个Buffer字符串 - byte[] buffer = new byte[1024]; - //每次读取的字符串长度,如果为-1,代表全部读取完毕 - int len = 0; - //使用一个输入流从buffer里把数据读取出来 - while ((len = inStream.read(buffer)) != -1) { - //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 - outStream.write(buffer, 0, len); - } - //关闭输入流 - inStream.close(); - //把outStream里的数据写入内存 - return outStream.toByteArray(); - } + public static boolean saveFile(String localPath, String fileFullName, MultipartFile file) throws Exception { + byte[] data = readInputStream(file.getInputStream()); + //new一个文件对象用来保存图片,默认保存当前工程根目录 + File uploadFile = new File(localPath + "/src/main/webapp/upload/" + fileFullName); + + //判断文件夹是否存在,不存在就差创建一个 + File fileDirectory = new File(localPath + "/src/main/webapp/upload/"); + if (!fileDirectory.exists()) { + //noinspection ResultOfMethodCallIgnored + fileDirectory.mkdir(); + } + + //创建输出流 + try (FileOutputStream outStream = new FileOutputStream(uploadFile)) {//写入数据 + outStream.write(data); + outStream.flush(); + + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + return uploadFile.exists(); + } + + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + //创建一个Buffer字符串 + byte[] buffer = new byte[1024]; + //每次读取的字符串长度,如果为-1,代表全部读取完毕 + int len = 0; + //使用一个输入流从buffer里把数据读取出来 + while ((len = inStream.read(buffer)) != -1) { + //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 + outStream.write(buffer, 0, len); + } + //关闭输入流 + inStream.close(); + //把outStream里的数据写入内存 + return outStream.toByteArray(); + } } From 4900c85e813d2bb3e147bba67f82dae2ec637c24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Fri, 1 Jul 2016 20:38:02 +0800 Subject: [PATCH 10/29] =?UTF-8?q?=E6=9B=B4=E6=94=B9save=E6=96=B9=E6=B3=95?= =?UTF-8?q?=E7=9A=84=E5=8F=82=E6=95=B0=EF=BC=8C=E6=B3=A8=E6=84=8F=E4=BF=9D?= =?UTF-8?q?=E5=AD=98=E8=B7=AF=E5=BE=84=E5=8F=82=E6=95=B0=E7=9A=84=E5=8F=98?= =?UTF-8?q?=E5=8C=96=EF=BC=81?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileUpload/Java/Controller/FileUploadController.java | 6 +++--- .../com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java | 9 ++++++--- 2 files changed, 9 insertions(+), 6 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java index d56f7e5..f5c421a 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java @@ -40,11 +40,11 @@ public String fileUpload(@RequestParam("id") String id, try { String path = FileUploadController.class.getResource("/").getFile(); - int build = path.indexOf("build"); - String realpath = path.substring(0, build); + int index = path.indexOf("build"); + String realPath = path.substring(0, index) + "/src/main/webapp/upload/"; String ext = name.substring(name.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + ext; - saveFile(realpath, fileName, file); + saveFile(realPath, fileName, file); } catch (Exception ex) { return "{\"error\":true}"; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 76973ce..936a3e3 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -1,5 +1,6 @@ package com.zhangzhihao.FileUpload.Java.Utils; +import org.jetbrains.annotations.NotNull; import org.springframework.web.multipart.MultipartFile; import java.io.ByteArrayOutputStream; @@ -9,13 +10,15 @@ public class SaveFile { - public static boolean saveFile(String localPath, String fileFullName, MultipartFile file) throws Exception { + public static boolean saveFile(@NotNull final String savePath, + @NotNull final String fileFullName, + @NotNull final MultipartFile file) throws Exception { byte[] data = readInputStream(file.getInputStream()); //new一个文件对象用来保存图片,默认保存当前工程根目录 - File uploadFile = new File(localPath + "/src/main/webapp/upload/" + fileFullName); + File uploadFile = new File(savePath + fileFullName); //判断文件夹是否存在,不存在就差创建一个 - File fileDirectory = new File(localPath + "/src/main/webapp/upload/"); + File fileDirectory = new File(savePath); if (!fileDirectory.exists()) { //noinspection ResultOfMethodCallIgnored fileDirectory.mkdir(); From 4f6b0576293dc0157d4f0a69151ee9007b5e2c96 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Sun, 3 Jul 2016 16:40:31 +0800 Subject: [PATCH 11/29] =?UTF-8?q?=E5=A2=9E=E5=8A=A0glyphicons?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../fonts/glyphicons-halflings-regular.ttf | Bin 0 -> 45404 bytes .../fonts/glyphicons-halflings-regular.woff | Bin 0 -> 23424 bytes .../fonts/glyphicons-halflings-regular.woff2 | Bin 0 -> 18028 bytes 3 files changed, 0 insertions(+), 0 deletions(-) create mode 100644 src/main/webapp/fonts/glyphicons-halflings-regular.ttf create mode 100644 src/main/webapp/fonts/glyphicons-halflings-regular.woff create mode 100644 src/main/webapp/fonts/glyphicons-halflings-regular.woff2 diff --git a/src/main/webapp/fonts/glyphicons-halflings-regular.ttf b/src/main/webapp/fonts/glyphicons-halflings-regular.ttf new file mode 100644 index 0000000000000000000000000000000000000000..1413fc609ab6f21774de0cb7e01360095584f65b GIT binary patch literal 45404 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 G`u_lkUM?>H literal 0 HcmV?d00001 diff --git a/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 b/src/main/webapp/fonts/glyphicons-halflings-regular.woff2 new file mode 100644 index 0000000000000000000000000000000000000000..64539b54c3751a6d9adb44c8e3a45ba5a73b77f0 GIT binary patch literal 18028 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 literal 0 HcmV?d00001 From ad5f2ee26d13fe9933529d99f618d88c8dbf1530 Mon Sep 17 00:00:00 2001 From: Lemon2016 <18331503830@163.com> Date: Fri, 8 Jul 2016 19:57:11 +0800 Subject: [PATCH 12/29] =?UTF-8?q?=E5=9B=BE=E7=89=87=E7=9A=84=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=E5=AE=8C=E6=88=90=EF=BC=8C=E5=8A=A0=E5=85=A5=E4=BA=86?= =?UTF-8?q?=E6=B7=B1copy=E6=96=B9=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/ImageUploadController.java | 97 +++++++++ .../FileUpload/Java/Utils/DeepCopy.java | 30 +++ .../FileUpload/Java/Utils/IsImag.java | 24 +++ .../WEB-INF/views/ImageUpload/Upload.jsp | 194 ++++++++++++++++++ src/main/webapp/index.jsp | 2 +- 5 files changed, 346 insertions(+), 1 deletion(-) create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java create mode 100644 src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java new file mode 100644 index 0000000..76b0d31 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java @@ -0,0 +1,97 @@ +package com.zhangzhihao.FileUpload.Java.Controller; + +import com.sun.javafx.css.CssError; +import com.zhangzhihao.FileUpload.Java.Model.File; +import com.zhangzhihao.FileUpload.Java.Service.FileService; +import org.apache.commons.fileupload.disk.DiskFileItem; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.RequestMapping; +import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.RequestParam; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; +import org.springframework.web.multipart.commons.CommonsMultipartFile; + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import java.io.FileInputStream; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Path; +import java.util.Date; +import java.util.UUID; + +//import static com.sun.rowset.JdbcRowSetResourceBundle.fileName; +import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; +import static com.zhangzhihao.FileUpload.Java.Utils.DeepCopy.deepClone; +import static com.zhangzhihao.FileUpload.Java.Utils.IsImag.isImage; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; + + +/** + * Created by LY on 2016/6/26. + */ +@Controller +@RequestMapping("/ImageUpload") +public class ImageUploadController { + @Autowired + private FileService fileService; + + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Upload() { + return "ImageUpload/Upload"; + } + + @ResponseBody + @RequestMapping(value = "/ImageUp", method = RequestMethod.POST) + public String fileUpload(@RequestParam("id") String id, + @RequestParam("name") String name, + @RequestParam("type") String type, + @RequestParam("lastModifiedDate") String lastModifiedDate, + @RequestParam("size") int size, + @RequestParam("file") MultipartFile file) { + String fileName = ""; + + MultipartFile saveFile=null; + + try { + saveFile= (MultipartFile) deepClone(file); + } catch (Exception e) { + e.printStackTrace(); + } + + + java.io.File tempFile=new java.io.File(UUID.randomUUID().toString()); + try { + file.transferTo(tempFile); + } catch (IOException e) { + e.printStackTrace(); + } + + + + if(!isImage(tempFile)) + return "{\"error\":true}"; + + + + try { + String path = ImageUploadController.class.getResource("/").getFile(); + int build = path.indexOf("build"); + String realpath = path.substring(0, build)+"src/main/webapp/upload/"; + String ext = name.substring(name.lastIndexOf(".")); + fileName = UUID.randomUUID().toString() + ext; + saveFile(realpath, fileName, saveFile); + } catch (Exception ex) { + System.out.println(ex); + return "{\"error\":true}"; + } + try { + fileService.save(new File(fileName, createMd5(file).toString(), new Date())); + } catch (Exception e) { + e.printStackTrace(); + } + return"{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}";} +} + diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java new file mode 100644 index 0000000..800bdec --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java @@ -0,0 +1,30 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import java.io.ByteArrayInputStream; +import java.io.ByteArrayOutputStream; +import java.io.ObjectInputStream; +import java.io.ObjectOutputStream; + +/** + * Created by LY on 2016/7/7. + */ +public class DeepCopy { + /** + * 深拷贝 + * + * @return 深拷贝得到的新实例 + */ + public static Object deepClone(Object object) throws Exception { + // 序列化 + ByteArrayOutputStream bos = new ByteArrayOutputStream(); + ObjectOutputStream oos = new ObjectOutputStream(bos); + + oos.writeObject(object); + + // 反序列化 + ByteArrayInputStream bis = new ByteArrayInputStream(bos.toByteArray()); + ObjectInputStream ois = new ObjectInputStream(bis); + + return ois.readObject(); + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java new file mode 100644 index 0000000..de4acde --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java @@ -0,0 +1,24 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import javax.imageio.ImageIO; +import javax.imageio.stream.ImageInputStream; +import java.io.File; + +public class IsImag { + public static boolean isImage(File tempFile){ + boolean flag=false; + try { + ImageInputStream is= ImageIO.createImageInputStream(tempFile); + if(null==is){ + return flag; + } + is.close(); + flag=true; + }catch (Exception e){ + + } + return flag; + + } +} diff --git a/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp new file mode 100644 index 0000000..38c218e --- /dev/null +++ b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp @@ -0,0 +1,194 @@ +<%@ page contentType="text/html;charset=UTF-8" language="java" %> + + + + + + 图片上传 + + + + + + + + + + +
+
+
+
+
+
+
+
+ 选择图片 + +
+
+ +
开始上传
+
+
+
+ + +<%--
--%> + <%--Username:--%> + <%----%> + <%----%> +<%--
--%> + + + + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 5c53dd9..376ecc1 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -8,7 +8,7 @@ 文件上传 -图片上传 +图片上传 大文件上传 From 3c8d488de6a6d80d33fcdbc5ea9ff2c1ad9e6077 Mon Sep 17 00:00:00 2001 From: hujian <1072841098@qq.com> Date: Wed, 13 Jul 2016 10:38:58 +0800 Subject: [PATCH 13/29] =?UTF-8?q?=E5=A4=A7=E6=96=87=E4=BB=B6=E4=B8=8A?= =?UTF-8?q?=E4=BC=A0=EF=BC=8C=E7=A7=92=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/BigFileUploadController.java | 107 +++++++++++++++--- .../FileUpload/Java/Model/UploadInfo.java | 83 ++++++++++++++ .../FileUpload/Java/Service/FileService.java | 18 +++ .../FileUpload/Java/Utils/CreateMd5.java | 4 +- .../FileUpload/Java/Utils/IsAllUploaded.java | 67 +++++++++++ .../FileUpload/Java/Utils/MergeFile.java | 43 +++++++ .../FileUpload/Java/Utils/SaveFile.java | 2 +- .../WEB-INF/views/BigFileUpload/Index.jsp | 2 +- 8 files changed, 309 insertions(+), 17 deletions(-) create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index ee822c9..b4b4a4f 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -1,25 +1,106 @@ package com.zhangzhihao.FileUpload.Java.Controller; import com.zhangzhihao.FileUpload.Java.Service.FileService; +import com.zhangzhihao.FileUpload.Java.Utils.SaveFile; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.multipart.MultipartFile; + +import static com.zhangzhihao.FileUpload.Java.Utils.IsAllUploaded.Uploaded; @Controller @RequestMapping("/BigFileUpload") -public class BigFileUploadController { - @Autowired - private FileService fileService; - - /** - * 转向操作页面 - * - * @return 操作页面 - */ - @RequestMapping(value = "/Index", method = RequestMethod.GET) - public String Index() { - return "BigFileUpload/Index"; - } +public class BigFileUploadController extends SaveFile { + @Autowired + private FileService fileService; + + /** + * 转向操作页面 + * + * @return 操作页面 + */ + @RequestMapping(value = "/Index", method = RequestMethod.GET) + public String Index() { + return "BigFileUpload/Index"; + } + + @ResponseBody + @RequestMapping(value = "/IsMD5Exist", method = RequestMethod.POST) + public String bigFileUpload(String fileMd5, String fileName, String fileID) { + + try { + boolean md5Exist = fileService.isMd5Exist(fileMd5); + if (md5Exist) { + return "this file is exist"; + } else { + return "this file is not exist"; + } + } catch (Exception e) { + e.printStackTrace(); + return "this file is not exist"; + } + } + + /** + * @param guid // 临时文件名 + * @param md5value + * @param chunks //分块数 + * @param chunk //分块序号 + * @param id + * @param name //上传文件名 + * @param type + * @param lastModifiedDate + * @param size + * @param file + * @return + */ + @ResponseBody + @RequestMapping(value = "/BigFileUp") + public String fileUpload(String guid, String md5value, String chunks, String chunk, String id, String name, String type, String lastModifiedDate, int size, MultipartFile file) { + String fileName = ""; + try { + int xuhao = 0; + + String path = FileUploadController.class.getResource("/").getFile(); + int index = path.indexOf("build"); + String tempPath = "/src/main/webapp/upload/"; + String realPath = path.substring(0, index) + tempPath; + + + String newTempPath = tempPath + guid + "/"; //创建临时文件夹保存分块文件 + String newRealPath = path.substring(0, index) + newTempPath; //分块文件临时保存路径 + String ext = name.substring(name.lastIndexOf(".")); + + + if (chunks != null && chunk != null) { //判断文件是否分块 + int chunksNumber = Integer.parseInt(chunks); + xuhao = Integer.parseInt(chunk); + fileName = String.valueOf(xuhao).toString() + ext; + saveFile(newRealPath, fileName, file); // 将文件分块保存到临时文件夹里,便于之后的合并文件 + + Uploaded(md5value, guid, chunk, chunks, path, fileName, ext, fileService); // 验证所有分块是否上传成功,成功的话进行合并 + + } else { + + fileName =guid + ext; + saveFile(realPath, fileName, file); //上传文件没有分块的话就直接保存 + } + + + } catch (Exception ex) { + return "{\"error\":true}"; + } + try { + + } catch (Exception e) { + return "{\"error\":true}"; + } + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } + } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java new file mode 100644 index 0000000..00da1ca --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Model/UploadInfo.java @@ -0,0 +1,83 @@ +package com.zhangzhihao.FileUpload.Java.Model; + + +public class UploadInfo { + private String md5; + private String chunks; + private String chunk; + private String path; + private String fileName; + private String ext; + + public UploadInfo() { + } + + public UploadInfo(String md5, String chunks, String chunk, String path, String fileName, String ext) { + this.md5 = md5; + this.chunks = chunks; + this.chunk = chunk; + this.path = path; + this.fileName = fileName; + this.ext = ext; + } + + @Override + public String toString() { + return "UploadInfo{" + + "md5='" + md5 + '\'' + + ", chunks='" + chunks + '\'' + + ", chunk='" + chunk + '\'' + + ", path='" + path + '\'' + + ", fileName='" + fileName + '\'' + + ", ext='" + ext + '\'' + + '}'; + } + + public String getMd5() { + return md5; + } + + public void setMd5(String md5) { + this.md5 = md5; + } + + public String getChunks() { + return chunks; + } + + public void setChunks(String chunks) { + this.chunks = chunks; + } + + public String getChunk() { + return chunk; + } + + public void setChunk(String chunk) { + this.chunk = chunk; + } + + public String getPath() { + return path; + } + + public void setPath(String path) { + this.path = path; + } + + public String getFileName() { + return fileName; + } + + public void setFileName(String fileName) { + this.fileName = fileName; + } + + public String getExt() { + return ext; + } + + public void setExt(String ext) { + this.ext = ext; + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java index c7d8653..53212b4 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java @@ -1,8 +1,26 @@ package com.zhangzhihao.FileUpload.Java.Service; +import com.zhangzhihao.FileUpload.Java.Dao.Query; import com.zhangzhihao.FileUpload.Java.Model.File; import org.springframework.stereotype.Service; +import javax.persistence.EntityManager; +import javax.persistence.PersistenceContext; + @Service public class FileService extends BaseService { + @PersistenceContext + private EntityManager entityManager; + + public boolean isMd5Exist(String md5) { + Query query = new Query(entityManager); + Object resultMD5 = query.from(File.class) + .select() + .whereEqual("MD5", md5) + .createTypedQuery() + .getSingleResult(); + + return resultMD5 != null; + + } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java index 05bf264..b59c143 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java @@ -13,11 +13,11 @@ public static StringBuilder createMd5(MultipartFile file){ StringBuilder sb = new StringBuilder(); try { - MessageDigest md5=MessageDigest.getInstance("MD5"); + MessageDigest md5=MessageDigest.getInstance("MD5"); //生成MD5实例 InputStream inputStream = file.getInputStream(); int available = inputStream.available(); byte[] bytes = new byte[available]; - md5.update(bytes);//执行MD5算法 + md5.update(bytes); //执行MD5算法 for (byte by : md5.digest()) { sb.append(String.format("%02X", by));//将生成的字节MD5值转换成字符串 diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java new file mode 100644 index 0000000..c1e140b --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java @@ -0,0 +1,67 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import com.zhangzhihao.FileUpload.Java.Model.UploadInfo; +import com.zhangzhihao.FileUpload.Java.Service.FileService; + +import java.util.ArrayList; +import java.util.Date; +import java.util.List; +import java.util.stream.Collectors; + +import static com.zhangzhihao.FileUpload.Java.Utils.MergeFile.mergeFile; + +public class IsAllUploaded { + + private static List uploadInfoList; + + public static boolean isAllUploaded(String md5, String chunks) { + int size = uploadInfoList.stream() + .filter(item -> item.getMd5().equals(md5)) + .distinct() + .collect(Collectors.toList()) + .size(); + boolean bool = (size == Integer.parseInt(chunks)); + if (bool) { + uploadInfoList.removeIf(item -> item.getMd5() == md5); + } + return bool; + } + + /** + * @param md5 + * @param chunk + * @param chunks + * @param path + * @param fileName + * @param ext + */ + public static void Uploaded(String md5, String guid, String chunk, String chunks, String path, String fileName, String ext, FileService fileService) { + if (uploadInfoList == null) { + uploadInfoList = new ArrayList<>(); + } + uploadInfoList.add(new UploadInfo(md5, chunks, chunk, path, fileName, ext)); + boolean allUploaded = isAllUploaded(md5, chunks); + int chunksNumber = Integer.parseInt(chunks); + + int index = path.indexOf("build"); + String tempPath = "/src/main/webapp/upload/"; + String realPath = path.substring(0, index) + tempPath; + + String newTempPath = tempPath + guid + "/"; //创建临时文件夹保存分块文件 + String newRealPath = path.substring(0, index) + newTempPath; //分块文件临时保存路径 + + + if (allUploaded) { + try { + mergeFile(chunksNumber, ext, md5, guid, newRealPath, realPath); + fileService.save(new com.zhangzhihao.FileUpload.Java.Model.File(guid + ext, md5, new Date())); + } catch (Exception e) { + e.printStackTrace(); + } + } + } +} + + + diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java new file mode 100644 index 0000000..75340eb --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -0,0 +1,43 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + +import java.io.*; + + +public class MergeFile { + + public static void mergeFile(int chunksNumber, String ext, String md5,String guid, String newRealPath, String realPath) throws Exception { + /*合并输入流*/ + SequenceInputStream s = null; + InputStream s1 = new FileInputStream(newRealPath + 0 + ext); + InputStream s2 = new FileInputStream(newRealPath + 1 + ext); + s = new SequenceInputStream(s1, s2); + for (int i = 2; i < chunksNumber; i++) { + InputStream s3 = new FileInputStream(newRealPath + i + ext); + s = new SequenceInputStream(s, s3); + } + + /*创建输出流,写入数据,合并分块*/ + OutputStream outstream = new FileOutputStream(realPath + guid + ext); + byte[] buffer = new byte[1024]; + int len = 0; + try { + while ((len = s.read(buffer)) != -1) { + outstream.write(buffer, 0, len); + outstream.flush(); + } + } catch (IOException e) { + throw e; + } finally { + outstream.close(); + s.close(); + } + /*删除临时文件夹*/ + File dir= new File(newRealPath); + File[] files=dir.listFiles(); + for (int i = 0; i Date: Wed, 13 Jul 2016 11:18:46 +0800 Subject: [PATCH 14/29] no message --- .../com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java index 75340eb..4a6fcde 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -35,7 +35,11 @@ public static void mergeFile(int chunksNumber, String ext, String md5,String gui File dir= new File(newRealPath); File[] files=dir.listFiles(); for (int i = 0; i Date: Thu, 14 Jul 2016 15:42:14 +0800 Subject: [PATCH 15/29] =?UTF-8?q?=E5=AE=9E=E7=8E=B0=E5=A4=A7=E6=96=87?= =?UTF-8?q?=E4=BB=B6=E5=88=86=E5=9D=97=E4=BF=9D=E5=AD=98=E4=BB=A5=E5=8F=8A?= =?UTF-8?q?=E5=90=88=E5=B9=B6=EF=BC=8C=E6=96=B0=E5=A2=9EDeleteFolder?= =?UTF-8?q?=E5=92=8CStreamUtil=E7=AD=89=E5=85=AC=E7=94=A8=E6=96=B9?= =?UTF-8?q?=E6=B3=95=E3=80=82?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/BigFileUploadController.java | 32 ++++++------- .../FileUpload/Java/Utils/DeleteFolder.java | 22 +++++++++ .../FileUpload/Java/Utils/IsAllUploaded.java | 34 ++++++------- .../FileUpload/Java/Utils/MergeFile.java | 48 +++++++------------ .../FileUpload/Java/Utils/SaveFile.java | 8 ++++ .../FileUpload/Java/Utils/StreamUtil.java | 34 +++++++++++++ 6 files changed, 111 insertions(+), 67 deletions(-) create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java create mode 100644 src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index b4b4a4f..ff3e234 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -46,7 +46,7 @@ public String bigFileUpload(String fileMd5, String fileName, String fileID) { /** * @param guid // 临时文件名 - * @param md5value + * @param md5value //客户端生成md5值 * @param chunks //分块数 * @param chunk //分块序号 * @param id @@ -63,33 +63,31 @@ public String fileUpload(String guid, String md5value, String chunks, String chu String fileName = ""; try { int xuhao = 0; - String path = FileUploadController.class.getResource("/").getFile(); int index = path.indexOf("build"); String tempPath = "/src/main/webapp/upload/"; - String realPath = path.substring(0, index) + tempPath; - + String uploadFolderPath = path.substring(0, index) + tempPath; - String newTempPath = tempPath + guid + "/"; //创建临时文件夹保存分块文件 - String newRealPath = path.substring(0, index) + newTempPath; //分块文件临时保存路径 + //创建临时文件夹保存分块文件 + String newTempPath = tempPath + guid + "/"; + //分块文件临时保存路径 + String mergePath = path.substring(0, index) + newTempPath; String ext = name.substring(name.lastIndexOf(".")); - - if (chunks != null && chunk != null) { //判断文件是否分块 - int chunksNumber = Integer.parseInt(chunks); + //判断文件是否分块 + if (chunks != null && chunk != null) { xuhao = Integer.parseInt(chunk); fileName = String.valueOf(xuhao).toString() + ext; - saveFile(newRealPath, fileName, file); // 将文件分块保存到临时文件夹里,便于之后的合并文件 - - Uploaded(md5value, guid, chunk, chunks, path, fileName, ext, fileService); // 验证所有分块是否上传成功,成功的话进行合并 - + // 将文件分块保存到临时文件夹里,便于之后的合并文件 + saveFile(mergePath, fileName, file); + // 验证所有分块是否上传成功,成功的话进行合并 + Uploaded(md5value, guid, chunk, chunks, uploadFolderPath, fileName, ext, fileService); } else { - - fileName =guid + ext; - saveFile(realPath, fileName, file); //上传文件没有分块的话就直接保存 + fileName = guid + ext; + //上传文件没有分块的话就直接保存 + saveFile(uploadFolderPath, fileName, file); } - } catch (Exception ex) { return "{\"error\":true}"; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java new file mode 100644 index 0000000..d643876 --- /dev/null +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java @@ -0,0 +1,22 @@ +package com.zhangzhihao.FileUpload.Java.Utils; + + +import org.jetbrains.annotations.NotNull; + +import java.io.File; + +public class DeleteFolder { + public static boolean deleteFolder(@NotNull final String folderPath) { + /*删除临时文件夹*/ + File dir = new File(folderPath); + File[] files = dir.listFiles(); + for (int i = 0; i < files.length; i++) { + try { + files[i].delete(); + } catch (Exception e) { + e.printStackTrace(); + } + } + return dir.delete(); + } +} diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java index c1e140b..46b2737 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java @@ -15,6 +15,11 @@ public class IsAllUploaded { private static List uploadInfoList; + /** + * @param md5 + * @param chunks + * @return + */ public static boolean isAllUploaded(String md5, String chunks) { int size = uploadInfoList.stream() .filter(item -> item.getMd5().equals(md5)) @@ -30,35 +35,24 @@ public static boolean isAllUploaded(String md5, String chunks) { /** * @param md5 - * @param chunk - * @param chunks - * @param path + * @param guid 随机生成的文件名 + * @param chunk //文件分块序号 + * @param chunks //文件分块数 * @param fileName - * @param ext + * @param ext //文件后缀名 + * @param fileService */ - public static void Uploaded(String md5, String guid, String chunk, String chunks, String path, String fileName, String ext, FileService fileService) { + public static void Uploaded(String md5, String guid, String chunk, String chunks, String uploadFolderPath, String fileName, String ext, FileService fileService) throws Exception { if (uploadInfoList == null) { uploadInfoList = new ArrayList<>(); } - uploadInfoList.add(new UploadInfo(md5, chunks, chunk, path, fileName, ext)); + uploadInfoList.add(new UploadInfo(md5, chunks, chunk, uploadFolderPath, fileName, ext)); boolean allUploaded = isAllUploaded(md5, chunks); int chunksNumber = Integer.parseInt(chunks); - int index = path.indexOf("build"); - String tempPath = "/src/main/webapp/upload/"; - String realPath = path.substring(0, index) + tempPath; - - String newTempPath = tempPath + guid + "/"; //创建临时文件夹保存分块文件 - String newRealPath = path.substring(0, index) + newTempPath; //分块文件临时保存路径 - - if (allUploaded) { - try { - mergeFile(chunksNumber, ext, md5, guid, newRealPath, realPath); - fileService.save(new com.zhangzhihao.FileUpload.Java.Model.File(guid + ext, md5, new Date())); - } catch (Exception e) { - e.printStackTrace(); - } + mergeFile(chunksNumber, ext, guid, uploadFolderPath); + fileService.save(new com.zhangzhihao.FileUpload.Java.Model.File(guid + ext, md5, new Date())); } } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java index 4a6fcde..04dc23c 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -2,46 +2,34 @@ import java.io.*; +import static com.zhangzhihao.FileUpload.Java.Utils.DeleteFolder.deleteFolder; +import static com.zhangzhihao.FileUpload.Java.Utils.StreamUtil.saveStreamToFile; + public class MergeFile { - public static void mergeFile(int chunksNumber, String ext, String md5,String guid, String newRealPath, String realPath) throws Exception { + /** + * @param chunksNumber + * @param ext + * @param guid + * @param uploadFolderPath + * @throws Exception + */ + public static void mergeFile(int chunksNumber, String ext, String guid, String uploadFolderPath) throws Exception { /*合并输入流*/ + String mergePath = uploadFolderPath + guid + "/"; SequenceInputStream s = null; - InputStream s1 = new FileInputStream(newRealPath + 0 + ext); - InputStream s2 = new FileInputStream(newRealPath + 1 + ext); + InputStream s1 = new FileInputStream(mergePath + 0 + ext); + InputStream s2 = new FileInputStream(mergePath + 1 + ext); s = new SequenceInputStream(s1, s2); for (int i = 2; i < chunksNumber; i++) { - InputStream s3 = new FileInputStream(newRealPath + i + ext); + InputStream s3 = new FileInputStream(mergePath + i + ext); s = new SequenceInputStream(s, s3); } - /*创建输出流,写入数据,合并分块*/ - OutputStream outstream = new FileOutputStream(realPath + guid + ext); - byte[] buffer = new byte[1024]; - int len = 0; - try { - while ((len = s.read(buffer)) != -1) { - outstream.write(buffer, 0, len); - outstream.flush(); - } - } catch (IOException e) { - throw e; - } finally { - outstream.close(); - s.close(); - } - /*删除临时文件夹*/ - File dir= new File(newRealPath); - File[] files=dir.listFiles(); - for (int i = 0; i Date: Fri, 15 Jul 2016 17:11:09 +0800 Subject: [PATCH 16/29] =?UTF-8?q?=E6=B3=A8=E9=87=8A=EF=BC=8C=E6=A0=BC?= =?UTF-8?q?=E5=BC=8F=E4=BF=AE=E6=AD=A3=EF=BC=8C=E7=B2=BE=E7=AE=80=E5=86=97?= =?UTF-8?q?=E4=BD=99=E8=AF=AD=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/BigFileUploadController.java | 27 ++++++++----------- .../FileUpload/Java/Utils/CreateMd5.java | 27 ++++++++----------- .../FileUpload/Java/Utils/DeepCopy.java | 8 +++--- .../FileUpload/Java/Utils/IsAllUploaded.java | 26 ++++++++++++------ .../FileUpload/Java/Utils/IsImag.java | 18 ++++++------- .../FileUpload/Java/Utils/MergeFile.java | 14 +++++++--- .../FileUpload/Java/Utils/SaveFile.java | 3 ++- .../FileUpload/Java/Utils/StreamUtil.java | 8 +++--- 8 files changed, 69 insertions(+), 62 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index ff3e234..28ae41e 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -45,16 +45,16 @@ public String bigFileUpload(String fileMd5, String fileName, String fileID) { } /** - * @param guid // 临时文件名 - * @param md5value //客户端生成md5值 - * @param chunks //分块数 - * @param chunk //分块序号 - * @param id - * @param name //上传文件名 - * @param type - * @param lastModifiedDate - * @param size - * @param file + * @param guid 临时文件名 + * @param md5value 客户端生成md5值 + * @param chunks 分块数 + * @param chunk 分块序号 + * @param id 文件id便于区分 + * @param name 上传文件名 + * @param type 文件类型 + * @param lastModifiedDate 上次修改时间 + * @param size 文件大小 + * @param file 文件本身 * @return */ @ResponseBody @@ -77,7 +77,7 @@ public String fileUpload(String guid, String md5value, String chunks, String chu //判断文件是否分块 if (chunks != null && chunk != null) { xuhao = Integer.parseInt(chunk); - fileName = String.valueOf(xuhao).toString() + ext; + fileName = String.valueOf(xuhao) + ext; // 将文件分块保存到临时文件夹里,便于之后的合并文件 saveFile(mergePath, fileName, file); // 验证所有分块是否上传成功,成功的话进行合并 @@ -91,11 +91,6 @@ public String fileUpload(String guid, String md5value, String chunks, String chu } catch (Exception ex) { return "{\"error\":true}"; } - try { - - } catch (Exception e) { - return "{\"error\":true}"; - } return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java index b59c143..ddf1b7e 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java @@ -1,5 +1,6 @@ package com.zhangzhihao.FileUpload.Java.Utils; +import org.jetbrains.annotations.NotNull; import org.springframework.web.multipart.MultipartFile; import java.io.IOException; @@ -9,23 +10,17 @@ public class CreateMd5 { - public static StringBuilder createMd5(MultipartFile file){ + public static StringBuilder createMd5(@NotNull final MultipartFile file) throws NoSuchAlgorithmException, IOException { StringBuilder sb = new StringBuilder(); - try { - - MessageDigest md5=MessageDigest.getInstance("MD5"); //生成MD5实例 - InputStream inputStream = file.getInputStream(); - int available = inputStream.available(); - byte[] bytes = new byte[available]; - md5.update(bytes); //执行MD5算法 - for (byte by : md5.digest()) - { - sb.append(String.format("%02X", by));//将生成的字节MD5值转换成字符串 - } - } catch (NoSuchAlgorithmException e) { - e.printStackTrace(); - } catch (IOException e) { - e.printStackTrace(); + //生成MD5实例 + MessageDigest md5 = MessageDigest.getInstance("MD5"); + InputStream inputStream = file.getInputStream(); + int available = inputStream.available(); + byte[] bytes = new byte[available]; + md5.update(bytes); + for (byte by : md5.digest()) { + //将生成的字节MD5值转换成字符串 + sb.append(String.format("%02X", by)); } return sb; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java index 800bdec..6df1055 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java @@ -1,20 +1,20 @@ package com.zhangzhihao.FileUpload.Java.Utils; +import org.jetbrains.annotations.NotNull; + import java.io.ByteArrayInputStream; import java.io.ByteArrayOutputStream; import java.io.ObjectInputStream; import java.io.ObjectOutputStream; -/** - * Created by LY on 2016/7/7. - */ + public class DeepCopy { /** * 深拷贝 * * @return 深拷贝得到的新实例 */ - public static Object deepClone(Object object) throws Exception { + public static Object deepClone(@NotNull final Object object) throws Exception { // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java index 46b2737..99021e2 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java @@ -3,6 +3,7 @@ import com.zhangzhihao.FileUpload.Java.Model.UploadInfo; import com.zhangzhihao.FileUpload.Java.Service.FileService; +import org.jetbrains.annotations.NotNull; import java.util.ArrayList; import java.util.Date; @@ -20,7 +21,8 @@ public class IsAllUploaded { * @param chunks * @return */ - public static boolean isAllUploaded(String md5, String chunks) { + public static boolean isAllUploaded(@NotNull final String md5, + @NotNull final String chunks) { int size = uploadInfoList.stream() .filter(item -> item.getMd5().equals(md5)) .distinct() @@ -34,15 +36,23 @@ public static boolean isAllUploaded(String md5, String chunks) { } /** - * @param md5 + * @param md5 MD5 * @param guid 随机生成的文件名 - * @param chunk //文件分块序号 - * @param chunks //文件分块数 - * @param fileName - * @param ext //文件后缀名 - * @param fileService + * @param chunk 文件分块序号 + * @param chunks 文件分块数 + * @param fileName 文件名 + * @param ext 文件后缀名 + * @param fileService fileService */ - public static void Uploaded(String md5, String guid, String chunk, String chunks, String uploadFolderPath, String fileName, String ext, FileService fileService) throws Exception { + public static void Uploaded(@NotNull final String md5, + @NotNull final String guid, + @NotNull final String chunk, + @NotNull final String chunks, + @NotNull final String uploadFolderPath, + @NotNull final String fileName, + @NotNull final String ext, + @NotNull final FileService fileService) + throws Exception { if (uploadInfoList == null) { uploadInfoList = new ArrayList<>(); } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java index de4acde..7c04f0b 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java @@ -4,20 +4,18 @@ import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import java.io.File; +import java.io.IOException; +@SuppressWarnings("ConstantConditions") public class IsImag { - public static boolean isImage(File tempFile){ + public static boolean isImage(File tempFile) throws IOException { boolean flag=false; - try { - ImageInputStream is= ImageIO.createImageInputStream(tempFile); - if(null==is){ - return flag; - } - is.close(); - flag=true; - }catch (Exception e){ - + ImageInputStream is= ImageIO.createImageInputStream(tempFile); + if(null==is){ + return flag; } + is.close(); + flag=true; return flag; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java index 04dc23c..02d2e39 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -1,5 +1,7 @@ package com.zhangzhihao.FileUpload.Java.Utils; +import org.jetbrains.annotations.NotNull; + import java.io.*; import static com.zhangzhihao.FileUpload.Java.Utils.DeleteFolder.deleteFolder; @@ -15,7 +17,11 @@ public class MergeFile { * @param uploadFolderPath * @throws Exception */ - public static void mergeFile(int chunksNumber, String ext, String guid, String uploadFolderPath) throws Exception { + public static void mergeFile(final int chunksNumber, + @NotNull final String ext, + @NotNull final String guid, + @NotNull final String uploadFolderPath) + throws Exception { /*合并输入流*/ String mergePath = uploadFolderPath + guid + "/"; SequenceInputStream s = null; @@ -27,9 +33,11 @@ public static void mergeFile(int chunksNumber, String ext, String guid, String u s = new SequenceInputStream(s, s3); } - saveStreamToFile(s, uploadFolderPath + guid + ext); //通过输出流向文件写入数据 + //通过输出流向文件写入数据 + saveStreamToFile(s, uploadFolderPath + guid + ext); - deleteFolder(mergePath); //删除保存分块文件的文件夹 + //删除保存分块文件的文件夹 + deleteFolder(mergePath); } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index a44c5f1..662ffd1 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -20,7 +20,8 @@ public class SaveFile { */ public static boolean saveFile(@NotNull final String savePath, @NotNull final String fileFullName, - @NotNull final MultipartFile file) throws Exception { + @NotNull final MultipartFile file) + throws Exception { byte[] data = readInputStream(file.getInputStream()); //new一个文件对象用来保存图片,默认保存当前工程根目录 File uploadFile = new File(savePath + fileFullName); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java index fb67c1b..fe3b5a6 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java @@ -13,19 +13,19 @@ public static boolean saveStreamToFile(@NotNull final InputStream inputStream, @NotNull final String filePath) throws IOException { boolean flag = false; /*创建输出流,写入数据,合并分块*/ - OutputStream outstream = new FileOutputStream(filePath); + OutputStream outputStream = new FileOutputStream(filePath); byte[] buffer = new byte[1024]; int len = 0; try { while ((len = inputStream.read(buffer)) != -1) { - outstream.write(buffer, 0, len); - outstream.flush(); + outputStream.write(buffer, 0, len); + outputStream.flush(); } } catch (IOException e) { flag = false; throw e; } finally { - outstream.close(); + outputStream.close(); inputStream.close(); flag = true; } From bd2983328d3cdb8662cdc58037ff66f96bc9b343 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Fri, 15 Jul 2016 17:27:06 +0800 Subject: [PATCH 17/29] =?UTF-8?q?=E9=94=99=E8=AF=AF=E4=BF=AE=E5=A4=8D?= =?UTF-8?q?=EF=BC=8C=E5=86=97=E4=BD=99=E4=BB=A3=E7=A0=81=E7=B2=BE=E7=AE=80?= =?UTF-8?q?&&=E6=B8=85=E7=90=86?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/ImageUploadController.java | 53 +++++-------------- 1 file changed, 12 insertions(+), 41 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java index 76b0d31..d4be3ac 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java @@ -1,9 +1,7 @@ package com.zhangzhihao.FileUpload.Java.Controller; -import com.sun.javafx.css.CssError; import com.zhangzhihao.FileUpload.Java.Model.File; import com.zhangzhihao.FileUpload.Java.Service.FileService; -import org.apache.commons.fileupload.disk.DiskFileItem; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import org.springframework.web.bind.annotation.RequestMapping; @@ -11,27 +9,16 @@ import org.springframework.web.bind.annotation.RequestParam; import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.multipart.MultipartFile; -import org.springframework.web.multipart.commons.CommonsMultipartFile; -import javax.imageio.ImageIO; -import javax.imageio.stream.ImageInputStream; -import java.io.FileInputStream; -import java.io.IOException; -import java.io.InputStream; -import java.nio.file.Path; import java.util.Date; import java.util.UUID; -//import static com.sun.rowset.JdbcRowSetResourceBundle.fileName; import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; import static com.zhangzhihao.FileUpload.Java.Utils.DeepCopy.deepClone; import static com.zhangzhihao.FileUpload.Java.Utils.IsImag.isImage; import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; -/** - * Created by LY on 2016/6/26. - */ @Controller @RequestMapping("/ImageUpload") public class ImageUploadController { @@ -53,45 +40,29 @@ public String fileUpload(@RequestParam("id") String id, @RequestParam("file") MultipartFile file) { String fileName = ""; - MultipartFile saveFile=null; + MultipartFile saveFile = null; try { - saveFile= (MultipartFile) deepClone(file); - } catch (Exception e) { - e.printStackTrace(); - } - - - java.io.File tempFile=new java.io.File(UUID.randomUUID().toString()); - try { + saveFile = (MultipartFile) deepClone(file); + java.io.File tempFile = new java.io.File(UUID.randomUUID().toString()); file.transferTo(tempFile); - } catch (IOException e) { - e.printStackTrace(); - } - - - - if(!isImage(tempFile)) - return "{\"error\":true}"; - + if (!isImage(tempFile)) + return "{\"error\":true}"; - - try { String path = ImageUploadController.class.getResource("/").getFile(); int build = path.indexOf("build"); - String realpath = path.substring(0, build)+"src/main/webapp/upload/"; + String realpath = path.substring(0, build) + "src/main/webapp/upload/"; String ext = name.substring(name.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + ext; saveFile(realpath, fileName, saveFile); + + fileService.save(new File(fileName, createMd5(file).toString(), new Date())); + } catch (Exception ex) { - System.out.println(ex); return "{\"error\":true}"; } - try { - fileService.save(new File(fileName, createMd5(file).toString(), new Date())); - } catch (Exception e) { - e.printStackTrace(); - } - return"{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}";} + + return "{jsonrpc = \"2.0\",id = id,filePath = \"/Upload/\" + fileFullName}"; + } } From 4dea0aab304288887b7c1420f59250d69ea70974 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Wed, 20 Jul 2016 15:47:51 +0800 Subject: [PATCH 18/29] =?UTF-8?q?=E6=95=B4=E7=90=86Utils=E4=BB=A3=E7=A0=81?= =?UTF-8?q?=E9=80=BB=E8=BE=91=EF=BC=8C=E4=BF=AE=E6=AD=A3=E9=94=99=E8=AF=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../FileUpload/Java/Utils/CreateMd5.java | 5 ++--- .../FileUpload/Java/Utils/DeepCopy.java | 3 ++- .../FileUpload/Java/Utils/DeleteFolder.java | 19 ++++++++++++------ .../FileUpload/Java/Utils/IsImag.java | 13 +++--------- .../FileUpload/Java/Utils/SaveFile.java | 1 - .../FileUpload/Java/Utils/StreamUtil.java | 20 +++++++++++-------- 6 files changed, 32 insertions(+), 29 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java index ddf1b7e..6227e0e 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/CreateMd5.java @@ -3,14 +3,13 @@ import org.jetbrains.annotations.NotNull; import org.springframework.web.multipart.MultipartFile; -import java.io.IOException; import java.io.InputStream; import java.security.MessageDigest; -import java.security.NoSuchAlgorithmException; public class CreateMd5 { - public static StringBuilder createMd5(@NotNull final MultipartFile file) throws NoSuchAlgorithmException, IOException { + public static StringBuilder createMd5(@NotNull final MultipartFile file) + throws Exception { StringBuilder sb = new StringBuilder(); //生成MD5实例 MessageDigest md5 = MessageDigest.getInstance("MD5"); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java index 6df1055..0f79130 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeepCopy.java @@ -14,7 +14,8 @@ public class DeepCopy { * * @return 深拷贝得到的新实例 */ - public static Object deepClone(@NotNull final Object object) throws Exception { + public static Object deepClone(@NotNull final Object object) + throws Exception { // 序列化 ByteArrayOutputStream bos = new ByteArrayOutputStream(); ObjectOutputStream oos = new ObjectOutputStream(bos); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java index d643876..52e2945 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/DeleteFolder.java @@ -6,15 +6,22 @@ import java.io.File; public class DeleteFolder { + /** + * 删除指定文件夹 + * @param folderPath 文件夹路径 + * @return 是否删除成功 + */ + @SuppressWarnings("ResultOfMethodCallIgnored") public static boolean deleteFolder(@NotNull final String folderPath) { - /*删除临时文件夹*/ File dir = new File(folderPath); File[] files = dir.listFiles(); - for (int i = 0; i < files.length; i++) { - try { - files[i].delete(); - } catch (Exception e) { - e.printStackTrace(); + if(files!=null){ + for (File file : files) { + try { + file.delete(); + } catch (Exception e) { + e.printStackTrace(); + } } } return dir.delete(); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java index 7c04f0b..a4dbe06 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsImag.java @@ -4,19 +4,12 @@ import javax.imageio.ImageIO; import javax.imageio.stream.ImageInputStream; import java.io.File; -import java.io.IOException; @SuppressWarnings("ConstantConditions") public class IsImag { - public static boolean isImage(File tempFile) throws IOException { - boolean flag=false; + public static boolean isImage(File tempFile) + throws Exception { ImageInputStream is= ImageIO.createImageInputStream(tempFile); - if(null==is){ - return flag; - } - is.close(); - flag=true; - return flag; - + return is!=null; } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 662ffd1..2b132d1 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -25,7 +25,6 @@ public static boolean saveFile(@NotNull final String savePath, byte[] data = readInputStream(file.getInputStream()); //new一个文件对象用来保存图片,默认保存当前工程根目录 File uploadFile = new File(savePath + fileFullName); - //判断文件夹是否存在,不存在就创建一个 File fileDirectory = new File(savePath); if (!fileDirectory.exists()) { diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java index fe3b5a6..c6a3dd1 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/StreamUtil.java @@ -4,14 +4,20 @@ import org.jetbrains.annotations.NotNull; import java.io.FileOutputStream; -import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; public class StreamUtil { - public static boolean saveStreamToFile(@NotNull final InputStream inputStream, - @NotNull final String filePath) throws IOException { - boolean flag = false; + /** + * 从stream中保存文件 + * + * @param inputStream inputStream + * @param filePath 保存路径 + * @throws Exception 异常 抛异常代表失败了 + */ + public static void saveStreamToFile(@NotNull final InputStream inputStream, + @NotNull final String filePath) + throws Exception { /*创建输出流,写入数据,合并分块*/ OutputStream outputStream = new FileOutputStream(filePath); byte[] buffer = new byte[1024]; @@ -21,14 +27,12 @@ public static boolean saveStreamToFile(@NotNull final InputStream inputStream, outputStream.write(buffer, 0, len); outputStream.flush(); } - } catch (IOException e) { - flag = false; + } catch (Exception e) { + e.printStackTrace(); throw e; } finally { outputStream.close(); inputStream.close(); - flag = true; } - return flag; } } From 45cb00ace62ac224fc08b45a44f2838dbe6b8218 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Wed, 20 Jul 2016 15:51:57 +0800 Subject: [PATCH 19/29] =?UTF-8?q?=E4=BF=AE=E6=AD=A3=E9=80=BB=E8=BE=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 2b132d1..6d577a4 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -28,8 +28,9 @@ public static boolean saveFile(@NotNull final String savePath, //判断文件夹是否存在,不存在就创建一个 File fileDirectory = new File(savePath); if (!fileDirectory.exists()) { - //noinspection ResultOfMethodCallIgnored - fileDirectory.mkdir(); + if (!fileDirectory.mkdir()) { + throw new Exception("文件夹创建失败!路径为:" + savePath); + } } //创建输出流 From 7796b50e7e49be279577e4bc70c1aac7f0d1d126 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Wed, 20 Jul 2016 15:55:52 +0800 Subject: [PATCH 20/29] update Reademe.md --- README.md | 11 ++++++++++- 1 file changed, 10 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index 4a71332..fdb3119 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,13 @@ # FileUpload.Java [![Build Status](https://travis-ci.org/izhangzhihao/FileUpload.Java.svg?branch=master)](https://travis-ci.org/izhangzhihao/FileUpload.Java) - 文件上传,图片上传(后缀名验证,文件类型验证),大文件分片上传,“秒传”,断点续传,传输失败自动重试,手动重试 +# 文件上传,图片上传(后缀名验证,文件类型验证),大文件分片上传,“秒传”,断点续传,传输失败自动重试,手动重试 + +1.主要功能经测试支持IE9以上,Chrome,FireFox;其他浏览器未测试; + +2.文件上传部分:主要实现了文件的上传,进度条,多文件一起上传,上传前删除,上传失败后手动删除,上传失败自动重试,上传失败手动重试(retry按钮),自动上传; + +3.大文件上传部分:重磅功能:大文件“秒传”;在文件上传部分已有功能的基础上实现了按10MB分为多个块,异步上传,服务端合并,MD5验证,文件秒传,断点续传,网络问题自动重试,手动重试; + +4.图片上传部分:在文件上传部分已有功能的基础上实现了上传前缩略图预览,前台js文件后缀验证,后台代码文件后缀验证和文件类型验证(就算修改后缀名也无法成功上传),支持图片上传前压缩; + From e84aba9fef2c7e2ccbc3f6abd534455c35253b57 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Mon, 15 Aug 2016 16:11:26 +0800 Subject: [PATCH 21/29] =?UTF-8?q?=E6=9B=B4=E6=96=B0webuploader.nolog.js=3D?= =?UTF-8?q?=E3=80=8Bwebuploader.nolog.min.js?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../WEB-INF/views/BigFileUpload/Index.jsp | 2 +- .../webapp/WEB-INF/views/FileUpload/Index.jsp | 2 +- .../WEB-INF/views/ImageUpload/Upload.jsp | 2 +- .../app/BigFileUpload/Index/BigFileUpload.js | 189 - src/main/webapp/assets/webuploader.nolog.js | 8012 ----------------- .../webapp/assets/webuploader.nolog.min.js | 3 + 6 files changed, 6 insertions(+), 8204 deletions(-) delete mode 100644 src/main/webapp/app/BigFileUpload/Index/BigFileUpload.js delete mode 100644 src/main/webapp/assets/webuploader.nolog.js create mode 100644 src/main/webapp/assets/webuploader.nolog.min.js diff --git a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp index 72582fc..11f441a 100644 --- a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp +++ b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp @@ -7,7 +7,7 @@ - + - + diff --git a/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp index 38c218e..a88d43d 100644 --- a/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp +++ b/src/main/webapp/WEB-INF/views/ImageUpload/Upload.jsp @@ -8,7 +8,7 @@ - + + + + + + + + + +
+ +
+ +

提示

+

只能上传四个文件,只支持以下后缀名的文件doc,docx,pdf,zip,rar,7z。如果需要上传其他文件(如图片)请压缩后上传。请依次点击按钮上传指定文件,如果添加了错误的文件可以删除。

+
+ +
+
+
+ +
+
+
+
+
+
+ 选择研究报告 + +
+
+
+
+ +
+
+
+
+ 选择研究报告支撑材料(限PDF) + +
+
+
+
+ +
+
+
+
+ 选择应用报告 + +
+
+
+
+ +
+
+
+
+ 选择应用报告支撑材料(限PDF) + +
+
+
+
+ +
+
+
开始上传所选文件
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/main/webapp/index.jsp b/src/main/webapp/index.jsp index 376ecc1..d593fa4 100644 --- a/src/main/webapp/index.jsp +++ b/src/main/webapp/index.jsp @@ -10,5 +10,6 @@ 文件上传 图片上传 大文件上传 +多选择器多文件上传 From bd7377aca9aecbb5dd15810748f37cf2806eab41 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Mon, 5 Dec 2016 11:01:13 +0800 Subject: [PATCH 27/29] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 092bc60..e5031df 100644 --- a/README.md +++ b/README.md @@ -11,7 +11,7 @@ 5.多选择器多文件上传:通过不同的文件选择器选择不同的文件,最后同时上传,Controller只是简单示意,并没有详细写实现,具体怎么做可参照上面的其它上穿方法。 -# 文件上传这里好多方法可以抽象出来,当然这个项目只是一个示例,所以我偷了点懒,应用到上产时甚至还要根据环境选择保存到不同的文件路径等等,大家根据自己的情况自己封装方法吧。 +# 文件上传这里好多方法可以抽象出来,当然这个项目只是一个示例,所以我偷了点懒,应用到生产环境时还要根据环境选择保存到不同的文件路径等等,大家根据自己的情况自己封装方法吧。 From 385e38829a281a74b8d302abd029f03f8e768b32 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Mon, 5 Dec 2016 11:12:50 +0800 Subject: [PATCH 28/29] =?UTF-8?q?=E5=B0=81=E8=A3=85=E4=BA=86=E4=B8=80?= =?UTF-8?q?=E4=B8=8B=E6=96=B9=E6=B3=95?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/BigFileUploadController.java | 15 +- .../Java/Controller/FileUploadController.java | 5 +- .../Controller/ImageUploadController.java | 5 +- .../FileUpload/Java/Utils/SaveFile.java | 154 ++++++++++++------ 4 files changed, 122 insertions(+), 57 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index 28ae41e..b3160c5 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -59,10 +59,19 @@ public String bigFileUpload(String fileMd5, String fileName, String fileID) { */ @ResponseBody @RequestMapping(value = "/BigFileUp") - public String fileUpload(String guid, String md5value, String chunks, String chunk, String id, String name, String type, String lastModifiedDate, int size, MultipartFile file) { - String fileName = ""; + public String fileUpload(String guid, + String md5value, + String chunks, + String chunk, + String id, + String name, + String type, + String lastModifiedDate, + int size, + MultipartFile file) { + String fileName; try { - int xuhao = 0; + int xuhao; String path = FileUploadController.class.getResource("/").getFile(); int index = path.indexOf("build"); String tempPath = "/src/main/webapp/upload/"; diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java index f5c421a..e1c51bf 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java @@ -14,6 +14,7 @@ import java.util.UUID; import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.getRealPath; import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; @@ -39,9 +40,7 @@ public String fileUpload(@RequestParam("id") String id, String fileName = ""; try { - String path = FileUploadController.class.getResource("/").getFile(); - int index = path.indexOf("build"); - String realPath = path.substring(0, index) + "/src/main/webapp/upload/"; + String realPath = getRealPath(); String ext = name.substring(name.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + ext; saveFile(realPath, fileName, file); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java index d4be3ac..1a3fce1 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/ImageUploadController.java @@ -16,6 +16,7 @@ import static com.zhangzhihao.FileUpload.Java.Utils.CreateMd5.createMd5; import static com.zhangzhihao.FileUpload.Java.Utils.DeepCopy.deepClone; import static com.zhangzhihao.FileUpload.Java.Utils.IsImag.isImage; +import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.getRealPath; import static com.zhangzhihao.FileUpload.Java.Utils.SaveFile.saveFile; @@ -49,9 +50,7 @@ public String fileUpload(@RequestParam("id") String id, if (!isImage(tempFile)) return "{\"error\":true}"; - String path = ImageUploadController.class.getResource("/").getFile(); - int build = path.indexOf("build"); - String realpath = path.substring(0, build) + "src/main/webapp/upload/"; + String realpath = getRealPath(); String ext = name.substring(name.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + ext; saveFile(realpath, fileName, saveFile); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 6d577a4..734465f 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -1,64 +1,122 @@ package com.zhangzhihao.FileUpload.Java.Utils; +import com.zhangzhihao.FileUpload.Java.Controller.FileUploadController; import org.jetbrains.annotations.NotNull; import org.springframework.web.multipart.MultipartFile; -import java.io.ByteArrayOutputStream; -import java.io.File; -import java.io.FileOutputStream; -import java.io.InputStream; +import java.io.*; +import java.nio.file.Files; +import java.nio.file.Path; +import java.nio.file.Paths; +import java.util.List; +import java.util.zip.ZipEntry; +import java.util.zip.ZipOutputStream; public class SaveFile { - /** - * - * @param savePath - * @param fileFullName - * @param file - * @return + /** + * @param savePath + * @param fileFullName + * @param file + * @return * @throws Exception */ - public static boolean saveFile(@NotNull final String savePath, - @NotNull final String fileFullName, - @NotNull final MultipartFile file) - throws Exception { - byte[] data = readInputStream(file.getInputStream()); - //new一个文件对象用来保存图片,默认保存当前工程根目录 - File uploadFile = new File(savePath + fileFullName); - //判断文件夹是否存在,不存在就创建一个 - File fileDirectory = new File(savePath); - if (!fileDirectory.exists()) { - if (!fileDirectory.mkdir()) { + public static boolean saveFile(@NotNull final String savePath, + @NotNull final String fileFullName, + @NotNull final MultipartFile file) + throws Exception { + byte[] data = readInputStream(file.getInputStream()); + //new一个文件对象用来保存图片,默认保存当前工程根目录 + File uploadFile = new File(savePath + fileFullName); + //判断文件夹是否存在,不存在就创建一个 + File fileDirectory = new File(savePath); + if (!fileDirectory.exists()) { + if (!fileDirectory.mkdir()) { throw new Exception("文件夹创建失败!路径为:" + savePath); } - } + } - //创建输出流 - try (FileOutputStream outStream = new FileOutputStream(uploadFile)) {//写入数据 - outStream.write(data); - outStream.flush(); + //创建输出流 + try (FileOutputStream outStream = new FileOutputStream(uploadFile)) {//写入数据 + outStream.write(data); + outStream.flush(); - } catch (Exception e) { - e.printStackTrace(); - throw e; - } - return uploadFile.exists(); - } + } catch (Exception e) { + e.printStackTrace(); + throw e; + } + return uploadFile.exists(); + } - public static byte[] readInputStream(InputStream inStream) throws Exception { - ByteArrayOutputStream outStream = new ByteArrayOutputStream(); - //创建一个Buffer字符串 - byte[] buffer = new byte[1024]; - //每次读取的字符串长度,如果为-1,代表全部读取完毕 - int len = 0; - //使用一个输入流从buffer里把数据读取出来 - while ((len = inStream.read(buffer)) != -1) { - //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 - outStream.write(buffer, 0, len); - } - //关闭输入流 - inStream.close(); - //把outStream里的数据写入内存 - return outStream.toByteArray(); - } + public static byte[] readInputStream(InputStream inStream) throws Exception { + ByteArrayOutputStream outStream = new ByteArrayOutputStream(); + //创建一个Buffer字符串 + byte[] buffer = new byte[1024]; + //每次读取的字符串长度,如果为-1,代表全部读取完毕 + int len = 0; + //使用一个输入流从buffer里把数据读取出来 + while ((len = inStream.read(buffer)) != -1) { + //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 + outStream.write(buffer, 0, len); + } + //关闭输入流 + inStream.close(); + //把outStream里的数据写入内存 + return outStream.toByteArray(); + } + + /** + * 获得绝对路径(不带文件名) + * + * @return + */ + public static String getRealPath() { + String realPath; + String path = FileUploadController.class.getResource("/").getFile(); + int index = path.indexOf("build"); + realPath = path.substring(0, index) + "/src/main/webapp/upload/"; + realPath = realPath.replaceFirst("/", ""); + return realPath; + } + + + /** + * 根据文件路径获取File + * + * @param filePath + * @return + * @throws IOException + */ + public static java.io.File getFileByPath(String filePath) throws IOException { + Path path = Paths.get(getRealPath() + filePath); + if (Files.exists(path)) { + return new java.io.File(path.toUri()); + } + return null; + } + + /** + * 压缩文件 + * + * @param srcFileList + * @param zipFile + * @throws IOException + */ + public static void zipFiles(List srcFileList, java.io.File zipFile) throws IOException { + byte[] buf = new byte[1024]; + //ZipOutputStream类:完成文件或文件夹的压缩 + ZipOutputStream out = new ZipOutputStream(new FileOutputStream(zipFile)); + + for (java.io.File aSrcFileList : srcFileList) { + FileInputStream in = new FileInputStream(aSrcFileList); + out.putNextEntry(new ZipEntry(aSrcFileList.getName())); + int len; + while ((len = in.read(buf)) > 0) { + out.write(buf, 0, len); + } + out.closeEntry(); + in.close(); + } + out.close(); + } } From 5c2d6aecb054cb33c87fa3918d30c6156e5b9672 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E5=BC=A0=E5=BF=97=E8=B1=AA?= Date: Thu, 4 May 2017 21:44:07 +0800 Subject: [PATCH 29/29] =?UTF-8?q?=E5=A2=9E=E5=8A=A0MD5=E8=AE=A1=E7=AE=97?= =?UTF-8?q?=E4=B8=AD=E7=9A=84=E6=8F=90=E7=A4=BA=EF=BC=8C=E8=AF=B7=E7=AD=89?= =?UTF-8?q?=E5=BE=85MD5=E8=AE=A1=E7=AE=97=E5=AE=8C=E6=AF=95=E5=90=8E?= =?UTF-8?q?=E5=86=8D=E4=B8=8A=E4=BC=A0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../Controller/BigFileUploadController.java | 17 +++++-------- .../Java/Controller/FileUploadController.java | 5 ++-- .../FileUpload/Java/Service/FileService.java | 7 +++--- .../FileUpload/Java/Utils/IsAllUploaded.java | 12 ++++++---- .../FileUpload/Java/Utils/MergeFile.java | 2 +- .../FileUpload/Java/Utils/SaveFile.java | 16 +++++++++---- .../WEB-INF/views/BigFileUpload/Index.jsp | 24 ++++++------------- 7 files changed, 39 insertions(+), 44 deletions(-) diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java index b3160c5..044ab7f 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/BigFileUploadController.java @@ -71,22 +71,16 @@ public String fileUpload(String guid, MultipartFile file) { String fileName; try { - int xuhao; - String path = FileUploadController.class.getResource("/").getFile(); - int index = path.indexOf("build"); - String tempPath = "/src/main/webapp/upload/"; - String uploadFolderPath = path.substring(0, index) + tempPath; + int index; + String uploadFolderPath = getRealPath(); - //创建临时文件夹保存分块文件 - String newTempPath = tempPath + guid + "/"; - //分块文件临时保存路径 - String mergePath = path.substring(0, index) + newTempPath; + String mergePath =uploadFolderPath + guid + "/"; String ext = name.substring(name.lastIndexOf(".")); //判断文件是否分块 if (chunks != null && chunk != null) { - xuhao = Integer.parseInt(chunk); - fileName = String.valueOf(xuhao) + ext; + index = Integer.parseInt(chunk); + fileName = String.valueOf(index) + ext; // 将文件分块保存到临时文件夹里,便于之后的合并文件 saveFile(mergePath, fileName, file); // 验证所有分块是否上传成功,成功的话进行合并 @@ -98,6 +92,7 @@ public String fileUpload(String guid, } } catch (Exception ex) { + ex.printStackTrace(); return "{\"error\":true}"; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java index e1c51bf..6138bef 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Controller/FileUploadController.java @@ -37,13 +37,12 @@ public String fileUpload(@RequestParam("id") String id, @RequestParam("lastModifiedDate") String lastModifiedDate, @RequestParam("size") int size, @RequestParam("file") MultipartFile file) { - String fileName = ""; + String fileName; try { - String realPath = getRealPath(); String ext = name.substring(name.lastIndexOf(".")); fileName = UUID.randomUUID().toString() + ext; - saveFile(realPath, fileName, file); + saveFile(getRealPath(), fileName, file); } catch (Exception ex) { return "{\"error\":true}"; } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java index 53212b4..edbd5f6 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Service/FileService.java @@ -6,6 +6,7 @@ import javax.persistence.EntityManager; import javax.persistence.PersistenceContext; +import java.util.List; @Service public class FileService extends BaseService { @@ -14,13 +15,13 @@ public class FileService extends BaseService { public boolean isMd5Exist(String md5) { Query query = new Query(entityManager); - Object resultMD5 = query.from(File.class) + @SuppressWarnings("unchecked") List result = query.from(File.class) .select() .whereEqual("MD5", md5) .createTypedQuery() - .getSingleResult(); + .getResultList(); - return resultMD5 != null; + return !result.isEmpty(); } } diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java index 99021e2..edb24e2 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/IsAllUploaded.java @@ -8,13 +8,14 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; +import java.util.Objects; import java.util.stream.Collectors; import static com.zhangzhihao.FileUpload.Java.Utils.MergeFile.mergeFile; public class IsAllUploaded { - private static List uploadInfoList; + private final static List uploadInfoList = new ArrayList<>(); /** * @param md5 @@ -30,7 +31,9 @@ public static boolean isAllUploaded(@NotNull final String md5, .size(); boolean bool = (size == Integer.parseInt(chunks)); if (bool) { - uploadInfoList.removeIf(item -> item.getMd5() == md5); + synchronized (uploadInfoList) { + uploadInfoList.removeIf(item -> Objects.equals(item.getMd5(), md5)); + } } return bool; } @@ -53,10 +56,9 @@ public static void Uploaded(@NotNull final String md5, @NotNull final String ext, @NotNull final FileService fileService) throws Exception { - if (uploadInfoList == null) { - uploadInfoList = new ArrayList<>(); + synchronized (uploadInfoList) { + uploadInfoList.add(new UploadInfo(md5, chunks, chunk, uploadFolderPath, fileName, ext)); } - uploadInfoList.add(new UploadInfo(md5, chunks, chunk, uploadFolderPath, fileName, ext)); boolean allUploaded = isAllUploaded(md5, chunks); int chunksNumber = Integer.parseInt(chunks); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java index 02d2e39..a2cf63a 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/MergeFile.java @@ -24,7 +24,7 @@ public static void mergeFile(final int chunksNumber, throws Exception { /*合并输入流*/ String mergePath = uploadFolderPath + guid + "/"; - SequenceInputStream s = null; + SequenceInputStream s ; InputStream s1 = new FileInputStream(mergePath + 0 + ext); InputStream s2 = new FileInputStream(mergePath + 1 + ext); s = new SequenceInputStream(s1, s2); diff --git a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java index 734465f..333639f 100644 --- a/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java +++ b/src/main/java/com/zhangzhihao/FileUpload/Java/Utils/SaveFile.java @@ -14,6 +14,7 @@ public class SaveFile { + private static final File uploadDirectory = new File(getRealPath()); /** * @param savePath * @param fileFullName @@ -30,9 +31,16 @@ public static boolean saveFile(@NotNull final String savePath, File uploadFile = new File(savePath + fileFullName); //判断文件夹是否存在,不存在就创建一个 File fileDirectory = new File(savePath); - if (!fileDirectory.exists()) { - if (!fileDirectory.mkdir()) { - throw new Exception("文件夹创建失败!路径为:" + savePath); + synchronized (uploadDirectory){ + if(!uploadDirectory.exists()){ + if(!uploadDirectory.mkdir()){ + throw new Exception("保存文件的父文件夹创建失败!路径为:" + savePath); + } + } + if (!fileDirectory.exists()) { + if (!fileDirectory.mkdir()) { + throw new Exception("文件夹创建失败!路径为:" + savePath); + } } } @@ -53,7 +61,7 @@ public static byte[] readInputStream(InputStream inStream) throws Exception { //创建一个Buffer字符串 byte[] buffer = new byte[1024]; //每次读取的字符串长度,如果为-1,代表全部读取完毕 - int len = 0; + int len; //使用一个输入流从buffer里把数据读取出来 while ((len = inStream.read(buffer)) != -1) { //用输出流往buffer里写入数据,中间参数代表从哪个位置开始读,len代表读取的长度 diff --git a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp index 11f441a..d2d9209 100644 --- a/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp +++ b/src/main/webapp/WEB-INF/views/BigFileUpload/Index.jsp @@ -38,7 +38,7 @@ //formData: { guid: WebUploader.guid() }, //一个文件有一个guid,在服务器端合并成一个文件 这里有个问题,多个文件或者上传一个文件成功后不刷新直接添加文件上传生成的guid不变!!! 暂时只能传一个大文件(已解决) //fileNumLimit :1, fileSizeLimit: 2000 * 1024 * 1024,//最大2GB - fileSingleSizeLImit: 2000 * 1024 * 1024, + fileSingleSizeLimit: 2000 * 1024 * 1024, resize: false//不压缩 @@ -51,15 +51,12 @@ //} }); - - - // 当有文件被添加进队列的时候 uploader.on('fileQueued', function (file) { $list.append('
' + '

' + file.name + '

' + - '

等待上传...

' + - '
');//id="' + file.id + 'btn" + '

正在计算文件MD5...请等待计算完毕后再点击上传!

' + + ''); //删除要上传的文件 //每次添加文件都给btn-delete绑定删除方法 $(".btn-delete").click(function () { @@ -78,6 +75,9 @@ //insertLog("
" + moment().format("YYYY-MM-DD HH:mm:ss") + " before-send-file preupload:计算文件(" + file.name + ")MD5完成. 耗时 " + (end - start) + '毫秒 fileMd5: ' + fileMd5); file.wholeMd5 = fileMd5;//获取到了md5 uploader.options.formData.md5value = file.wholeMd5;//每个文件都附带一个md5,便于实现秒传 + + $('#' + file.id).find('p.state').text('MD5计算完毕,可以点击上传了'); + $.ajax({//向服务端发送请求 cache: false, type: "post", @@ -195,23 +195,13 @@ } }); - - - uploader.on('uploadAccept', function (file, response) { - //if (uploader.errorCode) { - // // 通过return false来告诉组件,此文件上传有错。 - // return false; - //} - if (response._raw == '{"error":true}') { + if (response._raw === '{"error":true}') { return false; } }); }); - - -