0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

使用AOP实现异步上传

Android编程精选 来源:稀土掘金技术社区 作者:天机术士 2022-07-25 15:30 次阅读

前言

相信很多系统里都有这一种场景:用户上传Excel,后端解析Excel生成相应的数据,校验数据并落库。这就引发了一个问题:如果Excel的行非常多,或者解析非常复杂,那么解析+校验的过程就非常耗时。

如果接口是一个同步的接口,则非常容易出现接口超时,进而返回的校验错误信息也无法展示给前端,这就需要从功能上解决这个问题。一般来说都是启动一个子线程去做解析工作,主线程正常返回,由子线程记录上传状态+校验结果到数据库。同时提供一个查询页面用于实时查询上传的状态和校验信息。

50f074e4-068e-11ed-ba43-dac502259ad0.png

进一步的,如果我们每一个上传的任务都写一次线程池异步+日志记录的代码就显得非常冗余。同时,非业务代码也侵入了业务代码导致代码可读性下降。

从通用性的角度上讲,这种业务场景非常适合模板方法的设计模式。即设计一个抽象类,定义上传的抽象方法,同时实现记录日志的方法,例如:

//伪代码,省略了一些步骤
@Slf4j
publicabstractclassAbstractUploadService<T>{
publicstaticThreadFactorycommonThreadFactory=newThreadFactoryBuilder().setNameFormat("-upload-pool-%d")
.setPriority(Thread.NORM_PRIORITY).build();
publicstaticExecutorServiceuploadExecuteService=newThreadPoolExecutor(10,20,300L,
TimeUnit.SECONDS,newLinkedBlockingQueue<>(1024),commonThreadFactory,newThreadPoolExecutor.AbortPolicy());

protectedabstractStringupload(Listdata);

protectedvoidexecute(StringuserName,Listdata){
//生成一个唯一编号
Stringuuid=UUID.randomUUID().toString().replace("-","");
uploadExecuteService.submit(()->{
//记录日志
writeLogToDb(uuid,userName,updateTime,"导入中");
//一个字符串,用于记录upload的校验信息
StringerrorLog="";
//执行上传
try{
errorLog=upload(data);
writeSuccess(uuid,"导入中",updateTime);
}catch(Exceptione){
LOGGER.error("导入错误",e);
//计入导入错误日志
writeFailToDb(uuid,"导入失败",e.getMessage(),updateTime);
}
/**
*检查一下upload是不是返回了错误日志,如果有,需要注意记录
*
*因为错误日志可能比较长,
*可以写入一个文件然后上传到公司的文件服务器,
*然后在查看结果的时候允许用户下载该文件,
*这里不展开只做示意
*/
if(StringUtils.isNotEmpty(errorLog)){
writeFailToDb(uuid,"导入失败",errorLog,updateTime);
}

});
}
}

如上文所示,模板方法的方式虽然能够极大地减少重复代码,但是仍有下面两个问题:

  • upload方法得限定死参数结构,一旦有变化,不是很容易更改参数类型or数量
  • 每个上传的service还是要继承一下这个抽象类,还是不够简便和优雅

为解决上面两个问题,我也经常进行思考,结果在某次自定义事务提交or回滚的方法的时候得到了启发。这个上传的逻辑过程和事务提交的逻辑过程非常像,都是在实际操作前需要做初始化操作,然后在异常或者成功的时候做进一步操作。这种完全可以通过环装切面的方式实现,由此,我写了一个小轮子给团队使用。

当然了,这个小轮子在本人所在的大团队内部使用的很好,但是不一定适合其他人,但是思路一样,大家可以扩展自己的功能

多说无益,上代码!

代码与实现

首先定义一个日志实体

publicclassFileUploadLog{
privateIntegerid;
//唯一编码
privateStringbatchNo;
//上传到文件服务器的文件key
privateStringkey;
//错误日志文件名
privateStringfileName;
//上传状态
privateIntegerstatus;
//上传人
privateStringcreateName;
//上传类型
privateStringuploadType;
//结束时间
privateDateendTime;
//开始时间
privateDatestartTime;
}

然后定义一个上传的类型枚举,用于记录是哪里操作的

publicenumUploadType{
未知(1,"未知"),
类型2(2,"类型2"),
类型1(3,"类型1");

privateintcode;
privateStringdesc;
privatestaticMapmap=newHashMap<>();
static{
for(UploadTypevalue:UploadType.values()){
map.put(value.code,value);
}
}

UploadType(intcode,Stringdesc){
this.code=code;
this.desc=desc;
}

publicintgetCode(){
returncode;
}

publicStringgetDesc(){
returndesc;
}

publicstaticUploadTypegetByCode(Integercode){
returnmap.get(code);
}
}

