Spring Boot异步请求处理框架

HTTP超时 异步处理 线程池 多线程

Spring Boot异步请求处理框架

1、前言

​    在Spring Boot项目中,经常会遇到处理时间过长,导致出现HTTP请求超时问题,状态码:502。
​    例如一个文件导入接口需要导入一个Excel文件的学员记录,原来是针对一个班的学员,最多500条记录,1分钟的HTTP超时时长之内基本可以响应。现在将很多班级的学员混在一起,放在一个Excel文件中(这样可以提高操作人员的工作效率),比如5万条学员记录,于是就出现HTTP请求超时问题。
​    ​解决方案有:1)Ajax异步请求方式;2)WebSocket方式;3)异步请求处理方式:请求+轮询。
​    方案1,需要调整HTTP超时设置,Spring Boot开启异步处理(使用@EnableAsync和@Async),这种方式,问题是超时时长要设置多大,没有底。
​    方案2,需要前端支持Web2.0,对浏览器有所限制,代码也变得复杂。
​    方案3,使用异步请求处理。所谓异步请求处理,就是将请求异步化,前端发起请求后,后端很快就响应,返回一个任务ID,前端再用这个任务ID去轮询,获取处理进程和结果信息。需要两个接口:任务请求接口和任务信息轮询接口。
​    显然,对于长时间的业务处理,通过轮询,获取处理进程信息,可以获得较好的用户体验。正如大文件下载,用户可以了解下载的进度一样,业务处理同样可以通过输出处理日志信息和进度,使得长时间业务处理过程可视化,而不至于让用户长时间面对一个在空转鼠标符号。
​    本文针对方案3,提出一种通用的处理框架。使用这个通用的异步请求处理框架,可以适应各种不同的需要长时间异步处理的业务需求。

2、异步请求处理框架描述

​    本异步请求处理框架,主要包括任务信息、任务执行类(Runnable)、任务管理器。

2.1、任务信息对象类TaskInfo

​    任务信息对象,用于存储任务信息,其生命周期为:创建任务==>加入任务队列==>加入线程池工作线程队列==>任务执行==>任务执行完成==>任务对象缓存超期销毁。
​    1)任务识别信息:
​    1.1)任务ID:任务ID用于识别任务信息,一个任务ID对应一个任务,是全局唯一的,不区分任务类型。
​    1.2)任务名称:即任务类型的名称,对应于业务处理类型名称,如查询商品单价、查询商品库存等,这样可方便可视化识别任务。一个任务名称可以有多个任务实例。
​    1.3)会话ID(sessionId):用于身份识别,这样只有请求者才能根据返回的任务ID来查询任务信息,其它用户无权访问。想象一下同步请求,谁发起请求,响应给谁。
​    2)任务调用信息:使得任务管理器可以使用反射方法,调用任务处理方法。
​    2.1)任务处理对象:这是业务处理对象,为Object类型,一般为Service实现类对象。
​    2.2)任务处理方法:这是一个Method对象类型,为异步处理的业务处理方法对象。这个方法必须是public的方法。
&#x200B;    2.3&#xFF09;&#x4EFB;&#x52A1;&#x65B9;&#x6CD5;&#x53C2;&#x6570;&#xFF1A;&#x8FD9;&#x662F;&#x4E00;&#x4E2A;Map<string,object>&#x7C7B;&#x578B;&#x5B57;&#x5178;&#x5BF9;&#x8C61;&#xFF0C;&#x53EF;&#x9002;&#x5E94;&#x4EFB;&#x610F;&#x53C2;&#x6570;&#x7ED3;&#x6784;&#x3002;
&#x200B;    3&#xFF09;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x8FC7;&#x7A0B;&#x548C;&#x7ED3;&#x679C;&#x76F8;&#x5173;&#x4FE1;&#x606F;&#xFF1A;&#x53EF;&#x4EE5;&#x63D0;&#x4F9B;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x8FC7;&#x7A0B;&#x548C;&#x7ED3;&#x679C;&#x53EF;&#x89C6;&#x5316;&#x7684;&#x4FE1;&#x606F;&#x3002;
&#x200B;    3.1&#xFF09;&#x4EFB;&#x52A1;&#x72B6;&#x6001;&#xFF1A;&#x8868;&#x793A;&#x4EFB;&#x52A1;&#x76EE;&#x524D;&#x7684;&#x5904;&#x7406;&#x72B6;&#x6001;&#xFF0C;0-&#x672A;&#x5904;&#x7406;&#xFF0C;1-&#x5904;&#x7406;&#x4E2D;&#xFF0C;2-&#x5904;&#x7406;&#x7ED3;&#x675F;&#x3002;
&#x200B;    3.2&#xFF09;&#x5904;&#x7406;&#x65E5;&#x5FD7;&#xFF1A;&#x8FD9;&#x662F;&#x4E00;&#x4E2A;List<string>&#x7C7B;&#x578B;&#x7684;&#x5B57;&#x7B26;&#x4E32;&#x5217;&#x8868;&#xFF0C;&#x7528;&#x4E8E;&#x5B58;&#x653E;&#x5904;&#x7406;&#x65E5;&#x5FD7;&#x3002;&#x5904;&#x7406;&#x65E5;&#x5FD7;&#x683C;&#x5F0F;&#x5316;&#xFF1A;"time level taskId taskName --- logInfo"&#xFF0C;&#x4FBF;&#x4E8E;&#x524D;&#x7AEF;&#x5C55;&#x793A;&#x3002;
&#x200B;    3.3&#xFF09;&#x5904;&#x7406;&#x8FDB;&#x5EA6;&#x767E;&#x5206;&#x6BD4;&#xFF1A;&#x8FD9;&#x662F;double&#x7C7B;&#x578B;&#x6570;&#x636E;&#xFF0C;0.0-100.0&#xFF0C;&#x4E1A;&#x52A1;&#x5355;&#x5143;&#x53EF;&#x89C6;&#x9700;&#x8981;&#x4F7F;&#x7528;&#x3002;
&#x200B;    3.4&#xFF09;&#x5904;&#x7406;&#x7ED3;&#x679C;&#xFF1A;&#x8FD9;&#x662F;&#x4E00;&#x4E2A;Object&#x7C7B;&#x578B;&#x5BF9;&#x8C61;&#xFF0C;&#x771F;&#x5B9E;&#x6570;&#x636E;&#x7C7B;&#x578B;&#x7531;&#x4E1A;&#x52A1;&#x5355;&#x5143;&#x7EA6;&#x5B9A;&#x3002;&#x5728;&#x672A;&#x5904;&#x7406;&#x7ED3;&#x675F;&#x524D;&#xFF0C;&#x8BE5;&#x503C;&#x4E3A;null&#xFF0C;&#x5904;&#x7406;&#x7ED3;&#x675F;&#x540E;&#xFF0C;&#x5982;&#x6709;&#x8FD4;&#x56DE;&#x503C;&#xFF0C;&#x6B64;&#x65F6;&#x8D4B;&#x503C;&#x3002;
&#x200B;    3.5&#xFF09;&#x8FD4;&#x56DE;&#x7801;&#xFF1A;&#x4E1A;&#x52A1;&#x5904;&#x7406;&#xFF0C;&#x53EF;&#x80FD;&#x9047;&#x5230;&#x5F02;&#x5E38;&#xFF0C;&#x5982;&#x9700;&#x8BBE;&#x7F6E;&#x8FD4;&#x56DE;&#x7801;&#xFF0C;&#x6B64;&#x5904;&#x8D4B;&#x503C;&#x3002;
&#x200B;    3.6&#xFF09;&#x8FD4;&#x56DE;&#x6D88;&#x606F;&#xFF1A;&#x4E0E;&#x8FD4;&#x56DE;&#x7801;&#x76F8;&#x8054;&#x7CFB;&#x7684;&#x63D0;&#x793A;&#x4FE1;&#x606F;&#x3002;
&#x200B;    3.7&#xFF09;&#x5F00;&#x59CB;&#x5904;&#x7406;&#x65F6;&#x95F4;&#x6233;&#xFF1A;&#x5728;&#x4EFB;&#x52A1;&#x542F;&#x52A8;&#xFF08;&#x5F00;&#x59CB;&#x6267;&#x884C;&#x65F6;&#xFF09;&#x8BBE;&#x7F6E;&#xFF0C;&#x7528;&#x4E8E;&#x8BA1;&#x7B97;&#x4E1A;&#x52A1;&#x5904;&#x7406;&#x7684;&#x8017;&#x65F6;&#x65F6;&#x957F;&#x3002;
&#x200B;    4&#xFF09;&#x4EFB;&#x52A1;&#x7F13;&#x5B58;&#x5230;&#x671F;&#x65F6;&#x95F4;&#xFF1A;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x5B8C;&#x6210;&#x540E;&#xFF0C;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x4F1A;&#x7F13;&#x5B58;&#x4E00;&#x6BB5;&#x65F6;&#x95F4;&#xFF08;&#x5982;60&#x79D2;&#xFF09;&#xFF0C;&#x7B49;&#x5F85;&#x524D;&#x7AEF;&#x83B7;&#x53D6;&#xFF0C;&#x8D85;&#x671F;&#x540E;&#xFF0C;&#x4EFB;&#x52A1;&#x5BF9;&#x8C61;&#x88AB;&#x9500;&#x6BC1;&#xFF0C;&#x610F;&#x5473;&#x7740;&#x518D;&#x4E5F;&#x65E0;&#x6CD5;&#x83B7;&#x53D6;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x4E86;&#x3002;&#x540E;&#x7AEF;&#x7CFB;&#x7EDF;&#x4E0D;&#x53EF;&#x80FD;&#x7D2F;&#x79EF;&#x5B58;&#x653E;&#x8D85;&#x671F;&#x7684;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#xFF0C;&#x5426;&#x5219;&#x53EF;&#x80FD;&#x5BFC;&#x81F4;OOM(Out Of Memory)&#x5F02;&#x5E38;&#x3002;
</string></string,object>

