当前位置:首页 » 《关于电脑》 » 正文

SpringEvent 解决 WebUploader 大文件上传解耦问题

0 人参与  2024年12月21日 16:00  分类 : 《关于电脑》  评论

点击全文阅读


一、SpringEvent涉及的相关组件

为了让不熟悉SpringEvent的朋友对Event也有一个大致的印象。这里还是对SpringEvent对象包含的方法和相关组件的应用进行简单的介绍。

1、 事件(Event)

事件是应用程序中发生的某种事情,可以是用户行为、系统状态改变等。在Spring中,事件通常表示为一个Java类,它包含了与事件相关的信息。如果大家做过GUI界面的实际与实现,或者进行过Web界面的开发,相信对事件机制一定非常熟悉。比如鼠标点击事件、鼠标双击事件、鼠标拖拽事件、鼠标悬浮事件等等。事件一定是经过触发的,由某一种设备或者事务来进行触发,从而形成某种事件。在本文的场景中,文件上传后在服务器端进行合成是一种事件。

2、事件监听器

事件监听器是一段代码,它等待并响应事件的发生。在Spring中,事件监听器通常实现了ApplicationListener接口,该接口定义了监听事件的方法。如果对监听器模式有所了解朋友一定了解,监听器类的设计非常友好,会根据设计进行监听,而当有相应的变化进行发生时,监听器则会根据发生的情况同时相应的类或者接口,从而实现消息的动态传递。

3、事件发布器

事件发布器负责发布事件,通知所有监听该事件的监听器。在Spring中,ApplicationEventPublisher接口表示事件发布器,可以通过Spring容器自动注入或手动获取。通常在Spring工作环境中,我们会使用applicationContext来进行事件的发布。

上面三者就是SpringEvent的核心组件。事件发布器(publisher)会在事件(event)发生时进行事件的发布,事件发布后,有监听者进行事件监听,当监听到自己感兴趣的主体事件,则进行相应的事件处理。由此形成时间的发布、监听和处理的闭环操作。

在介绍上述的重要组件之后,我们通过大文件处理的实例来具体介绍SpringEvent的详细应用。

二、WebUploader大文件处理的相关事件分析

本节重点介绍WebUploader大文件处理组件中的后台相关事件处理。通过本节将了解何时进行相应事件的注册,具体的事件发布方法是什么?

1、事件发布的时机

事件的发布时机是非常重要的,关于Webuploader则不再进行具体介绍。但是需要注意的是,如果在应用程序中采用了WebUploader这种后台处理机制,我们需要在后台实现数据的分片上传处理、分片的合并的操作。同时为了能兼容大文件和小文件的处理。

以Webuploader为例,针对大文件,我们以5MB作为一个分片的切分逻辑,这种情况下可能有两种情况需要处理。

第一种是单个文件的大小小于5MB,根据分片的策略,小于5MB的文件将不会进行分片而直接上传到后台。这时候也同样不会触发分片的合并逻辑。

第二种情况是文件的大小超过5MB,比如有一个256MB的文件,就会进行分片上传。在服务端我们实现自定义的分片上传之后,还需要进行文件的合并。

因此,我们在选择事件的发布时机时,就有两个点需要考虑的。需要分片的和不需要分片的文件处理时机。这两种都需要考虑,才能不漏掉相应的文件处理。

2、事件发布的代码

在掌握了事件的发布时机后,我们就知道了在处理文件上传时的程序中如何切入事件的发布。事件的发布入口有两个地方,第一个无需分片的事件处理入口。第二个是在分片合并完成的事件入口。

在进行事件发布前,我们需要在程序中创建一个Event的实例对象,用来进行事件信息的绑定和设置。这里我们取名位文件上传事件,关键代码如下所示:

package com.yelang.framework.event;import org.springframework.context.ApplicationEvent;import com.yelang.project.webupload.domain.FileEntity;import lombok.Getter;import lombok.Setter;import lombok.ToString;@Setter@Getter@ToStringpublic class FileUploadEvent extends ApplicationEvent { private static final long serialVersionUID = 7396389156436678379L; private FileEntity fileEntity;//上传文件对象 /**   *   重写构造函数  * @param source 事件源对象  * @param fileEntity 已上传的文件对象  */ public FileUploadEvent(Object source,FileEntity fileEntity) {  super(source);  this.fileEntity = fileEntity; }}

为了方便大家可以获取上传的文件信息实体,我们将文件实体类在事件发布时一同绑定到事件上下文中。fileEntity其实就是一个文件上传的接收实体,关键代码如下:

package com.yelang.project.webupload.domain;import com.baomidou.mybatisplus.annotation.TableField;import com.baomidou.mybatisplus.annotation.TableName;import com.yelang.framework.web.domain.BaseEntity;import lombok.AllArgsConstructor;import lombok.Getter;import lombok.NoArgsConstructor;import lombok.Setter;import lombok.ToString;@TableName("biz_file")@NoArgsConstructor@AllArgsConstructor@Setter@Getter@ToStringpublic class FileEntity extends BaseEntity { private static final long serialVersionUID = 1L; private Long id; @TableField(value = "f_id") private String fid; @TableField(value = "b_id") private String bid; @TableField(value = "f_type") private String type; @TableField(value = "f_name") private String name; @TableField(value = "f_desc") private String desc; @TableField(value = "f_state") private Integer state; @TableField(value = "f_size") private Long size; @TableField(value = "f_path") private String path; @TableField(value = "table_name") private String tablename = "temp_table"; private String md5code; private String directory; @TableField(value = "biz_type") private String bizType; @TableField(exist = false) private boolean previewSign; @TableField(exist = false) private String previewType;}

我们在小文件(小于5MB)上传成功之后以及大文件合并完成之后就可以发布文件上传事件,在下面的代码中,我们通过applicationContext上下文对象发布了一个FilaUploadEvent的事件。大致的代码如下所示:

@SuppressWarnings("resource")private AjaxResult mergeChunks(FileEntity db_file,String chunk_dir,String chunks,String f_path) throws IOException { if (db_file == null) {  return AjaxResult.error(AjaxResult.Type.WEBUPLOADERROR.value(), "找不到数据"); } if (db_file.getState() == 1) {  //未分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行  applicationContext.publishEvent(new FileUploadEvent(this, db_file));  return AjaxResult.success();    } if(db_file.getSize() > block_size){  //xxx 其它业务逻辑  db_file.setState(1);  fileService.updateById(db_file);  File tempFile = new File(chunk_dir);  if (tempFile.isDirectory() && tempFile.exists()) {   tempFile.delete();  }  //分片文件上传成功合并成功后发布相应事件,各监听器自由监听并执行  applicationContext.publishEvent(new FileUploadEvent(this, db_file)); } return AjaxResult.success();}

三、事件监听器及实际的业务处理

在上面小节中,我们介绍如何发布Spring的Event,同时以一个大文件的上传为例,具体的介绍了如何进行文件上传事件的发布。本节接着在上面的例子中,重点讲解在事件发布后,如何进行事件的监听以及具体的业务回调处理机制。通过本节可以掌握在实际业务中进行灵活的业务扩展和定制。

1、文件上传处理枚举

在讲解事件监听器之前,首先我们对监听器中的具体回调业务类进行注册。在实际业务中,我们可以选择将具体回调业务类进行持久化处理,比如使用关系型数据库 进行处理,将具体的业务类、物理表、业务属性、回调业务实现类统一保存的数据库中。这样在执行的时候统一通过数据去获取即可。这种模式也是可以的,实现起来也比较简单。

如何在不引入数据库的前提下实现呢?其实我们可以利用枚举类来轻松实现这类需求。下面分享一下这种设计,文件上传处理枚举类的业务逻辑如下所示:

package com.yelang.framework.aspectj.lang.enums;/** * 文件上传监听服务注册枚举类 * @author 夜郎king */public enum FileUploadServiceRegisterEnum { UNKOWN(-1,"UNKOWN","","","未知"), PROJZSPRCSINFSERVIMPL(0,"biz_student","studentUploadCallbackServiceImpl","123a","项目程序管理文件上传回调处理枚举"); private int index;//下标,编号作用 private String tableName;//业务表名称,根据表名检索具体执行的servcie private String execService;//业务实际执行service private String bizType;//业务类型 private String desc;//描述说明 public int getIndex() {  return index; } public void setIndex(int index) {  this.index = index; } public String getTableName() {  return tableName; } public void setTableName(String tableName) {  this.tableName = tableName; } public String getExecService() {  return execService; } public void setExecService(String execService) {  this.execService = execService; } public String getDesc() {  return desc; } public void setDesc(String desc) {  this.desc = desc; } public String getBizType() {  return bizType; } public void setBizType(String bizType) {  this.bizType = bizType; } private FileUploadServiceRegisterEnum(int index, String tableName, String execService, String bizType, String desc) {  this.index = index;  this.tableName = tableName;  this.execService = execService;  this.bizType = bizType;  this.desc = desc; } public static FileUploadServiceRegisterEnum getEnumByTableName(String tableName){  FileUploadServiceRegisterEnum result = null;  for (FileUploadServiceRegisterEnum enumObj : FileUploadServiceRegisterEnum.values()) {   if(enumObj.getTableName().equals(tableName)){    result = enumObj;    break;   }  }  return result; } public static FileUploadServiceRegisterEnum getEnumByTableNameAndBizType(String tableName,String bizType){  FileUploadServiceRegisterEnum result = null;  for (FileUploadServiceRegisterEnum enumObj : FileUploadServiceRegisterEnum.values()) {   if(enumObj.getTableName().equals(tableName) && enumObj.getBizType().equals(bizType)){    result = enumObj;    break;   }  }  return result; }}

在进行业务注册时,我们会定义具体的枚举实例,如下:PROJZSPRCSINFSERVIMPL(0,"biz_student","studentUploadCallbackServiceImpl","123a","项目程序管理文件上传回调处理枚举");

0是下标索引号,biz_student是业务表,studentUploadCallbackServiceImpl是回调的具体业务实现类,123a是业务类型描述,根据需要可以用来区分同一个表的不同业务实现。最后一个是业务的描述。

2、文件上传监听器的实现

在定义上述的枚举类之后,我们来进行文件上传监听器的实现,核心代码如下:

package com.yelang.framework.event.listener;import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.context.event.EventListener;import org.springframework.scheduling.annotation.Async;import org.springframework.stereotype.Component;import com.yelang.common.utils.StringUtils;import com.yelang.common.utils.spring.SpringUtils;import com.yelang.framework.aspectj.lang.enums.FileUploadServiceRegisterEnum;import com.yelang.framework.event.FileUploadEvent;import com.yelang.project.common.service.IFileUploadCallbackService;import com.yelang.project.webupload.domain.FileEntity;/** * 公共事件监听器组件,具体实现使用策略模式实现,统一由本类处理后进行相应转发, * 多种event监听均在本类中实现注册监听,使用event模式便于程序解耦,程序处理逻辑更加清晰 * @author 夜郎king */@Componentpublic class YelangSpringListener { private static final Logger sys_user_logger = LoggerFactory.getLogger("sys-user");  @EventListener public void fileUploadEventRegister(FileUploadEvent event){  try {   sys_user_logger.info("当前处理线程名称:" + Thread.currentThread().getName());   FileEntity fileEntity = event.getFileEntity();   if(StringUtils.isNotEmpty(fileEntity.getTablename())){    FileUploadServiceRegisterEnum rigisterEnum = null;    if(StringUtils.isNotBlank(fileEntity.getBizType())) {//业务类型不为空,则根据表名和业务名称来查找执行service     rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableNameAndBizType(fileEntity.getTablename(), fileEntity.getBizType());    }else {     rigisterEnum = FileUploadServiceRegisterEnum.getEnumByTableName(fileEntity.getTablename());    }    if(null != rigisterEnum && StringUtils.isNotEmpty(rigisterEnum.getExecService())){     String execService = rigisterEnum.getExecService();     IFileUploadCallbackService service = SpringUtils.getBean(execService);     service.process(fileEntity);    }else{     sys_user_logger.info("未注册文件上传监听回调处理器.");    }   }  } catch (Exception e) {   sys_user_logger.error("文件上传事件监听发生错误.",e);  } }}

图片

图片

上面的逻辑中,重点就是找到回调的具体枚举实例,然后使用Spring的IOC机制,找到注册到Spring上下文中的IFileUploadCallbackService

if(null != rigisterEnum && StringUtils.isNotEmpty(rigisterEnum.getExecService())){ String execService = rigisterEnum.getExecService(); IFileUploadCallbackService service = SpringUtils.getBean(execService); service.process(fileEntity);}

然后调用process方法开始进行文件的处理。

3、文件具体处理逻辑

为了让不同的业务实现不同的业务处理需要,我们将文件处理方法封装成统一的一个接口,然后通过不同的实例类来进行实现。接口的定义如下:

package com.yelang.project.common.service;import com.yelang.project.webupload.domain.FileEntity;public interface IFileUploadCallbackService { /**  * 文件上传事件监听器回调服务接口,封装公共服务,可以读取相关表格或修改业务表,具体实现由各实现类来完成  * @param fileEntity 文件实体  * @throws Exception  */ void process(FileEntity fileEntity) throws Exception;}

然后定义统一的文件处理实现类,实现上述的接口,并实现具体的文件处理方法。

package com.yelang.project.common.service.impl; import org.slf4j.Logger;import org.slf4j.LoggerFactory;import org.springframework.beans.factory.annotation.Autowired;import org.springframework.stereotype.Service;import org.springframework.transaction.annotation.Propagation;import org.springframework.transaction.annotation.Transactional;import com.yelang.common.utils.StringUtils;import com.yelang.project.common.service.IFileUploadCallbackService;import com.yelang.project.extend.student.domain.Student;import com.yelang.project.extend.student.service.IStudentService;import com.yelang.project.webupload.domain.FileEntity;@Service("studentUploadCallbackServiceImpl")public class StudentUploadCallbackServiceImpl implements IFileUploadCallbackService{ private static final Logger logger = LoggerFactory.getLogger("sys-user"); @Autowired private IStudentService studentService; @Override @Transactional(propagation=Propagation.REQUIRED,rollbackFor=Exception.class) public void process(FileEntity fileEntity) throws Exception {  if(null != fileEntity && StringUtils.isNotEmpty(fileEntity.getBid())){   String pkId = fileEntity.getBid();   Student stu = studentService.selectStudentById(Long.valueOf(pkId));   //System.out.println(fileEntity.getPath());   //System.out.println(stu.getName() + "\t" + stu.getAddress());   logger.info("开始处理........");   Thread.sleep(35 * 1000);//休眠35秒测试   logger.info("执行结束");  } }}

上面的程序逻辑比较简单,我们仅演示了如何从事件发布器中获取FileEntity实体的信息,同时打印相应的信息。在实际业务中,可以实现更复杂的业务。

4、实际处理实例

下面我们结合实际场景来看一下具体的实现及调用过程。

图片

图片

我们来看一下后台的处理信息的输出,

图片

可以很明显的看到,在后台的控制台已经成功的输出相应的内容,表明事件的发布、监听、处理按照预定的设计运行。

23:14:53.169 [http-nio-8080-exec-37] INFO  sys-user - [fileUploadEventRegister,32] - 当前处理线程名称:http-nio-8080-exec-3723:14:53.198 [http-nio-8080-exec-37] DEBUG c.y.p.e.s.m.S.selectById - [debug,137] - <==      Total: 123:14:53.199 [http-nio-8080-exec-37] INFO  sys-user - [process,32] - 开始处理........23:15:08.200 [http-nio-8080-exec-37] INFO  sys-user - [process,34] - 执行结束

四、总结

以上就是本文的主要内容,本文以WebUploader大文件上传组件为例,在大文件处理的场景中使用SpringEvent的事件发布机制,灵活的扩展对文件的处理需求。

本文通过代码实例的讲解,让您快速的了解如何在Spring中快速开发Event应用程序,同时使用枚举来实现动态的注册过程,实现方便灵活的注册机制。行文仓促,定有不足之处,真诚期待各位专家朋友在评论区批评指正,不甚感激。


点击全文阅读


本文链接:http://m.zhangshiyu.com/post/204732.html

<< 上一篇 下一篇 >>

  • 评论(0)
  • 赞助本站

◎欢迎参与讨论,请在这里发表您的看法、交流您的观点。

关于我们 | 我要投稿 | 免责申明

Copyright © 2020-2022 ZhangShiYu.com Rights Reserved.豫ICP备2022013469号-1