最后,定义一个注解,用于标识切点

@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public@interfaceUpload{
//记录上传类型
UploadTypetype()defaultUploadType.未知;
}

然后,编写切面

@Component
@Aspect
@Slf4j
publicclassUploadAspect{
publicstaticThreadFactorycommonThreadFactory=newThreadFactoryBuilder().setNameFormat("upload-pool-%d")
.setPriority(Thread.NORM_PRIORITY).build();
publicstaticExecutorServiceuploadExecuteService=newThreadPoolExecutor(10,20,300L,
TimeUnit.SECONDS,newLinkedBlockingQueue<>(1024),commonThreadFactory,newThreadPoolExecutor.AbortPolicy());


@Pointcut("@annotation(com.aaa.bbb.Upload)")
publicvoiduploadPoint(){}

@Around(value="uploadPoint()")
publicObjectuploadControl(ProceedingJoinPointpjp){
//获取方法上的注解,进而获取uploadType
MethodSignaturesignature=(MethodSignature)pjp.getSignature();
Uploadannotation=signature.getMethod().getAnnotation(Upload.class);
UploadTypetype=annotation==null?UploadType.未知:annotation.type();
//获取batchNo
StringbatchNo=UUID.randomUUID().toString().replace("-","");
//初始化一条上传的日志,记录开始时间
writeLogToDB(batchNo,type,newDate)
//线程池启动异步线程,开始执行上传的逻辑,pjp.proceed()就是你实现的上传功能
uploadExecuteService.submit(()->{
try{
StringerrorMessage=pjp.proceed();
//没有异常直接成功
if(StringUtils.isEmpty(errorMessage)){
//成功,写入数据库,具体不展开了
writeSuccessToDB(batchNo);
}else{
//失败,因为返回了校验信息
fail(errorMessage,batchNo);
}
}catch(Throwablee){
LOGGER.error("导入失败:",e);
//失败,抛了异常,需要记录
fail(e.toString(),batchNo);
}
});
returnnewObject();
}

privatevoidfail(Stringmessage,StringbatchNo){
//生成上传错误日志文件的文件key
Strings3Key=UUID.randomUUID().toString().replace("-","");
//生成文件名称
StringfileName="错误日志_"+
DateUtil.dateToString(newDate(),"yyyy年MM月dd日HH时mm分ss秒")+ExportConstant.txtSuffix;
StringfilePath="/home/xxx/xxx/"+fileName;
//生成一个文件,写入错误数据
Filefile=newFile(filePath);
OutputStreamoutputStream=null;
try{
outputStream=newFileOutputStream(file);
outputStream.write(message.getBytes());

}catch(Exceptione){
LOGGER.error("写入文件错误",e);
}finally{
try{
if(outputStream!=null)
outputStream.close();
}catch(Exceptione){
LOGGER.error("关闭错误",e);
}
}
//上传错误日志文件到文件服务器,我们用的是s3
upFileToS3(file,s3Key);
//记录上传失败,同时记录错误日志文件地址到数据库,方便用户查看错误信息
writeFailToDB(batchNo,s3Key,fileName);
//删除文件,防止硬盘爆炸
deleteFile(file)
}

}

至此整个异步上传功能就完成了,是不是很简单?(笑)

那么怎么使用呢?更简单,只需要在service层加入注解即可,顶多就是把错误信息return出去。

@Upload(type=UploadType.类型1)
publicStringupload(Listitems){
if(items==null||items.size()==0){
return;
}
//校验
Stringerror=uploadCheck(items);
if(StringUtils.isNotEmpty){
returnerror;
}
//删除旧的
deleteAll();
//插入新的
batchInsert(items);
}

结语

写了个小轮子提升团队整体开发效率感觉真不错。程序员的最高品质就是解放双手(偷懒?),然后成功的用自己写的代码把自己干毕业。。。。。。

审核编辑:汤梓红


声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 接口
    +关注

    关注

    33

    文章

    7633

    浏览量

    148449
  • Excel
    +关注

    关注

    4

    文章

    212

    浏览量

    55182
  • AOP
    AOP
    +关注

    关注

    0

    文章

    37

    浏览量

    11043

原文标题:实现一个小轮子—用AOP实现异步上传