2.2、任务执行类TaskRunnable

&#x200B;    &#x4EFB;&#x52A1;&#x6267;&#x884C;&#x7C7B;&#xFF0C;&#x5B9E;&#x73B0;Runnable&#x63A5;&#x53E3;&#xFF0C;&#x662F;&#x4E3A;&#x7EBF;&#x7A0B;&#x6C60;&#x7684;&#x5DE5;&#x4F5C;&#x7EBF;&#x7A0B;&#x63D0;&#x4F9B;&#x5904;&#x7406;&#x65B9;&#x6CD5;&#x3002;
&#x200B;    &#x4EFB;&#x52A1;&#x6267;&#x884C;&#x7C7B;&#xFF0C;&#x4F7F;&#x7528;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5BF9;&#x8C61;&#x4F5C;&#x4E3A;&#x53C2;&#x6570;&#xFF0C;&#x5E76;&#x8C03;&#x7528;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x7684;&#x4EFB;&#x52A1;&#x8C03;&#x7528;&#x4FE1;&#x606F;&#xFF0C;&#x4F7F;&#x7528;&#x53CD;&#x5C04;&#x65B9;&#x6CD5;&#xFF0C;&#x6765;&#x6267;&#x884C;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x3002;

2.3、任务管理器类TaskManService

&#x200B;    &#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#xFF0C;&#x5168;&#x5C40;&#x5BF9;&#x8C61;&#xFF0C;&#x4F7F;&#x7528;@Service&#x6CE8;&#x89E3;&#xFF0C;&#x52A0;&#x5165;Spring&#x5BB9;&#x5668;&#x3002;&#x8FD9;&#x6837;&#xFF0C;&#x4EFB;&#x4F55;&#x9700;&#x8981;&#x5F02;&#x6B65;&#x5904;&#x7406;&#x7684;&#x4E1A;&#x52A1;&#x90FD;&#x53EF;&#x4EE5;&#x8BBF;&#x95EE;&#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#x3002;
&#x200B;    &#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#xFF0C;&#x5305;&#x542B;&#x4E0B;&#x5217;&#x5C5E;&#x6027;&#xFF1A;
&#x200B;    1&#xFF09;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#xFF1A;LinkedBlockingQueue<taskinfo>&#x7C7B;&#x578B;&#xFF0C;&#x8003;&#x8651;&#x5230;OOM&#x95EE;&#x9898;&#xFF0C;&#x5BB9;&#x91CF;&#x4F7F;&#x7528;&#x6709;&#x9650;&#x503C;&#xFF0C;&#x5982;1&#x4E07;&#xFF0C;&#x5373;&#x6700;&#x5927;&#x7F13;&#x5B58;1&#x4E07;&#x4E2A;&#x4EFB;&#x52A1;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x4E8C;&#x7EA7;&#x7F13;&#x5B58;&#x3002;
&#x200B;    2&#xFF09;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5B57;&#x5178;&#xFF1A;Map<integer,taskinfo>&#x7C7B;&#x578B;&#xFF0C;key&#x4E3A;taskId&#xFF0C;&#x76EE;&#x7684;&#x662F;&#x4E3A;&#x4E86;&#x65B9;&#x4FBF;&#x6839;&#x636E;taskId&#x5FEB;&#x901F;&#x67E5;&#x8BE2;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x3002;
&#x200B;    3&#xFF09;&#x7EBF;&#x7A0B;&#x6C60;&#xFF1A;ThreadPoolExecutor&#x7C7B;&#x578B;&#xFF0C;&#x5DE5;&#x4F5C;&#x7EBF;&#x7A0B;&#x961F;&#x5217;&#x957F;&#x5EA6;&#x4E3A;&#x7EBF;&#x7A0B;&#x6C60;&#x7684;&#x6700;&#x5927;&#x7EBF;&#x7A0B;&#x6570;&#xFF0C;&#x76F8;&#x5F53;&#x4E8E;&#x4E00;&#x7EA7;&#x7F13;&#x5B58;&#x3002;&#x53EF;&#x4EE5;&#x8BBE;&#x7F6E;&#x6838;&#x5FC3;&#x7EBF;&#x7A0B;&#x6570;&#xFF0C;&#x6700;&#x5927;&#x7EBF;&#x7A0B;&#x6570;&#xFF0C;&#x5DE5;&#x4F5C;&#x7EBF;&#x7A0B;&#x961F;&#x5217;&#x957F;&#x5EA6;&#x7B49;&#x53C2;&#x6570;&#xFF0C;&#x5982;&#x8BBE;&#x7F6E;&#x6838;&#x5FC3;&#x7EBF;&#x7A0B;&#x6570;&#x4E3A;5&#xFF0C;&#x6700;&#x5927;&#x7EBF;&#x7A0B;&#x6570;&#x4E3A;100&#xFF0C;&#x5DE5;&#x4F5C;&#x7EBF;&#x7A0B;&#x961F;&#x5217;&#x957F;&#x5EA6;&#x4E3A;100&#x3002;&#x7EBF;&#x7A0B;&#x5DE5;&#x5382;ThreadFactory&#x4F7F;&#x7528;Executors.defaultThreadFactory()&#x3002;
&#x200B;    4&#xFF09;&#x4EFB;&#x52A1;ID&#x8BA1;&#x6570;&#x5668;&#xFF1A;AtomicInteger&#x7C7B;&#x578B;&#xFF0C;&#x7528;&#x4E8E;&#x5206;&#x914D;&#x552F;&#x4E00;&#x7684;&#x4EFB;&#x52A1;ID&#x3002;
&#x200B;    5&#xFF09;&#x76D1;&#x89C6;&#x7EBF;&#x7A0B;&#xFF1A;&#x7528;&#x4E8E;&#x4EFB;&#x52A1;&#x8C03;&#x5EA6;&#xFF0C;&#x4EE5;&#x53CA;&#x68C0;&#x67E5;&#x7F13;&#x5B58;&#x5230;&#x671F;&#x65F6;&#x95F4;&#x8D85;&#x671F;&#x7684;&#x5DF2;&#x7ED3;&#x675F;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x3002;
&#x200B;    6&#xFF09;&#x76D1;&#x89C6;&#x7EBF;&#x7A0B;&#x7684;&#x6267;&#x884C;&#x7C7B;&#x5BF9;&#x8C61;&#xFF1A;Runnable&#x5BF9;&#x8C61;&#xFF0C;&#x63D0;&#x4F9B;&#x76D1;&#x89C6;&#x7EBF;&#x7A0B;&#x7684;&#x6267;&#x884C;&#x65B9;&#x6CD5;&#x3002;
&#x200B;    7&#xFF09;&#x4E0A;&#x6B21;&#x68C0;&#x67E5;&#x65F6;&#x95F4;&#x6233;&#xFF1A;&#x7528;&#x4E8E;&#x68C0;&#x67E5;&#x7F13;&#x5B58;&#x5230;&#x671F;&#x65F6;&#x95F4;&#xFF0C;&#x6BCF;&#x79D2;1&#x6B21;&#x68C0;&#x67E5;&#x3002;
&#x200B;    &#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#xFF0C;&#x5305;&#x542B;&#x4E0B;&#x5217;&#x63A5;&#x53E3;&#x65B9;&#x6CD5;&#xFF1A;
&#x200B;    1&#xFF09;&#x6DFB;&#x52A0;&#x4EFB;&#x52A1;&#xFF1A;addTask&#xFF0C;&#x83B7;&#x53D6;sessionId&#xFF0C;&#x68C0;&#x67E5;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x5BF9;&#x8C61;&#x3001;&#x65B9;&#x6CD5;&#x53CA;&#x53C2;&#x6570;&#x662F;&#x5426;&#x4E3A;null&#xFF0C;&#x7136;&#x540E;&#x5206;&#x914D;&#x4EFB;&#x52A1;ID&#xFF0C;&#x521B;&#x5EFA;&#x4EFB;&#x52A1;&#x5BF9;&#x8C61;&#xFF0C;&#x52A0;&#x5165;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x3002;&#x5982;&#x679C;&#x53C2;&#x6570;&#x4E3A;void&#xFF0C;&#x4E5F;&#x9700;&#x8981;&#x6784;&#x9020;&#x4E00;&#x4E2A;&#x7A7A;&#x7684;Map<string,object>&#x5B57;&#x5178;&#x5BF9;&#x8C61;&#x3002;&#x5982;&#x679C;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x672A;&#x6EE1;&#xFF0C;&#x5C31;&#x5C06;&#x4EFB;&#x52A1;&#x52A0;&#x5165;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x4E2D;&#xFF0C;&#x5E76;&#x8FD4;&#x56DE;&#x5305;&#x542B;&#x4EFB;&#x52A1;ID&#x7684;&#x5B57;&#x5178;&#xFF0C;&#x5426;&#x5219;&#x629B;&#x51FA;&#x201C;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x5DF2;&#x6EE1;&#x201D;&#x7684;&#x5F02;&#x5E38;&#x4FE1;&#x606F;&#x3002;
&#x200B;    2&#xFF09;&#x83B7;&#x53D6;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#xFF1A;getTaskInfo&#xFF0C;&#x53C2;&#x6570;&#x4E3A;request&#x548C;&#x4EFB;&#x52A1;ID&#xFF0C;&#x5982;&#x679C;sessionId&#x4E0E;&#x8BF7;&#x6C42;&#x65F6;&#x76F8;&#x540C;&#xFF0C;&#x4E14;&#x4EFB;&#x52A1;&#x5BF9;&#x8C61;&#x80FD;&#x5728;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5B57;&#x5178;&#x4E2D;&#x627E;&#x5230;&#xFF0C;&#x5C31;&#x8FD4;&#x56DE;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5BF9;&#x8C61;&#xFF0C;&#x5426;&#x5219;&#x629B;&#x51FA;&#x76F8;&#x5173;&#x5F02;&#x5E38;&#x3002;
&#x200B;    &#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#x7684;&#x6838;&#x5FC3;&#x65B9;&#x6CD5;&#xFF1A;
&#x200B;    1&#xFF09;&#x521D;&#x59CB;&#x5316;&#xFF1A;&#x4F7F;&#x7528;@PostConstruct&#x6CE8;&#x89E3;&#xFF0C;&#x542F;&#x52A8;&#x76D1;&#x89C6;&#x7EBF;&#x7A0B;&#xFF0C;&#x5E76;&#x9884;&#x542F;&#x52A8;&#x7EBF;&#x7A0B;&#x6C60;&#x7684;&#x4E00;&#x4E2A;&#x6838;&#x5FC3;&#x7EBF;&#x7A0B;&#x3002;
&#x200B;    2&#xFF09;&#x76D1;&#x89C6;&#x7EBF;&#x7A0B;&#x7684;&#x6267;&#x884C;&#x7C7B;run&#x65B9;&#x6CD5;&#xFF1A;&#x5B9E;&#x73B0;&#x6BCF;&#x79D2;&#x4E00;&#x6B21;&#x7684;&#x8D85;&#x671F;&#x5DF2;&#x5904;&#x7406;&#x7ED3;&#x675F;&#x7684;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x7684;&#x68C0;&#x67E5;&#xFF0C;&#x4EE5;&#x53CA;&#x4EFB;&#x52A1;&#x8C03;&#x5EA6;&#x3002;&#x4EFB;&#x52A1;&#x8C03;&#x5EA6;&#x65B9;&#x6CD5;&#xFF1A;
&#x200B;    2.1&#xFF09;&#x5982;&#x679C;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x975E;&#x7A7A;&#xFF0C;&#x4E14;&#x7EBF;&#x7A0B;&#x6C60;&#x672A;&#x6EE1;&#xFF0C;&#x5219;&#x53D6;&#x51FA;&#x4E00;&#x4E2A;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5BF9;&#x8C61;&#xFF0C;&#x5E76;&#x521B;&#x5EFA;&#x4E00;&#x4E2A;&#x4EFB;&#x52A1;&#x6267;&#x884C;&#x7C7B;&#x5BF9;&#x8C61;&#xFF0C;&#x52A0;&#x5165;&#x5230;&#x7EBF;&#x7A0B;&#x6C60;&#x7684;&#x5DE5;&#x4F5C;&#x7EBF;&#x7A0B;&#x961F;&#x5217;&#xFF08;execute&#x65B9;&#x6CD5;&#x52A0;&#x5165;&#xFF09;&#x3002;
&#x200B;    2.2&#xFF09;&#x5982;&#x679C;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x975E;&#x7A7A;&#xFF0C;&#x4E14;&#x7EBF;&#x7A0B;&#x6C60;&#x5DF2;&#x6EE1;&#xFF0C;&#x5219;&#x7B49;&#x5F85;100&#x6BEB;&#x79D2;&#x3002;
&#x200B;    2.3&#xFF09;&#x5982;&#x679C;&#x4EFB;&#x52A1;&#x961F;&#x5217;&#x4E3A;&#x7A7A;&#xFF0C;&#x5219;&#x7B49;&#x5F85;100&#x6BEB;&#x79D2;&#x3002;
</string,object></integer,taskinfo></taskinfo>

3、异步请求处理框架代码

3.1、任务信息对象类TaskInfo

&#x200B;    &#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5BF9;&#x8C61;&#x7C7B;TaskInfo&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.asyncproc;

import java.lang.reflect.Method;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;

import lombok.Data;

/**
 * @className   : TaskInfo
 * @description : 任务信息
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/17   1.0.0       sheng.zheng     初版
 *
 */
@Data
public class TaskInfo {

    // ////////////////////////////////////////////////
    // 任务识别信息

    // 任务ID
    private Integer taskId = 0;

    // sessionId,用于识别请求者
    private String sessionId = "";

    // 任务名称,即业务处理的名称,如查询商品最低价,导入学员名册
    private String taskName = "";

    // ////////////////////////////////////////////////
    // 任务执行相关的

    // 请求参数,使用字典进行封装,以便适应任意数据结构
    private Map params;

    // 处理对象,一般是service对象
    private Object procObject;

    // 处理方法
    private Method method;

    // ////////////////////////////////////////////////
    // 任务处理产生的数据,中间数据,结果

    // 处理状态,0-未处理,1-处理中,2-处理结束
    private int procStatus = 0;

    // 处理结果,数据类型由业务单元约定
    private Object result;

    // 处理日志,包括中间结果,格式化显示:Time level taskId taskName logInfo
    private List logList = new ArrayList();

    // 处理进度百分比
    private double progress = 0;

    // 到期时间,UTC,任务完成后才设置,超时后销毁
    private long expiredTime = 0;

    // 返回码,保留,0表示操作成功
    private int resultCode = 0;

    // 响应消息,保留
    private String message = "";

    // 开始处理时间,便于统计任务处理时长
    private long startTime = 0;

    // ////////////////////////////////////////////////
    // 日志相关的方法
    private DateTimeFormatter df = DateTimeFormatter.ofPattern("yyyy-MM-dd HH:mm:ss.SSS");

    // 添加处理日志
    public void addLogInfo(String level,String logInfo) {
        // 格式化显示:Time level taskId taskName logInfo
        LocalDateTime current = LocalDateTime.now();
        String strCurrent = current.format(df);
        String log = String.format("%s %s %d %s --- %s",
                strCurrent,level,taskId,taskName,logInfo);
        logList.add(log);
    }

    // ////////////////////////////////////////////////
    // 不同状态的参数设置接口

    // 设置任务初始化,未开始
    public void init(Integer taskId,String taskName,String sessionId,
            Object procObject,Method method,Map params) {
        this.procStatus = 0;
        this.taskId = taskId;
        this.taskName = taskName;
        this.sessionId = sessionId;
        this.procObject = procObject;
        this.method = method;
        this.params = params;
    }