文章出处:【微信号:AndroidPush,微信公众号:Android编程精选】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    Spring Boot如何实现异步任务

    Spring Boot 提供了多种方式来实现异步任务,这里介绍三种主要实现方式。 1、基于注解 @Async @Async 注解是 Spring 提供的一种轻量级异步方法
    的头像 发表于 09-30 10:32 482次阅读

    AOP知识详解

    今天我们继续看看AOP相关的知识,前面说到了Javassit,Spring AOP,通过该篇,让你对AOP有更完整的认识。 AOP 再看AOP
    的头像 发表于 09-25 11:14 447次阅读
    <b class='flag-5'>AOP</b>知识详解

    Spring AOP如何破解java应用

    预编译方式和运行期间动态代理实现程序功能的统一维护的一种技术。AOP是OOP的延续,从另一视角扩展了对面向对象编程的形式。利用AOP可以对业务逻辑的各个部分进行隔离,从而使得业务逻辑各部分之间的耦合度
    的头像 发表于 09-25 11:16 592次阅读
    Spring <b class='flag-5'>AOP</b>如何破解java应用

    Labview 异步TCP怎么实现

    Labview的TCP控件为同步超时模式实现的,在一对多场景明显不够用,效率低,看了官方例程也没有异步TCP的说明,该如何实现异步TCP呢?
    发表于 04-25 17:08

    具有AoP技术的雷达传感器

    毫米波雷达为汽车和工业应用提供了一种高度精确的感应方式,可提供富有洞察力的物体信息,如距离、角度和速度,从而实现更智能的感应解决方案,用于检测几厘米到几百米范围内的物体。通常,雷达传感器安装在由雷达
    发表于 11-04 06:32

    个体与群体思维状态下的AOP语言

    引入群体思维状态对GOAL进行改进,建立了个体与群体思维状态下的AOP语言IG-AOP,给出其语法和操作语义。举例证明该语言的表达力比GOAL强,可以较好地满足多Agent合作求解过程的刻
    发表于 04-16 10:17 11次下载

    AOP中使用标注改进日志功能的实现

    面向方面编程(AOP)可避免横切关注点对核心代码的不良影响,但AOP 中的方法签名匹配模式难以精确表达系统中的横切点,使得在大中型系统中直接使用连接点匹配方式捕获某些横
    发表于 04-17 09:01 25次下载

    基于动态AOP 和WebServices 的轻量级RBAC

    针对传统基于角色的访问控制系统在面向中小企业应用中的不足,设计了一个面向中小企业的基于WebServices 的轻量级RBAC 系统。利用动态AOP 技术将权限验证实现为方面,通过对WebS
    发表于 06-18 11:00 16次下载

    高速异步FIFO的设计与实现

    本文主要研究了用FPGA 芯片内部的EBRSRAM 来实现异步FIFO 设计方案,重点阐述了异步FIFO 的标志信号——空/满状态的设计思路,并且用VHDL 语言实现,最后进行了仿真验
    发表于 01-13 17:11 40次下载

    基于反射机制的AOP模型的研究_张波

    基于反射机制的AOP模型的研究_张波
    发表于 03-17 15:47 0次下载

    基于Iframe内联框架的异步文件上传与删除

    在Weh应用程序开发过程中,文件上传功能是个很常用又非常重要的功能,它要处理的内容主要包括:如何将上传的文件以文件的形式保存到服务器,上传Internet上的资源,提取相应文件名等信息传递到数据库
    发表于 11-11 10:20 5次下载
    基于Iframe内联框架的<b class='flag-5'>异步</b>文件<b class='flag-5'>上传</b>与删除

    基于AOP的科研申报系统的设计与实现

    也带来困难。针对上述问题,本论文使用面向方面编程(AOP)的思想来解决,利用AOP中的方面(Aspect)来对非功能属性进行建模,并采用UML中的类图进行描述,并给出其在科研申报系统中权限控制模块的实现,验证了面向方面编程在解决
    发表于 11-11 17:44 8次下载
    基于<b class='flag-5'>AOP</b>的科研申报系统的设计与<b class='flag-5'>实现</b>

    java Web如何实现文件的上传与下载

    文件上传概述,实现web开发中的文件上传功能,需完成如下二步操作: 在web页面中添加上传输入项在servlet 中读取上传文件的数据,
    发表于 03-06 11:03 7次下载
    java Web如何<b class='flag-5'>实现</b>文件的<b class='flag-5'>上传</b>与下载

    如何实现动态上传jar包热部署

    近期开发系统过程中遇到的一个需求,系统给定一个接口,用户可以自定义开发该接口的实现,并将实现打成jar包,上传到系统中。系统完成热部署,并切换该接口的实现
    的头像 发表于 06-20 16:57 1219次阅读

    AOP要怎么使用

    AOP(Aspect-Oriented Programming)经常会出现在面试过程中,AOP到底有没有用,要怎么使用呢。本篇来一起拨开迷雾! 1 第一个AOP示例 我们会一次将所有的通知类型都覆盖
    的头像 发表于 10-09 16:18 330次阅读
    <b class='flag-5'>AOP</b>要怎么使用