    // 启动任务
    public void start() {
        this.procStatus = 1;
        addLogInfo(TaskConstants.LEVEL_INFO,"开始处理任务...");
        // 记录任务开始处理的时间
        startTime = System.currentTimeMillis();
    }

    // 结束任务
    public void finish(Object result) {
        this.result = result;
        this.procStatus = 2;
        // 设置结果缓存的到期时间
        expired();
    }

    // 处理异常
    public void error(int resultCode,String message) {
        this.resultCode = resultCode;
        this.message = message;
        this.procStatus = 2;
        // 设置结果缓存的到期时间
        expired();
    }

    // 设置过期过期
    public void expired() {
        long current = System.currentTimeMillis();
        this.expiredTime = current + TaskConstants.PROC_EXPIRE_TIME;
        long duration = 0;
        double second = 0.0;
        duration = current - startTime;
        second = duration / 1000.0;
        addLogInfo(TaskConstants.LEVEL_INFO,"任务处理结束,耗时(s):"+second);
    }
}

&#x200B;    &#x8BF4;&#x660E;&#xFF1A;&#x4EFB;&#x52A1;&#x4FE1;&#x606F;&#x5BF9;&#x8C61;&#x7C7B;TaskInfo&#x63D0;&#x4F9B;&#x4E86;&#x51E0;&#x4E2A;&#x5E38;&#x7528;&#x7684;&#x5904;&#x7406;&#x65B9;&#x6CD5;&#xFF0C;&#x5982;addLogInfo&#x3001;init&#x3001;start&#x3001;finish&#x3001;error&#xFF0C;&#x4FBF;&#x4E8E;&#x7B80;&#x5316;&#x5C5E;&#x6027;&#x503C;&#x8BBE;&#x7F6E;&#x3002;

3.2、任务执行类TaskRunnable

&#x200B;    &#x4EFB;&#x52A1;&#x6267;&#x884C;&#x7C7B;TaskRunnable&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.asyncproc;

import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import com.abc.example.common.utils.LogUtil;
import com.abc.example.exception.BaseException;
import com.abc.example.exception.ExceptionCodes;

/**
 * @className   : TaskRunnable
 * @description : 可被线程执行的任务执行类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/17   1.0.0       sheng.zheng     初版
 *
 */
public class TaskRunnable implements Runnable {
    // 任务信息
    private TaskInfo taskInfo;

    public TaskRunnable(TaskInfo taskInfo) {
        this.taskInfo = taskInfo;
    }

    // 获取任务ID
    public Integer getTaskId() {
        if (taskInfo != null) {
            return taskInfo.getTaskId();
        }
        return 0;
    }

    @Override
    public void run() {
        Object procObject = taskInfo.getProcObject();
        Method method = taskInfo.getMethod();

        try {
            // 使用反射方法,调用方法来处理任务
            method.invoke(procObject, taskInfo);
        }catch(BaseException e) {
            // 优先处理业务处理异常
            taskInfo.error(e.getCode(),e.getMessage());
            LogUtil.error(e);
        }catch(InvocationTargetException e) {
            taskInfo.error(ExceptionCodes.ERROR.getCode(),e.getMessage());
            LogUtil.error(e);
        }catch(IllegalAccessException e) {
            taskInfo.error(ExceptionCodes.ERROR.getCode(),e.getMessage());
            LogUtil.error(e);
        }catch(IllegalArgumentException e) {
            taskInfo.error(ExceptionCodes.ERROR.getCode(),e.getMessage());
            LogUtil.error(e);
        }catch(Exception e) {
            // 最后处理未知异常
            taskInfo.error(ExceptionCodes.ERROR.getCode(),e.getMessage());
            LogUtil.error(e);
        }
    }
}

3.3、任务常量类TaskConstants

&#x200B;    &#x4EFB;&#x52A1;&#x5E38;&#x91CF;&#x7C7B;TaskConstants&#xFF0C;&#x63D0;&#x4F9B;&#x5F02;&#x6B65;&#x8BF7;&#x6C42;&#x5904;&#x7406;&#x6846;&#x67B6;&#x6A21;&#x5757;&#x7684;&#x76F8;&#x5173;&#x5E38;&#x91CF;&#x8BBE;&#x7F6E;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.asyncproc;

/**
 * @className   : TaskConstants
 * @description : 任务处理相关常量
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/18   1.0.0       sheng.zheng     初版
 *
 */
public class TaskConstants {
    // 任务缓存过期时间,单位毫秒,即任务处理完成后,设置此时长,超期销毁
    public static final int PROC_EXPIRE_TIME = 60000;

    // 线程池核心线程数
    public static final int CORE_POOL_SIZE = 5;

    // 线程池最大线程数
    public static final int MAX_POOL_SIZE  = 100;

    // 线程池KeepAlive参数,单位秒
    public static final long KEEP_ALIVE_SECONDS = 10;

    // 任务队列最大数目
    public static final int MAX_TASK_NUMS  = 10000;

    // 日志信息告警等级
    public static final String LEVEL_INFO = "INFO";
    public static final String LEVEL_ERROR = "ERROR";

}

3.4、任务管理器类TaskManService

&#x200B;    &#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#x7C7B;TaskManService&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.asyncproc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
import javax.annotation.PostConstruct;
import javax.servlet.http.HttpServletRequest;
import org.springframework.stereotype.Service;
import com.abc.example.common.utils.LogUtil;
import com.abc.example.exception.BaseException;
import com.abc.example.exception.ExceptionCodes;

/**
 * @className   : TaskManService
 * @description : 任务管理器
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/18   1.0.0       sheng.zheng     初版
 *
 */
@Service
public class TaskManService {
    // 任务队列,考虑OOM(Out Of Memory)问题,限定任务队列长度,相当于二级缓存
    private BlockingQueue taskQueue =
            new LinkedBlockingQueue(TaskConstants.MAX_TASK_NUMS);

    // 任务信息字典,key为taskId,目的是为了方便根据taskId查询任务信息
    private Map taskMap = new HashMap();

    // 线程池,工作线程队列长度为线程池的最大线程数,相当于一级缓存
    private ThreadPoolExecutor executor = new ThreadPoolExecutor(
            TaskConstants.CORE_POOL_SIZE,
            TaskConstants.MAX_POOL_SIZE,
            TaskConstants.KEEP_ALIVE_SECONDS,
            TimeUnit.SECONDS,
            new LinkedBlockingQueue<>(TaskConstants.MAX_POOL_SIZE),
            Executors.defaultThreadFactory());

    // 任务ID计数器,累加
    private AtomicInteger taskIdCounter = new AtomicInteger();

    // 用于缓存上次检查时间
    private long lastTime = 0;

    // 监视线程,用于任务调度,以及检查已结束任务的缓存到期时间
    private Thread monitor;
    @PostConstruct
    public void init(){
        // 启动线程实例
        monitor = new Thread(checkRunnable);
        monitor.start();

        // 启动一个核心线程
        executor.prestartCoreThread();
    }

    // 检查已结束任务的缓存到期时间,超期的销毁
    private Runnable checkRunnable = new Runnable() {
        @Override
                public void run() {
            while (true) {
                long current = System.currentTimeMillis();
                if(current - lastTime >= 1000) {
                    // 离上次检查时间超过1秒
                    checkAndremove();
                    // 更新lastTime
                    lastTime = current;
                }
                synchronized(this) {
                    try {
                        // 检查任务队列
                        if(taskQueue.isEmpty()) {
                            // 如果任务队列为空,则等待100ms
                            Thread.sleep(100);
                        }else {
                            // 如果任务队列不为空
                            // 检查线程池队列
                            if (executor.getQueue().size() < TaskConstants.MAX_POOL_SIZE) {
                                // 如果线程池队列未满
                                // 从任务队列中获取一个任务
                                TaskInfo taskInfo = taskQueue.take();
                                // 创建Runnable对象
                                TaskRunnable tr = new TaskRunnable(taskInfo);
                                // 调用线程池执行任务
                                executor.execute(tr);
                            }else {
                                // 如果线程池队列已满,则等待100ms
                                Thread.sleep(100);
                            }
                        }
                    }catch (InterruptedException e) {
                        LogUtil.error(e);
                    }
                }
            }
        }
    };

    /**
     *
     * @methodName  : checkAndremove
     * @description : 检查并移除过期对象
     * @history :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/15   1.0.0       sheng.zheng     初版
     *
     */
    private void checkAndremove() {
        synchronized(taskMap) {
            if (taskMap.size() == 0) {
                // 如果无对象
                return;
            }
            long current = System.currentTimeMillis();
            Iterator> iter = taskMap.entrySet().iterator();
            while(iter.hasNext()) {
                Map.Entry entry = iter.next();
                TaskInfo taskInfo = entry.getValue();
                long expiredTime = taskInfo.getExpiredTime();
                if ((expiredTime != 0) && ((current - expiredTime) > TaskConstants.PROC_EXPIRE_TIME)) {
                    // 如果过期,移除
                    iter.remove();
                }
            }
        }
    }

    /**
     *
     * @methodName      : addTask
     * @description         : 添加任务
     * @param request   : request对象
     * @param taskName  : 任务名称
     * @param procObject    : 处理对象
     * @param method    : 处理方法
     * @param params    : 方法参数,透明传递到处理方法中
     * @return      : 处理ID,唯一标识该请求的处理
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/19   1.0.0       sheng.zheng     初版
     *
     */
    public Integer addTask(HttpServletRequest request,
            String taskName,Object procObject,Method method,
            Map params) {
        // 获取sessionId
        String sessionId = null;
        if (request.getSession() != null) {
            sessionId = request.getSession().getId();
        }else {
            // 无效的session
            throw new BaseException(ExceptionCodes.SESSION_IS_NULL);
        }

        // 空指针保护
        if (procObject == null) {
            throw new BaseException(ExceptionCodes.ARGUMENTS_ERROR,"procObject对象为null");
        }
        if (method == null) {
            throw new BaseException(ExceptionCodes.ARGUMENTS_ERROR,"method对象为null");
        }
        if (params == null) {
            throw new BaseException(ExceptionCodes.ARGUMENTS_ERROR,"params对象为null");
        }

        // 获取可用的任务ID
        Integer taskId = taskIdCounter.incrementAndGet();

        // 生成任务处理信息对象
        TaskInfo item = new TaskInfo();
        // 初始化任务信息
        item.init(taskId,taskName,sessionId,procObject,method,params);

        // 加入处理队列
        try {
            synchronized(taskQueue) {
                taskQueue.add(item);
            }
        }catch(IllegalStateException e) {
            // 队列已满
            throw new BaseException(ExceptionCodes.ADD_OBJECT_FAILED,"任务队列已满");
        }

        // 加入字典
        synchronized(taskMap) {
            taskMap.put(taskId, item);
        }

        return taskId;
    }

    /**
     *
     * @methodName      : getTaskInfo
     * @description         : 获取任务信息
     * @param request   : request对象
     * @param taskId    : 任务ID
     * @return      : TaskInfo对象
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/19   1.0.0       sheng.zheng     初版
     *
     */
    public TaskInfo getTaskInfo(HttpServletRequest request,Integer taskId) {
        TaskInfo item = null;
        synchronized(taskMap) {
            if (taskMap.containsKey(taskId)) {
                item = taskMap.get(taskId);
                String sessionId = request.getSession().getId();
                if (!sessionId.equals(item.getSessionId())) {
                    throw new BaseException(ExceptionCodes.TASKID_NOT_RIGHTS);
                }
            }else {
                throw new BaseException(ExceptionCodes.TASKID_NOT_EXIST);
            }
        }

        return item;
    }

}

3.5、异常处理类BaseException

&#x200B;    &#x5F02;&#x5E38;&#x5904;&#x7406;&#x7C7B;BaseException&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.exception;

import lombok.Data;

/**
 * @className   : BaseException
 * @description : 异常信息基类
 * @summary : 可以处理系统异常和自定义异常
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
@Data
public class BaseException extends RuntimeException{
    private static final long serialVersionUID = 4359709211352401087L;

    // 异常码
    private int  code ;

    // 异常信息ID
    private String messageId;

    // 异常信息
    private String message;

    // =============== 以下为各种构造函数,重载 ===================================

    public BaseException(String message) {
        this.message = message;
    }

    public BaseException(String message, Throwable e) {
        this.message = message;
    }

    public BaseException(int code, String message) {
        this.message = message;
        this.code = code;
    }

    public BaseException(ExceptionCodes e) {
        this.code = e.getCode();
        this.messageId = e.getMessageId();
        this.message = e.getMessage();
    }

    public BaseException(ExceptionCodes e,String message) {
        this.code = e.getCode();
        this.messageId = e.getMessageId();
        this.message = e.getMessage() + ":" + message;
    }

    public BaseException(int code, String message, Throwable e) {
        this.message = message;
        this.code = code;

    }
}

3.6、异常信息枚举类ExceptionCodes

&#x200B;    &#x5F02;&#x5E38;&#x4FE1;&#x606F;&#x679A;&#x4E3E;&#x7C7B;ExceptionCodes&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.exception;

/**
 * @className   : ExceptionCodes
 * @description : 异常信息枚举类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
public enum ExceptionCodes {
    // 0-99,reserved for common exception
    SUCCESS(0, "message.SUCCESS", "操作成功"),
    FAILED(1, "message.FAILED", "操作失败"),
    ERROR(99, "message.ERROR", "操作异常"),
    ARGUMENTS_ERROR(2, "message.ARGUMENTS_ERROR","参数错误"),
    TASKID_NOT_EXIST(16, "message.TASKID_NOT_EXIST","任务ID不存在,可能已过期销毁"),
    TASKID_NOT_RIGHTS(17, "message.TASKID_NOT_RIGHTS","无权访问此任务ID"),
    SESSION_IS_NULL(18, "message.SESSION_IS_NULL","session为空,请重新登录"),

    ARGUMENTS_IS_EMPTY(22, "message.ARGUMENTS_IS_EMPTY","参数值不能为空"),
    ADD_OBJECT_FAILED(30, "message.ADD_OBJECT_FAILED", "新增对象失败"),

    ;   // 定义结束

    // 返回码
    private int code;
    public int getCode() {
        return this.code;
    }

    // 返回消息ID
    private String messageId;
    public String getMessageId() {
        return this.messageId;
    }

    // 返回消息
    private String message;
    public String getMessage() {
        return this.message;
    }

    ExceptionCodes(int code, String messageId, String message) {
        this.code = code;
        this.messageId = messageId;
        this.message = message;
    }
}

3.7、通用异常处理类UniveralExceptionHandler

&#x200B;    &#x901A;&#x7528;&#x5F02;&#x5E38;&#x5904;&#x7406;&#x7C7B;UniveralExceptionHandler&#xFF0C;&#x8FD9;&#x662F;&#x4E00;&#x4E2A;&#x5F02;&#x5E38;&#x4FE1;&#x606F;&#x6355;&#x83B7;&#x7684;&#x62E6;&#x622A;&#x5668;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.exception;

import java.util.HashMap;
import java.util.Map;

import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ControllerAdvice;
import org.springframework.web.bind.annotation.ExceptionHandler;
import org.springframework.web.bind.annotation.ResponseBody;

/**
 * @className   : UniveralExceptionHandler
 * @description : 通用异常处理类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
@ControllerAdvice
public class UniveralExceptionHandler {
    Logger logger = LoggerFactory.getLogger(getClass());

    /**
     *
     * @methodName  : handleException
     * @description : 拦截非业务异常
     * @param e : Exception类型的异常
     * @return  : JSON格式的异常信息
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    @ResponseBody
    @ExceptionHandler(Exception.class)
    public Map handleException(Exception e) {
        //将异常信息写入日志
        logger.error(e.getMessage(), e);
        //输出通用错误代码和信息
        Map map = new HashMap<>();
        map.put("code", ExceptionCodes.ERROR.getCode());
        map.put("message", ExceptionCodes.ERROR.getMessage());
        return map;
    }

    /**
     *
     * @methodName  : handleBaseException
     * @description : 拦截业务异常
     * @param e     : BaseException类型的异常
     * @return      : JSON格式的异常信息
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    @ResponseBody
    @ExceptionHandler(BaseException.class)
    public Map handleBaseException(BaseException e) {
        //将异常信息写入日志
        logger.error("业务异常:code:{},messageId:{},message:{}", e.getCode(), e.getMessageId(), e.getMessage());
        //输出错误代码和信息
        Map map = new HashMap<>();
        map.put("code", e.getCode());
        map.put("message" ,e.getMessage());
        return map;
    }

}

3.8、日志工具类LogUtil

&#x200B;    &#x65E5;&#x5FD7;&#x5DE5;&#x5177;&#x7C7B;LogUtil&#xFF0C;&#x76F8;&#x5173;&#x65B9;&#x6CD5;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.common.utils;

import lombok.extern.slf4j.Slf4j;

/**
 * @className   : LogUtil
 * @description : 日志工具类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
@Slf4j
public class LogUtil {
    /**
     *
     * @methodName  : error
     * @description : 输出异常信息
     * @param e : Exception对象
     * @history :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    public static void error(Exception e) {
        e.printStackTrace();
        String ex = getString(e);
        log.error(ex);
    }

    /**
     *
     * @methodName  : getString
     * @description : 获取Exception的getStackTrace信息
     * @param ex    : Exception对象
     * @return  : 错误调用栈信息
     * @history :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    public static String getString(Exception ex) {

        StringBuilder stack = new StringBuilder();
        StackTraceElement[] sts = ex.getStackTrace();
        for (StackTraceElement st : sts) {
            stack.append(st.toString()).append("\r\n");
        }
        return stack.toString();
    }
}

4、异步请求处理测试例子

&#x200B;    &#x4E0B;&#x9762;&#x4F7F;&#x7528;&#x4E00;&#x4E2A;&#x6D4B;&#x8BD5;&#x4F8B;&#x5B50;&#xFF0C;&#x6765;&#x8BF4;&#x660E;&#x5982;&#x4F55;&#x4F7F;&#x7528;&#x6B64;&#x6846;&#x67B6;&#x3002;

4.1、异步任务的业务处理类

&#x200B;    &#x5047;&#x8BBE;&#x6709;&#x4E00;&#x4E2A;&#x6D4B;&#x8BD5;&#x4EFB;&#x52A1;&#x670D;&#x52A1;&#x7C7B;TestTaskService&#xFF0C;&#x7B80;&#x5355;&#x8D77;&#x89C1;&#xFF0C;&#x4E0D;&#x7528;&#x63A5;&#x53E3;&#x7C7B;&#x4E86;&#xFF0C;&#x76F4;&#x63A5;&#x5C31;&#x662F;&#x53EF;&#x5B9E;&#x4F8B;&#x5316;&#x7684;&#x7C7B;&#x3002;&#x8FD9;&#x4E2A;&#x7C7B;&#x6709;&#x4E00;&#x4E2A;&#x9700;&#x8981;&#x5F02;&#x6B65;&#x5904;&#x7406;&#x7684;&#x65B9;&#x6CD5;&#xFF0C;&#x65B9;&#x6CD5;&#x540D;&#x4E3A;testTask&#x3002;
&#x200B;    testTask&#x65B9;&#x6CD5;&#x53EA;&#x63A5;&#x53D7;TaskInfo&#x7C7B;&#x578B;&#x7684;&#x53C2;&#x6570;&#xFF0C;&#x4F46;&#x5B9E;&#x9645;&#x53C2;&#x6570;params&#x4E3A;Map&#x5B57;&#x5178;&#xFF08;&#x76F8;&#x5F53;&#x4E8E;JSON&#x5BF9;&#x8C61;&#xFF09;&#xFF0C;&#x5305;&#x542B;repeat&#x548C;delay&#xFF0C;&#x8FD9;&#x4E24;&#x4E2A;&#x53C2;&#x6570;&#x662F;testTask&#x65B9;&#x6CD5;&#x6240;&#x9700;&#x8981;&#x7684;&#x3002;&#x5904;&#x7406;&#x7ED3;&#x679C;result&#x6B64;&#x5904;&#x4E3A;&#x5B57;&#x7B26;&#x4E32;&#x7C7B;&#x578B;&#xFF0C;&#x8FD9;&#x4E2A;&#x7C7B;&#x578B;&#x5728;&#x5B9E;&#x9645;&#x5904;&#x7406;&#x65F6;&#x53EF;&#x4EE5;&#x662F;&#x4EFB;&#x610F;&#x7C7B;&#x578B;&#xFF0C;&#x53EA;&#x9700;&#x8981;&#x4E0E;&#x524D;&#x7AEF;&#x6709;&#x7EA6;&#x5B9A;&#x5373;&#x53EF;&#x3002;
&#x200B;    &#x4E3A;&#x65B9;&#x4FBF;&#x63A7;&#x5236;&#x5668;&#x8C03;&#x7528;&#xFF0C;TestTaskService&#x63D0;&#x4F9B;&#x4E24;&#x4E2A;&#x63A5;&#x53E3;&#x65B9;&#x6CD5;&#xFF1A;addAsyncTask&#x548C;getTaskInfo&#x3002;
&#x200B;    addAsyncTask&#x65B9;&#x6CD5;&#xFF0C;&#x6709;2&#x4E2A;&#x53C2;&#x6570;&#xFF0C;request&#x548C;&#x8BF7;&#x6C42;&#x53C2;&#x6570;params&#xFF0C;&#x8BF7;&#x6C42;&#x53C2;&#x6570;params&#x662F;&#x63A7;&#x5236;&#x5668;@RequestBody&#x7684;&#x8BF7;&#x6C42;&#x53C2;&#x6570;&#xFF0C;&#x6216;&#x8005;&#x91CD;&#x65B0;&#x5C01;&#x88C5;&#x7684;&#x9002;&#x5E94;testTask&#x5904;&#x7406;&#x7684;&#x53C2;&#x6570;&#x3002;&#x5BF9;&#x4E8E;&#x4E1A;&#x52A1;&#x5904;&#x7406;&#x7C7B;TestTaskService&#x6765;&#x8BF4;&#xFF0C;testTask&#x65B9;&#x6CD5;&#x9700;&#x8981;&#x4EC0;&#x4E48;&#x5F62;&#x5F0F;&#x548C;&#x7C7B;&#x578B;&#x7684;&#x53C2;&#x6570;&#xFF0C;&#x5C5E;&#x4E8E;&#x5185;&#x90E8;&#x7EA6;&#x5B9A;&#xFF0C;&#x53EA;&#x8981;&#x4E24;&#x8005;&#x5339;&#x914D;&#x5373;&#x53EF;&#x3002;&#x672C;&#x4F8B;&#x5B50;&#x6BD4;&#x8F83;&#x7B80;&#x5355;&#xFF0C;&#x76F4;&#x63A5;&#x900F;&#x4F20;HTTP&#x8BF7;&#x6C42;&#x53C2;&#x6570;&#xFF0C;&#x4F5C;&#x4E3A;&#x4EFB;&#x52A1;&#x5904;&#x7406;&#x7684;&#x65B9;&#x6CD5;&#x53C2;&#x6570;&#x3002;addAsyncTask&#x65B9;&#x6CD5;&#xFF0C;&#x6267;&#x884C;&#x8F93;&#x5165;&#x53C2;&#x6570;&#x6821;&#x9A8C;&#xFF08;&#x4E0D;&#x8981;&#x7B49;&#x6267;&#x884C;&#x4EFB;&#x52A1;&#x65F6;&#xFF0C;&#x518D;&#x53BB;&#x6821;&#x9A8C;&#x53C2;&#x6570;&#xFF09;&#xFF0C;&#x7136;&#x540E;&#x8C03;&#x7528;&#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;addTask&#x65B9;&#x6CD5;&#xFF0C;&#x52A0;&#x5165;&#x4E00;&#x4E2A;&#x4EFB;&#x52A1;&#xFF0C;&#x5E76;&#x83B7;&#x53D6;&#x4EFB;&#x52A1;ID&#xFF0C;&#x8FD4;&#x56DE;&#x524D;&#x7AEF;&#x3002;
&#x200B;    getTaskInfo&#x65B9;&#x6CD5;&#xFF0C;&#x6709;2&#x4E2A;&#x53C2;&#x6570;&#xFF0C;request&#x548C;&#x8BF7;&#x6C42;&#x53C2;&#x6570;params&#xFF0C;&#x8BF7;&#x6C42;&#x53C2;&#x6570;params&#x5305;&#x542B;&#x4EFB;&#x52A1;ID&#x53C2;&#x6570;&#xFF0C;&#x8C03;&#x7528;&#x4EFB;&#x52A1;&#x7BA1;&#x7406;&#x5668;&#x7684;getTaskInfo&#x65B9;&#x6CD5;&#x3002;&#x83B7;&#x53D6;TaskInfo&#x5BF9;&#x8C61;&#xFF0C;&#x7136;&#x540E;&#x5C4F;&#x853D;&#x4E00;&#x4E9B;&#x4E0D;&#x9700;&#x8981;&#x5C55;&#x793A;&#x7684;&#x4FE1;&#x606F;&#xFF0C;&#x8FD4;&#x56DE;&#x524D;&#x7AEF;&#x3002;getTaskInfo&#x65B9;&#x6CD5;&#x7528;&#x4E8E;&#x524D;&#x7AEF;&#x8F6E;&#x8BE2;&#xFF0C;&#x67E5;&#x8BE2;&#x4EFB;&#x52A1;&#x6267;&#x884C;&#x8FC7;&#x7A0B;&#x548C;&#x7ED3;&#x679C;&#x3002;

&#x200B;    &#x6D4B;&#x8BD5;&#x4EFB;&#x52A1;&#x670D;&#x52A1;&#x7C7B;TestTaskService&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.asyncproc;

import java.lang.reflect.Method;
import java.util.HashMap;
import java.util.Map;

import javax.servlet.http.HttpServletRequest;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import com.abc.example.common.utils.LogUtil;
import com.abc.example.common.utils.Utility;
import com.abc.example.exception.BaseException;
import com.abc.example.exception.ExceptionCodes;

/**
 * @className   : TestTaskService
 * @description : 测试任务服务类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/19   1.0.0       sheng.zheng     初版
 *
 */
@Service
public class TestTaskService {
    // 任务管理器
    @Autowired
    private TaskManService taskManService;

    /**
     *
     * @methodName      : addAsyncTask
     * @description         : 新增一个异步任务
     * @summary     : 新增测试任务类型的异步任务,
     *  如果处理队列未满,可立即获取任务ID:
     *      根据此任务ID,可以通过调用getTaskInfo,获取任务的处理进度信息;
     *      如果任务处理完毕,任务信息缓存60秒,过期后无法再获取;
     *  如果处理队列已满,返回任务队列已满的失败提示。
     * @param request   : request对象
     * @param params    : 请求参数,形式如下:
     *  {
     *      "repeat"    : 10,   // 重复次数,默认为10,可选
     *      "delay"     : 1000, // 延时毫秒数,默认为1000,可选
     *  }
     * @return          : JSON对象,形式如下:
     *  {
     *      "taskId"    : 1,    // 任务ID
     *  }
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/19   1.0.0       sheng.zheng     初版
     *
     */
    public Map addAsyncTask(HttpServletRequest request,
            Map params){
        // 参数校验
        Integer repeat = (Integer)params.get("repeat");
        if (repeat == null) {
            repeat = 10;
        }
        Integer delay = (Integer)params.get("delay");
        if (delay == null) {
            delay = 1000;
        }
        if (repeat  map = new HashMap();
        map.put("taskId", taskId);
        return map;
    }

    /**
     *
     * @methodName      : getTaskInfo
     * @description         : 根据任务ID,获取任务信息
     * @summary     : 如果任务ID对应的任务,属于当前用户,则可以有权获取信息,否则拒绝。
     *  如果任务状态为未处理或处理中,可以获取任务信息。
     *  如果任务状态为处理结束,且在缓存到期时间之前,也可以获取任务信息。否则,无法获取任务信息。
     * @param request   : request对象
     * @param params    : 请求参数,形式如下:
     *  {
     *      "taskId"    : 1,    // 任务ID,必选
     *  }
     * @return          : JSON对象,形式如下:
     *  {
     *      "taskId"    : 1,    // 任务ID
     *      "procStatus": 1,    // 处理状态,0-未处理,1-处理中,2-处理结束
     *      "progress"  : 0.0,  // 处理进度百分比
     *      "logList"   : [],   // 处理日志,字符串列表,格式化显示:Time level taskId taskName logInfo
     *      "result"    : "",   // 处理结果,字符串类型
     *      "resultCode": 0,    // 返回码,0表示操作成功
     *      "message"   : "",   // 响应消息
     *  }
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/19   1.0.0       sheng.zheng     初版
     *
     */
    public Map getTaskInfo(HttpServletRequest request,
            Map params){
        // 从请求参数中获取taskId
        Integer taskId = (Integer)params.get("taskId");
        if(taskId == null) {
            throw new BaseException(ExceptionCodes.ARGUMENTS_IS_EMPTY,"taskId");
        }
        // 调用任务管理器的方法,获取任务信息对象
        TaskInfo taskInfo = taskManService.getTaskInfo(request, taskId);

        // 返回值处理
        // 从任务信息对象,筛选一些属性返回
        Map map = new HashMap();
        // 任务ID
        map.put("taskId", taskId);
        // 任务状态
        map.put("procStatus", taskInfo.getProcStatus());
        // 处理结果,如任务未结束,则为null
        map.put("result", taskInfo.getResult());
        // 处理日志
        map.put("logList", taskInfo.getLogList());
        // 处理进度
        map.put("progress", taskInfo.getProgress());
        // 可能的返回码和消息
        map.put("resultCode", taskInfo.getResultCode());
        map.put("message", taskInfo.getMessage());

        return map;
    }

    /**
     *
     * @methodName      : testTask
     * @description         : 测试任务,异步执行
     * @param taskInfo  : 任务信息
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2022/08/19   1.0.0       sheng.zheng     初版
     *
     */
    public void testTask(TaskInfo taskInfo) {
        // 开始处理任务
        taskInfo.start();

        // 获取参数
        Map params = (Map)taskInfo.getParams();
        Integer repeat = (Integer)params.get("repeat");
        if (repeat == null) {
            repeat = 10;
        }
        Integer delay = (Integer)params.get("delay");
        if (delay == null) {
            delay = 1000;
        }

        String result = "";

        // 重复n次
        for(int i = 0; i < repeat; i++) {
            taskInfo.addLogInfo(TaskConstants.LEVEL_INFO, "处理步骤" + (i+1));
            // 显示处理进度
            taskInfo.setProgress((i+1)*1.0/repeat*100);
            // 延迟delay毫秒
            try {
                Thread.sleep(delay);
            } catch (InterruptedException e) {
                LogUtil.error(e);
            }
        }
        result = "OK";

        // 处理完毕
        taskInfo.finish(result);
    }
}

4.2、相关工具类

&#x200B;    4.1&#x4E2D;&#x6D89;&#x53CA;&#x5230;&#x5DE5;&#x5177;&#x7C7B;Utility&#x7684;getMethodByName&#x65B9;&#x6CD5;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;

package com.abc.example.common.utils;

import java.lang.reflect.Method;

/**
 * @className   : Utility
 * @description : 工具类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
public class Utility {
    /**
     *
     * @methodName      : getMethodByName
     * @description         : 根据方法名称获取方法对象
     * @param object    : 方法所在的类对象
     * @param methodName    : 方法名
     * @return      : Method类型对象
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    public static Method getMethodByName(Object object,String methodName) {
        Class class1 = object.getClass();
        Method retItem = null;
        Method[] methods = class1.getMethods();
        for (int i = 0; i < methods.length; i++) {
            Method item = methods[i];
            if (item.getName().equals(methodName)) {
                retItem = item;
                break;
            }
        }
        return retItem;
    }
}

4.3、异步测试任务控制器类AsycTestController

&#x200B;    &#x5F02;&#x6B65;&#x6D4B;&#x8BD5;&#x4EFB;&#x52A1;&#x63A7;&#x5236;&#x5668;&#x7C7B;AsycTestController&#xFF0C;&#x63D0;&#x4F9B;HTTP&#x8BBF;&#x95EE;&#x63A5;&#x53E3;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.controller;

import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;
import com.abc.example.asyncproc.TestTaskService;
import com.abc.example.vo.common.BaseResponse;

/**
 * @className   : AsycTestController
 * @description : 异步任务测试控制器类
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2022/08/19   1.0.0       sheng.zheng     初版
 *
 */
@RequestMapping("/asycTest")
@RestController
public class AsycTestController extends BaseController{
    @Autowired
    private TestTaskService tts;

    @RequestMapping("/addTask")
    public BaseResponse> addTask(HttpServletRequest request,
             @RequestBody Map params) {
        Map map = tts.addAsyncTask(request,params);
        return successResponse(map);
    }

    @RequestMapping("/getTaskInfo")
    public BaseResponse> getTaskInfo(HttpServletRequest request,
             @RequestBody Map params) {
        Map map = tts.getTaskInfo(request, params);
        return successResponse(map);
    }
}

4.4、基本响应消息体对象类BaseResponse

&#x200B;    &#x57FA;&#x672C;&#x54CD;&#x5E94;&#x6D88;&#x606F;&#x4F53;&#x5BF9;&#x8C61;&#x7C7B;BaseResponse&#xFF0C;&#x63D0;&#x4F9B;&#x6807;&#x51C6;&#x5F62;&#x5F0F;&#x7684;HTTP&#x54CD;&#x5E94;&#x683C;&#x5F0F;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.vo.common;

import lombok.Data;

/**
 * @className   : BaseResponse
 * @description : 基本响应消息体对象
 * @summary :
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
@Data
public class BaseResponse {
    // 响应码
    private int code;

    // 响应消息
    private String message;

    // 响应实体信息
    private T data;

    // 简单起见,屏蔽下列信息
    // 分页信息
    // private Page page;

    // 附加通知信息
    // private Additional additional;
}

4.5、控制器基类BaseController

&#x200B;    &#x63A7;&#x5236;&#x5668;&#x57FA;&#x7C7B;BaseController&#xFF0C;&#x652F;&#x6301;&#x4E8B;&#x52A1;&#x5904;&#x7406;&#xFF0C;&#x5E76;&#x63D0;&#x4F9B;&#x64CD;&#x4F5C;&#x6210;&#x529F;&#x7684;&#x65B9;&#x6CD5;&#xFF0C;&#x4EE3;&#x7801;&#x5982;&#x4E0B;&#xFF1A;
package com.abc.example.controller;

import java.util.List;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.bind.annotation.RestController;
import com.abc.example.exception.ExceptionCodes;
import com.abc.example.vo.common.BaseResponse;
import com.abc.example.vo.common.Page;
import com.github.pagehelper.PageInfo;

/**
 * @className   : BaseController
 * @description : 控制器基类
 * @summary : 支持事务处理,并提供操作成功的方法
 * @history :
 * ------------------------------------------------------------------------------
 * date         version     modifier        remarks
 * ------------------------------------------------------------------------------
 * 2021/01/01   1.0.0       sheng.zheng     初版
 *
 */
@RestController
@Transactional(propagation = Propagation.REQUIRED, rollbackFor = Exception.class)
public class BaseController {
    /**
     *
     * @methodName      : successResponse
     * @description         : 操作成功,返回信息不含数据体
     * @param       : 模板类型
     * @return      : 操作成功的返回码和消息,不含数据体
     * @history     :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    protected  BaseResponse successResponse() {
        BaseResponse response = new BaseResponse();
        response.setCode(ExceptionCodes.SUCCESS.getCode());
        response.setMessage(ExceptionCodes.SUCCESS.getMessage());
        return response;
    }

    /**
     *
     * @methodName      : successResponse
     * @description         : 操作成功,返回信息含数据体
     * @param       : 模板类型
     * @param data      : 模板类型的数据
     * @return          : 操作成功的返回码和消息,并包含数据体
     * @history             :
     * ------------------------------------------------------------------------------
     * date         version     modifier        remarks
     * ------------------------------------------------------------------------------
     * 2021/01/01   1.0.0       sheng.zheng     初版
     *
     */
    protected  BaseResponse successResponse(T data) {
        BaseResponse response = new BaseResponse();
        response.setCode(ExceptionCodes.SUCCESS.getCode());
        response.setMessage(ExceptionCodes.SUCCESS.getMessage());
        response.setData(data);
        return response;
    }
}

5、测试

&#x200B;    &#x4F7F;&#x7528;postman&#x6765;&#x6D4B;&#x8BD5;&#x3002;

5.1、添加测试任务

url:/asycTest/addTask
method: POST
request body:
{
    "repeat" : 10,  // &#x6B65;&#x9AA4;&#x6570;&#x76EE;
    "delay" : 2000  // &#x7B49;&#x5F85;&#x65F6;&#x957F;&#xFF0C;&#x6BEB;&#x79D2;
}
response:
{
    "code": 0,
    "message": "&#x64CD;&#x4F5C;&#x6210;&#x529F;",
    "data": {
        "taskId": 1
    }
}

5.2、获取任务信息,任务处理过程中

url:/asycTest/getTaskInfo
method: POST
request body:
{
    "taskId": 1
}
response:
{
    "code": 0,
    "message": "操作成功",
    "data": {
        "result": null,
        "procStatus": 1,
        "logList": [
            "2022-08-19 16:26:29.919 INFO 1 测试任务 --- 开始处理任务...",
            "2022-08-19 16:26:29.921 INFO 1 测试任务 --- 处理步骤1",
            "2022-08-19 16:26:31.923 INFO 1 测试任务 --- 处理步骤2",
            "2022-08-19 16:26:33.928 INFO 1 测试任务 --- 处理步骤3",
            "2022-08-19 16:26:35.933 INFO 1 测试任务 --- 处理步骤4",
            "2022-08-19 16:26:37.937 INFO 1 测试任务 --- 处理步骤5",
            "2022-08-19 16:26:39.942 INFO 1 测试任务 --- 处理步骤6",
            "2022-08-19 16:26:41.946 INFO 1 测试任务 --- 处理步骤7",
            "2022-08-19 16:26:43.951 INFO 1 测试任务 --- 处理步骤8",
        ],
        "resultCode": 0,
        "progress": 80.0,
        "message": "",
        "taskId": 1
    }
}

5.3、获取任务信息,处理结束

url:/asycTest/getTaskInfo
method: POST
request body:
{
    "taskId": 1
}
response:
{
    "code": 0,
    "message": "操作成功",
    "data": {
        "result": "OK",
        "procStatus": 2,
        "logList": [
            "2022-08-19 16:26:29.919 INFO 1 测试任务 --- 开始处理任务...",
            "2022-08-19 16:26:29.921 INFO 1 测试任务 --- 处理步骤1",
            "2022-08-19 16:26:31.923 INFO 1 测试任务 --- 处理步骤2",
            "2022-08-19 16:26:33.928 INFO 1 测试任务 --- 处理步骤3",
            "2022-08-19 16:26:35.933 INFO 1 测试任务 --- 处理步骤4",
            "2022-08-19 16:26:37.937 INFO 1 测试任务 --- 处理步骤5",
            "2022-08-19 16:26:39.942 INFO 1 测试任务 --- 处理步骤6",
            "2022-08-19 16:26:41.946 INFO 1 测试任务 --- 处理步骤7",
            "2022-08-19 16:26:43.951 INFO 1 测试任务 --- 处理步骤8",
            "2022-08-19 16:26:45.956 INFO 1 测试任务 --- 处理步骤9",
            "2022-08-19 16:26:47.960 INFO 1 测试任务 --- 处理步骤10",
            "2022-08-19 16:26:49.965 INFO 1 测试任务 --- 任务处理结束,耗时(s):20.044"
        ],
        "resultCode": 0,
        "progress": 100.0,
        "message": "",
        "taskId": 1
    }
}

5.4、获取任务信息,处理结束后任务信息缓存时间过期

url:/asycTest/getTaskInfo
method: POST
request body:
{
    "taskId": 1
}
response:
{
    "code": 16,
    "message": "任务ID不存在,可能已过期销毁"
}

6、异步处理的注意事项

&#x200B;    &#x7F16;&#x5199;&#x5F02;&#x6B65;&#x5904;&#x7406;&#x65B9;&#x6CD5;&#xFF08;&#x5982;&#x6D4B;&#x8BD5;&#x4F8B;&#x5B50;&#x4E2D;&#x7684;testTask&#x65B9;&#x6CD5;&#xFF09;&#x65F6;&#xFF0C;&#x6709;&#x51E0;&#x70B9;&#x6CE8;&#x610F;&#x4E8B;&#x9879;&#xFF1A;
&#x200B;    1&#xFF09;request&#x53C2;&#x6570;&#x5931;&#x6548;&#xFF0C;&#x5982;&#x679C;&#x8981;&#x4F20;&#x9012;request&#x53C2;&#x6570;&#xFF0C;&#x5C06;&#x4E4B;&#x5C01;&#x88C5;&#x5230;params&#x53C2;&#x6570;&#x4E2D;&#xFF0C;&#x4F1A;&#x6709;&#x5F88;&#x591A;&#x95EE;&#x9898;&#xFF0C;&#x5982;Session&#x4E3A;null&#x7B49;&#xFF0C;&#x53EF;&#x4EE5;&#x53C2;&#x8003;&#x6B64;&#x6587;&#xFF1A;https://www.shuzhiduo.com/A/gVdnaPp85W/&#x3002;
&#x200B;    2&#xFF09;&#x4E8B;&#x52A1;&#x5904;&#x7406;&#xFF1A;&#x7EBF;&#x7A0B;&#x65B9;&#x6CD5;&#x7684;@Transactional&#x4F1A;&#x5931;&#x6548;&#xFF0C;&#x5982;&#x9700;&#x8981;&#x4F7F;&#x7528;&#x4E8B;&#x52A1;&#x5904;&#x7406;&#xFF0C;&#x53EF;&#x53C2;&#x8003;&#x6B64;&#x6587;&#xFF1A;https://blog.csdn.net/u013844437/article/details/112983780&#x3002;

Original: https://www.cnblogs.com/alabo1999/p/16607827.html
Author: 阿拉伯1999
Title: Spring Boot异步请求处理框架

原创文章受到原创版权保护。转载请注明出处:https://www.johngo689.com/643715/

转载文章受原作者版权保护。转载请注明原作者出处!

(0)

大家都在看

亲爱的 Coder【最近整理,可免费获取】👉 最新必读书单  | 👏 面试题下载  | 🌎 免费的AI知识星球