├── src └── main │ ├── resources │ ├── META-INF │ │ └── spring.factories │ └── static │ │ └── async │ │ └── index.html │ └── java │ └── com │ └── xy │ └── async │ ├── strategy │ ├── context │ │ └── StrategyContext.java │ ├── DefaultStrategy.java │ ├── StrategyService.java │ └── StrategyFactory.java │ ├── config │ ├── AsyncDataSourceConfig.java │ ├── AsyncListener.java │ ├── SpringBeanConfig.java │ ├── AsyncProxy.java │ ├── AsyncConverter.java │ ├── AsyncInitBean.java │ ├── AsyncAspect.java │ ├── AsyncConfig.java │ └── LoginInterceptor.java │ ├── handler │ ├── HandlerService.java │ ├── impl │ │ ├── AsyncHandlerService.java │ │ ├── ThreadHandlerService.java │ │ ├── AsyncSaveHandlerService.java │ │ ├── SaveAsyncHandlerService.java │ │ ├── SyncSaveHandlerService.java │ │ └── AbstractHandlerService.java │ └── context │ │ └── AsyncContext.java │ ├── biz │ ├── AsyncBizService.java │ └── impl │ │ └── AsyncBizServiceImpl.java │ ├── dto │ ├── ProxyMethodDto.java │ ├── PageInfoDto.java │ └── AsyncExecDto.java │ ├── domain │ ├── dao │ │ ├── AsyncLogDao.java │ │ ├── impl │ │ │ ├── AsyncLogDaoImpl.java │ │ │ └── AsyncReqDaoImpl.java │ │ └── AsyncReqDao.java │ ├── service │ │ ├── AsyncLogService.java │ │ ├── impl │ │ │ ├── AsyncLogServiceImpl.java │ │ │ └── AsyncReqServiceImpl.java │ │ └── AsyncReqService.java │ └── entity │ │ ├── AsyncLog.java │ │ └── AsyncReq.java │ ├── constant │ └── AsyncConstant.java │ ├── enums │ ├── ExecStatusEnum.java │ └── AsyncTypeEnum.java │ ├── annotation │ └── AsyncExec.java │ ├── util │ ├── Md5Util.java │ └── JacksonUtil.java │ ├── mq │ ├── AsyncProducer.java │ └── AsyncConsumer.java │ ├── job │ ├── CompJob.java │ └── AsyncJob.java │ └── controller │ └── AsyncController.java ├── .gitignore ├── pom.xml └── README.md /src/main/resources/META-INF/spring.factories: -------------------------------------------------------------------------------- 1 | org.springframework.boot.autoconfigure.EnableAutoConfiguration=com.xy.async.config.AsyncConfig -------------------------------------------------------------------------------- /src/main/java/com/xy/async/strategy/context/StrategyContext.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.strategy.context; 2 | 3 | import java.io.Serializable; 4 | 5 | /** 6 | * 策略上下文 7 | * 8 | * @author xiongyan 9 | * @date 2019/10/23 10 | */ 11 | public class StrategyContext implements Serializable { 12 | 13 | /** 14 | * @Fields: serialVersionUID 15 | */ 16 | private static final long serialVersionUID = 1L; 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncDataSourceConfig.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.util.LinkedHashMap; 4 | 5 | import org.springframework.boot.context.properties.ConfigurationProperties; 6 | 7 | /** 8 | * AsyncDataSourceConfig 9 | * 10 | * @author xiongyan 11 | * @date 2022/5/9 12 | */ 13 | @ConfigurationProperties(prefix = "async.datasource") 14 | public class AsyncDataSourceConfig extends LinkedHashMap { 15 | 16 | } 17 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/strategy/DefaultStrategy.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.strategy; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | /** 9 | * 默认策略注解 10 | * 11 | * @author xiongyan 12 | * @date 2020/4/22 13 | */ 14 | @Retention(RetentionPolicy.RUNTIME) 15 | @Target(ElementType.TYPE) 16 | public @interface DefaultStrategy { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/HandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler; 2 | 3 | import com.xy.async.handler.context.AsyncContext; 4 | import com.xy.async.strategy.StrategyService; 5 | 6 | /** 7 | * 异步执行接口 8 | * 9 | * @author xiongyan 10 | * @date 2021/11/17 11 | */ 12 | public interface HandlerService extends StrategyService { 13 | 14 | /** 15 | * 执行异步策略 16 | * 17 | * @param context 18 | * @return 19 | */ 20 | boolean execute(AsyncContext context); 21 | } 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.factorypath 2 | */target/ 3 | *.iml 4 | .idea/ 5 | .classpath 6 | .project 7 | .settings 8 | ##filter databfile、sln file## 9 | *.mdb 10 | *.ldb 11 | *.sln 12 | ##class file## 13 | *.com 14 | *.class 15 | *.dll 16 | *.exe 17 | *.o 18 | *.so 19 | # compression file 20 | *.7z 21 | *.dmg 22 | *.gz 23 | *.iso 24 | *.jar 25 | *.rar 26 | *.tar 27 | *.zip 28 | *.via 29 | *.tmp 30 | *.err 31 | # OS generated files # 32 | .DS_Store 33 | .DS_Store? 34 | ._* 35 | .Spotlight-V100 36 | .Trashes 37 | Icon? 38 | ehthumbs.db 39 | Thumbs.db 40 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/strategy/StrategyService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.strategy; 2 | 3 | import java.util.List; 4 | 5 | import com.xy.async.strategy.context.StrategyContext; 6 | 7 | /** 8 | * 策略基础接口类 每个策略业务接口类必须继承此类 9 | * 10 | * @author xiongyan 11 | * @date 2019/10/23 12 | */ 13 | public interface StrategyService { 14 | 15 | /** 16 | * 策略类型列表 17 | * 18 | * @return 19 | */ 20 | List listType(); 21 | 22 | /** 23 | * 处理策略 24 | * 25 | * @param t 26 | * @return 27 | */ 28 | boolean handle(T t); 29 | } 30 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/biz/AsyncBizService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.biz; 2 | 3 | import com.xy.async.domain.entity.AsyncReq; 4 | import com.xy.async.dto.AsyncExecDto; 5 | 6 | /** 7 | * 异步执行接口 8 | * 9 | * @author xiongyan 10 | * @date 2021/01/08 11 | */ 12 | public interface AsyncBizService { 13 | 14 | /** 15 | * 执行方法 16 | * 17 | * @param asyncReq 18 | * @return 19 | */ 20 | boolean invoke(AsyncReq asyncReq); 21 | 22 | /** 23 | * 执行方法 24 | * 25 | * @param asyncExecDto 26 | * @return 27 | */ 28 | boolean invoke(AsyncExecDto asyncExecDto); 29 | 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/dto/ProxyMethodDto.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.dto; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.Method; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | /** 11 | * 代理方法 12 | * 13 | * @author xiongyan 14 | * @date 2021/01/02 15 | */ 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class ProxyMethodDto implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 类实例 25 | */ 26 | private Object bean; 27 | 28 | /** 29 | * 方法 30 | */ 31 | private Method method; 32 | 33 | } -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/dao/AsyncLogDao.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.dao; 2 | 3 | import com.xy.async.domain.entity.AsyncLog; 4 | 5 | /** 6 | * 异步执行日志DAO 7 | * 8 | * @author xiongyan 9 | * @date 2021/01/08 10 | */ 11 | public interface AsyncLogDao { 12 | 13 | /** 14 | * 保存 15 | * 16 | * @param asyncLog 17 | */ 18 | void save(AsyncLog asyncLog); 19 | 20 | /** 21 | * 删除 22 | * 23 | * @param asyncId 24 | */ 25 | void delete(Long asyncId); 26 | 27 | /** 28 | * 获取最后一次失败信息 29 | * 30 | * @param asyncId 31 | * @return 32 | */ 33 | String getErrorData(Long asyncId); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/service/AsyncLogService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.service; 2 | 3 | import com.xy.async.domain.entity.AsyncLog; 4 | 5 | /** 6 | * 异步执行日志接口 7 | * 8 | * @author xiongyan 9 | * @date 2021/01/08 10 | */ 11 | public interface AsyncLogService { 12 | 13 | /** 14 | * 保存 15 | * 16 | * @param asyncLog 17 | */ 18 | void save(AsyncLog asyncLog); 19 | 20 | /** 21 | * 删除 22 | * 23 | * @param asyncId 24 | */ 25 | void delete(Long asyncId); 26 | 27 | /** 28 | * 获取最后一次失败信息 29 | * 30 | * @param asyncId 31 | * @return 32 | */ 33 | String getErrorData(Long asyncId); 34 | } 35 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/constant/AsyncConstant.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.constant; 2 | 3 | /** 4 | * 常量 5 | * 6 | * @author xiongyan 7 | * @date 2020/5/21 8 | */ 9 | public class AsyncConstant { 10 | 11 | /** 12 | * 执行代理方法防止死循环 13 | */ 14 | public static final ThreadLocal PUBLISH_EVENT = ThreadLocal.withInitial(() -> false); 15 | 16 | /** 17 | * 成功 18 | */ 19 | public static final int SUCCESS = 1; 20 | 21 | /** 22 | * 失败 23 | */ 24 | public static final int ERROR = 0; 25 | 26 | /** 27 | * 登录 28 | */ 29 | public static final int LOGIN = -1; 30 | 31 | /** 32 | * 队列后缀 33 | */ 34 | public static final String QUEUE_SUFFIX = "_async_queue"; 35 | 36 | } 37 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/entity/AsyncLog.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import lombok.Data; 7 | 8 | /** 9 | * 异步执行日志实体类 10 | * 11 | * @author xiongyan 12 | * @date 2021/01/08 13 | */ 14 | @Data 15 | public class AsyncLog implements Serializable { 16 | 17 | private static final long serialVersionUID = 7606071967983038048L; 18 | 19 | /** 20 | * 主键ID 21 | */ 22 | private Long id; 23 | 24 | /** 25 | * 异步请求ID 26 | */ 27 | private Long asyncId; 28 | 29 | /** 30 | * 执行错误信息 31 | */ 32 | private String errorData; 33 | 34 | /** 35 | * 创建时间 36 | */ 37 | private Date createTime; 38 | 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/enums/ExecStatusEnum.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 执行状态枚举 7 | * 8 | * @author xiongyan 9 | * @date 2021/01/02 10 | */ 11 | @Getter 12 | public enum ExecStatusEnum { 13 | 14 | /** 15 | * 初始化 16 | */ 17 | INIT(0, "初始化"), 18 | 19 | /** 20 | * 执行失败 21 | */ 22 | ERROR(1, "执行失败"), 23 | 24 | /** 25 | * 执行成功 26 | */ 27 | SUCCESS(2, "执行成功"); 28 | 29 | /** 30 | * 类型 31 | */ 32 | private final int status; 33 | 34 | /** 35 | * 名称 36 | */ 37 | private final String name; 38 | 39 | ExecStatusEnum(int status, String name) { 40 | this.status = status; 41 | this.name = name; 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/dto/PageInfoDto.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.dto; 2 | 3 | import java.io.Serializable; 4 | import java.util.List; 5 | 6 | import lombok.Getter; 7 | import lombok.Setter; 8 | import lombok.ToString; 9 | 10 | /** 11 | * PageInfoDto 12 | * 13 | * @author xiongyan 14 | * @date 2021/11/28 15 | */ 16 | @Getter 17 | @Setter 18 | @ToString 19 | public class PageInfoDto implements Serializable { 20 | 21 | private static final long serialVersionUID = 1L; 22 | 23 | /** 24 | * 页码 25 | */ 26 | private int pageNum; 27 | 28 | /** 29 | * 每页展示数量 30 | */ 31 | private int pageSize; 32 | 33 | /** 34 | * 总记录数 35 | */ 36 | private long total; 37 | 38 | /** 39 | * 数据 40 | */ 41 | private List list; 42 | 43 | } 44 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/AsyncHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.xy.async.enums.AsyncTypeEnum; 9 | import com.xy.async.handler.context.AsyncContext; 10 | 11 | /** 12 | * 仅异步消息处理 13 | * 14 | * @author xiongyan 15 | * @date 2021/11/17 16 | */ 17 | @Component 18 | public class AsyncHandlerService extends AbstractHandlerService { 19 | 20 | @Override 21 | public List listType() { 22 | return Collections.singletonList(AsyncTypeEnum.ASYNC.name()); 23 | } 24 | 25 | @Override 26 | public boolean execute(AsyncContext context) { 27 | // 放入消息队列 28 | return asyncProducer.send(context.getAsyncExecDto()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/context/AsyncContext.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.context; 2 | 3 | import com.xy.async.dto.AsyncExecDto; 4 | import org.aspectj.lang.ProceedingJoinPoint; 5 | 6 | import com.xy.async.annotation.AsyncExec; 7 | import com.xy.async.strategy.context.StrategyContext; 8 | 9 | import lombok.Data; 10 | 11 | /** 12 | * AsyncContext 13 | * 14 | * @author xiongyan 15 | * @date 2021/11/17 16 | */ 17 | @Data 18 | public class AsyncContext extends StrategyContext { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 切面方法 24 | */ 25 | private ProceedingJoinPoint joinPoint; 26 | 27 | /** 28 | * 异步执行策略 29 | */ 30 | private AsyncExec asyncExec; 31 | 32 | /** 33 | * 异步执行数据 34 | */ 35 | private AsyncExecDto asyncExecDto; 36 | 37 | } 38 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/annotation/AsyncExec.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.annotation; 2 | 3 | import java.lang.annotation.ElementType; 4 | import java.lang.annotation.Retention; 5 | import java.lang.annotation.RetentionPolicy; 6 | import java.lang.annotation.Target; 7 | 8 | import com.xy.async.enums.AsyncTypeEnum; 9 | 10 | /** 11 | * 异步执行注解 12 | * 13 | * @author xiongyan 14 | * @date 2021/01/02 15 | */ 16 | @Retention(RetentionPolicy.RUNTIME) 17 | @Target(ElementType.METHOD) 18 | public @interface AsyncExec { 19 | 20 | /** 21 | * 异步执行策略 22 | * 23 | * @return 24 | */ 25 | AsyncTypeEnum type(); 26 | 27 | /** 28 | * 延迟处理时间 29 | * 30 | * @return 31 | */ 32 | int delayTime() default 0; 33 | 34 | /** 35 | * 业务描述 36 | * 37 | * @return 38 | */ 39 | String remark(); 40 | } 41 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/dto/AsyncExecDto.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.dto; 2 | 3 | import java.io.Serializable; 4 | 5 | import lombok.Getter; 6 | import lombok.Setter; 7 | import lombok.ToString; 8 | 9 | /** 10 | * 异步执行DTO 11 | * 12 | * @author xiongyan 13 | * @date 2021/01/02 14 | */ 15 | @Getter 16 | @Setter 17 | @ToString 18 | public class AsyncExecDto implements Serializable { 19 | 20 | private static final long serialVersionUID = 1L; 21 | 22 | /** 23 | * 主键ID 24 | */ 25 | private Long id; 26 | 27 | /** 28 | * 应用名称 29 | */ 30 | private String applicationName; 31 | 32 | /** 33 | * 方法签名 34 | */ 35 | private String sign; 36 | 37 | /** 38 | * 全路径类名称 39 | */ 40 | private String className; 41 | 42 | /** 43 | * method名称 44 | */ 45 | private String methodName; 46 | 47 | /** 48 | * 异步策略类型 49 | */ 50 | private String asyncType; 51 | 52 | /** 53 | * 参数json字符串 54 | */ 55 | private String paramJson; 56 | 57 | /** 58 | * 业务描述 59 | */ 60 | private String remark; 61 | } -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/service/impl/AsyncLogServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.service.impl; 2 | 3 | import java.util.Date; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Service; 7 | 8 | import com.xy.async.domain.dao.AsyncLogDao; 9 | import com.xy.async.domain.entity.AsyncLog; 10 | import com.xy.async.domain.service.AsyncLogService; 11 | 12 | /** 13 | * 异步执行日志接口实现 14 | * 15 | * @author xiongyan 16 | * @date 2021/01/08 17 | */ 18 | @Service 19 | public class AsyncLogServiceImpl implements AsyncLogService { 20 | 21 | @Autowired(required = false) 22 | private AsyncLogDao asyncLogDao; 23 | 24 | @Override 25 | public void save(AsyncLog asyncLog) { 26 | asyncLog.setCreateTime(new Date()); 27 | asyncLogDao.save(asyncLog); 28 | } 29 | 30 | @Override 31 | public void delete(Long asyncId) { 32 | asyncLogDao.delete(asyncId); 33 | } 34 | 35 | @Override 36 | public String getErrorData(Long asyncId) { 37 | return asyncLogDao.getErrorData(asyncId); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/enums/AsyncTypeEnum.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.enums; 2 | 3 | import lombok.Getter; 4 | 5 | /** 6 | * 异步执行枚举 7 | * 8 | * @author xiongyan 9 | * @date 2021/01/02 10 | */ 11 | @Getter 12 | public enum AsyncTypeEnum { 13 | 14 | /** 15 | * 先保存数据库再异步消息处理 16 | */ 17 | SAVE_ASYNC("先保存数据库再异步消息处理"), 18 | 19 | /** 20 | * 先同步处理失败再保存数据库 21 | */ 22 | SYNC_SAVE("先同步处理失败再保存数据库"), 23 | 24 | /** 25 | * 先异步消息处理失败再保存数据库 26 | */ 27 | ASYNC_SAVE("先异步消息处理失败再保存数据库"), 28 | 29 | /** 30 | * 仅异步消息处理 31 | */ 32 | ASYNC("仅异步消息处理"), 33 | 34 | /** 35 | * 仅异步线程处理 36 | */ 37 | THREAD("仅异步线程处理"); 38 | 39 | /** 40 | * 描述 41 | */ 42 | private final String desc; 43 | 44 | AsyncTypeEnum(String desc) { 45 | this.desc = desc; 46 | } 47 | 48 | public static String getDesc(String type) { 49 | for (AsyncTypeEnum typeEnum : AsyncTypeEnum.values()) { 50 | if (typeEnum.name().equals(type)) { 51 | return typeEnum.getDesc(); 52 | } 53 | } 54 | return null; 55 | } 56 | 57 | } 58 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/ThreadHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | import java.util.concurrent.Executor; 6 | 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.stereotype.Component; 9 | 10 | import com.xy.async.biz.AsyncBizService; 11 | import com.xy.async.enums.AsyncTypeEnum; 12 | import com.xy.async.handler.context.AsyncContext; 13 | 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * 异步线程处理 18 | * 19 | * @author xiongyan 20 | * @date 2021/11/17 21 | */ 22 | @Slf4j 23 | @Component 24 | public class ThreadHandlerService extends AbstractHandlerService { 25 | 26 | @Autowired(required = false) 27 | private Executor asyncExecute; 28 | 29 | @Autowired 30 | private AsyncBizService asyncBizService; 31 | 32 | @Override 33 | public List listType() { 34 | return Collections.singletonList(AsyncTypeEnum.THREAD.name()); 35 | } 36 | 37 | @Override 38 | public boolean execute(AsyncContext context) { 39 | asyncExecute.execute(() -> asyncBizService.invoke(context.getAsyncExecDto())); 40 | return true; 41 | } 42 | } 43 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/AsyncSaveHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.xy.async.domain.entity.AsyncReq; 9 | import com.xy.async.enums.AsyncTypeEnum; 10 | import com.xy.async.enums.ExecStatusEnum; 11 | import com.xy.async.handler.context.AsyncContext; 12 | 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * 先异步消息处理失败再保存数据库 17 | * 18 | * @author xiongyan 19 | * @date 2021/11/17 20 | */ 21 | @Slf4j 22 | @Component 23 | public class AsyncSaveHandlerService extends AbstractHandlerService { 24 | 25 | @Override 26 | public List listType() { 27 | return Collections.singletonList(AsyncTypeEnum.ASYNC_SAVE.name()); 28 | } 29 | 30 | @Override 31 | public boolean execute(AsyncContext context) { 32 | // 放入消息队列 33 | boolean success = asyncProducer.send(context.getAsyncExecDto()); 34 | if (success) { 35 | return true; 36 | } 37 | // 保存数据库 38 | AsyncReq asyncReq = this.saveAsyncReq(context.getAsyncExecDto(), ExecStatusEnum.ERROR.getStatus()); 39 | return null != asyncReq; 40 | } 41 | } 42 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/service/AsyncReqService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.service; 2 | 3 | import java.util.List; 4 | 5 | import com.xy.async.domain.entity.AsyncReq; 6 | import com.xy.async.dto.PageInfoDto; 7 | 8 | /** 9 | * 异步执行接口 10 | * 11 | * @author xiongyan 12 | * @date 2021/01/08 13 | */ 14 | public interface AsyncReqService { 15 | 16 | /** 17 | * 保存 18 | * 19 | * @param asyncReq 20 | */ 21 | void save(AsyncReq asyncReq); 22 | 23 | /** 24 | * 更新状态 25 | * 26 | * @param id 27 | * @param execStatus 28 | */ 29 | void updateStatus(Long id, Integer execStatus); 30 | 31 | /** 32 | * 删除 33 | * 34 | * @param id 35 | */ 36 | void delete(Long id); 37 | 38 | /** 39 | * 根据ID查询 40 | * 41 | * @param id 42 | * @return 43 | */ 44 | AsyncReq getById(Long id); 45 | 46 | /** 47 | * 自动重试 48 | * 49 | * @return 50 | */ 51 | List listRetry(); 52 | 53 | /** 54 | * 自动补偿 55 | * 56 | * @return 57 | */ 58 | List listComp(); 59 | 60 | /** 61 | * 人工执行 62 | * 63 | * @param pageInfo 64 | */ 65 | void listAsyncPage(PageInfoDto pageInfo); 66 | } 67 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncListener.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import org.springframework.stereotype.Component; 4 | import org.springframework.transaction.event.TransactionPhase; 5 | import org.springframework.transaction.event.TransactionalEventListener; 6 | import org.springframework.util.Assert; 7 | 8 | import com.xy.async.handler.HandlerService; 9 | import com.xy.async.handler.context.AsyncContext; 10 | import com.xy.async.strategy.StrategyFactory; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * 事件监听 16 | * 17 | * @author xiongyan 18 | * @date 2022/3/2 19 | */ 20 | @Slf4j 21 | @Component 22 | public class AsyncListener { 23 | 24 | /** 25 | * 处理事件
26 | * fallbackExecution=true 没有事务正在运行,依然处理事件
27 | * TransactionPhase.AFTER_COMPLETION 事务提交,事务回滚都处理事件 28 | * 29 | * @param context 30 | */ 31 | @TransactionalEventListener(fallbackExecution = true, phase = TransactionPhase.AFTER_COMPLETION) 32 | public void asyncHandler(AsyncContext context) { 33 | HandlerService handlerService = StrategyFactory.doStrategy(context.getAsyncExec().type().name(), HandlerService.class); 34 | Assert.notNull(handlerService, "异步执行策略不存在"); 35 | handlerService.handle(context); 36 | } 37 | 38 | } 39 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/dao/impl/AsyncLogDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.dao.impl; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.jdbc.core.JdbcTemplate; 5 | import org.springframework.stereotype.Repository; 6 | 7 | import com.xy.async.domain.dao.AsyncLogDao; 8 | import com.xy.async.domain.entity.AsyncLog; 9 | 10 | /** 11 | * 异步执行日志DAO 12 | * 13 | * @author xiongyan 14 | * @date 2021/01/08 15 | */ 16 | @Repository 17 | public class AsyncLogDaoImpl implements AsyncLogDao { 18 | 19 | @Autowired(required = false) 20 | private JdbcTemplate asyncJdbcTemplate; 21 | 22 | @Override 23 | public void save(AsyncLog asyncLog) { 24 | String sql = "insert into async_log(async_id, error_data, create_time) values (?, ?, ?)"; 25 | asyncJdbcTemplate.update(sql, asyncLog.getAsyncId(), asyncLog.getErrorData(), asyncLog.getCreateTime()); 26 | } 27 | 28 | @Override 29 | public void delete(Long asyncId) { 30 | String sql = "delete from async_log where async_id = ?"; 31 | asyncJdbcTemplate.update(sql, asyncId); 32 | } 33 | 34 | @Override 35 | public String getErrorData(Long asyncId) { 36 | String sql = "select error_data from async_log where async_id = ? order by id desc limit 1"; 37 | return asyncJdbcTemplate.queryForObject(sql, String.class, asyncId); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/entity/AsyncReq.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.entity; 2 | 3 | import java.io.Serializable; 4 | import java.util.Date; 5 | 6 | import lombok.Data; 7 | 8 | /** 9 | * 异步执行实体类 10 | * 11 | * @author xiongyan 12 | * @date 2021/01/08 13 | */ 14 | @Data 15 | public class AsyncReq implements Serializable { 16 | 17 | private static final long serialVersionUID = 7606071967983038048L; 18 | 19 | /** 20 | * 主键ID 21 | */ 22 | private Long id; 23 | 24 | /** 25 | * 应用名称 26 | */ 27 | private String applicationName; 28 | 29 | /** 30 | * 方法签名 31 | */ 32 | private String sign; 33 | 34 | /** 35 | * 全路径类名称 36 | */ 37 | private String className; 38 | 39 | /** 40 | * method名称 41 | */ 42 | private String methodName; 43 | 44 | /** 45 | * 异步策略类型 46 | */ 47 | private String asyncType; 48 | 49 | /** 50 | * 执行状态 0:未处理 1:处理失败 51 | */ 52 | private Integer execStatus; 53 | 54 | /** 55 | * 执行次数 56 | */ 57 | private Integer execCount; 58 | 59 | /** 60 | * 参数json字符串 61 | */ 62 | private String paramJson; 63 | 64 | /** 65 | * 业务描述 66 | */ 67 | private String remark; 68 | 69 | /** 70 | * 创建时间 71 | */ 72 | private Date createTime; 73 | 74 | /** 75 | * 修改时间 76 | */ 77 | private Date updateTime; 78 | 79 | } 80 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/util/Md5Util.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.util; 2 | 3 | import java.security.MessageDigest; 4 | import java.security.NoSuchAlgorithmException; 5 | 6 | /** 7 | * MD5 8 | * 9 | * @author xiongyan 10 | * @date 2019/09/19 11 | */ 12 | public class Md5Util { 13 | 14 | private Md5Util() { 15 | } 16 | 17 | /** 18 | * Used building output as Hex 19 | */ 20 | private static final char[] DIGITS = { '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f' }; 21 | 22 | /** 23 | * 对字符串进行MD5加密 24 | * 25 | * @param text 明文 26 | * @return 密文 27 | */ 28 | public static String md5(String text) { 29 | MessageDigest mdInst; 30 | try { 31 | // 获得MD5摘要算法的 MessageDigest 对象 32 | mdInst = MessageDigest.getInstance("MD5"); 33 | } catch (NoSuchAlgorithmException e) { 34 | throw new IllegalStateException("System doesn't support MD5 algorithm."); 35 | } 36 | 37 | // 使用指定的字节更新摘要 38 | mdInst.update(text.getBytes()); 39 | // 获得密文 40 | byte[] mds = mdInst.digest(); 41 | // 把密文转换成十六进制的字符串形式 42 | int j = mds.length; 43 | char[] str = new char[j * 2]; 44 | int k = 0; 45 | for (byte md : mds) { 46 | str[k++] = DIGITS[md >>> 4 & 0xf]; 47 | str[k++] = DIGITS[md & 0xf]; 48 | } 49 | return new String(str); 50 | } 51 | 52 | } 53 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/SpringBeanConfig.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.util.Map; 4 | 5 | import org.springframework.beans.BeansException; 6 | import org.springframework.context.ApplicationContext; 7 | import org.springframework.context.ApplicationContextAware; 8 | import org.springframework.core.Ordered; 9 | import org.springframework.core.annotation.Order; 10 | import org.springframework.stereotype.Component; 11 | 12 | /** 13 | * SpringBeanConfig 14 | * 15 | * @author xiongyan 16 | * @date 2021/4/2 17 | */ 18 | @Order(value = Ordered.HIGHEST_PRECEDENCE) 19 | @Component 20 | public class SpringBeanConfig implements ApplicationContextAware { 21 | 22 | public static ApplicationContext applicationContext; 23 | 24 | @Override 25 | public void setApplicationContext(ApplicationContext applicationContext) { 26 | SpringBeanConfig.applicationContext = applicationContext; 27 | } 28 | 29 | public static T getBean(String name, Class clazz) throws BeansException { 30 | return applicationContext.getBean(name, clazz); 31 | } 32 | 33 | public static T getBean(Class clazz) throws BeansException { 34 | return applicationContext.getBean(clazz); 35 | } 36 | 37 | public static Object getBean(String name) { 38 | return applicationContext.getBean(name); 39 | } 40 | 41 | public static Map getBeansOfType(Class clazz) throws BeansException { 42 | return applicationContext.getBeansOfType(clazz); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/SaveAsyncHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.xy.async.domain.entity.AsyncReq; 9 | import com.xy.async.enums.AsyncTypeEnum; 10 | import com.xy.async.enums.ExecStatusEnum; 11 | import com.xy.async.handler.context.AsyncContext; 12 | 13 | import lombok.extern.slf4j.Slf4j; 14 | 15 | /** 16 | * 先保存数据库再异步消息处理 17 | * 18 | * @author xiongyan 19 | * @date 2021/11/17 20 | */ 21 | @Slf4j 22 | @Component 23 | public class SaveAsyncHandlerService extends AbstractHandlerService { 24 | 25 | @Override 26 | public List listType() { 27 | return Collections.singletonList(AsyncTypeEnum.SAVE_ASYNC.name()); 28 | } 29 | 30 | @Override 31 | public boolean execute(AsyncContext context) { 32 | // 保存数据库 33 | AsyncReq asyncReq = this.saveAsyncReq(context.getAsyncExecDto(), ExecStatusEnum.INIT.getStatus()); 34 | if (null == asyncReq) { 35 | // 降级为仅异步消息处理 36 | return asyncProducer.send(context.getAsyncExecDto()); 37 | } 38 | // 放入消息队列(需要数据库ID) 39 | boolean success = asyncProducer.send(asyncConverter.toAsyncExecDto.apply(asyncReq)); 40 | if (success) { 41 | return true; 42 | } 43 | // 更新状态为失败 44 | asyncReqService.updateStatus(asyncReq.getId(), ExecStatusEnum.ERROR.getStatus()); 45 | return true; 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/dao/AsyncReqDao.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.dao; 2 | 3 | import java.util.List; 4 | 5 | import com.xy.async.domain.entity.AsyncReq; 6 | 7 | /** 8 | * 异步执行 dao 9 | * 10 | * @author xiongyan 11 | * @date 2021/01/08 12 | */ 13 | public interface AsyncReqDao { 14 | 15 | /** 16 | * 保存 17 | * 18 | * @param asyncReq 19 | */ 20 | void save(AsyncReq asyncReq); 21 | 22 | /** 23 | * 更新 24 | * 25 | * @param asyncReq 26 | */ 27 | void update(AsyncReq asyncReq); 28 | 29 | /** 30 | * 删除 31 | * 32 | * @param id 33 | */ 34 | void delete(Long id); 35 | 36 | /** 37 | * 根据ID查询 38 | * 39 | * @param id 40 | * @return 41 | */ 42 | AsyncReq getById(Long id); 43 | 44 | /** 45 | * 自动重试 46 | * 47 | * @param applicationName 48 | * @return 49 | */ 50 | List listRetry(String applicationName); 51 | 52 | /** 53 | * 自动补偿 54 | * 55 | * @param applicationName 56 | * @return 57 | */ 58 | List listComp(String applicationName); 59 | 60 | /** 61 | * 人工执行总数量 62 | * 63 | * @param applicationName 64 | * @return 65 | */ 66 | Integer countAsync(String applicationName); 67 | 68 | /** 69 | * 人工执行 70 | * 71 | * @param applicationName 72 | * @param pageIndex 73 | * @param pageSize 74 | * @return 75 | */ 76 | List listAsync(String applicationName, int pageIndex, int pageSize); 77 | } 78 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/mq/AsyncProducer.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.mq; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.kafka.core.KafkaTemplate; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.xy.async.constant.AsyncConstant; 9 | import com.xy.async.dto.AsyncExecDto; 10 | 11 | import lombok.extern.slf4j.Slf4j; 12 | 13 | /** 14 | * 异步执行提供者 15 | * 16 | * @author xiongyan 17 | * @date 2021/01/08 18 | */ 19 | @Slf4j 20 | @Component 21 | public class AsyncProducer { 22 | 23 | @Autowired 24 | private KafkaTemplate kafkaTemplate; 25 | 26 | /** 27 | * 队列名称前缀:默认是应用名称 28 | */ 29 | @Value("${async.topic:${spring.application.name}}") 30 | private String asyncTopic; 31 | 32 | /** 33 | * 发送消息 34 | * 35 | * @param asyncExecDto 36 | * @return 37 | */ 38 | public boolean send(AsyncExecDto asyncExecDto) { 39 | String queueName = asyncTopic + AsyncConstant.QUEUE_SUFFIX; 40 | try { 41 | log.info("kafka消息开始发送,queueName:'{}', message:{}", queueName, asyncExecDto); 42 | kafkaTemplate.send(queueName, asyncExecDto); 43 | log.info("kafka消息发送成功,queueName:'{}', message:{}", queueName, asyncExecDto); 44 | return true; 45 | } catch (Exception e) { 46 | log.error("kafka消息发送失败,queueName:'{}', message:{}", queueName, asyncExecDto, e); 47 | return false; 48 | } 49 | } 50 | 51 | } 52 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncProxy.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Map; 5 | import java.util.concurrent.ConcurrentHashMap; 6 | 7 | import org.springframework.stereotype.Component; 8 | 9 | import com.xy.async.dto.ProxyMethodDto; 10 | import com.xy.async.util.Md5Util; 11 | 12 | /** 13 | * 代理类 14 | * 15 | * @author xiongyan 16 | * @date 2021/1/14 17 | */ 18 | @Component 19 | public class AsyncProxy { 20 | 21 | /** 22 | * 代理类方法 23 | */ 24 | private static final Map PROXY_METHOD_MAP = new ConcurrentHashMap<>(); 25 | 26 | /** 27 | * 设置代理方法 28 | * 29 | * @param key 30 | * @param proxyMethodDto 31 | */ 32 | public void setProxyMethod(String key, ProxyMethodDto proxyMethodDto) { 33 | AsyncProxy.PROXY_METHOD_MAP.put(key, proxyMethodDto); 34 | } 35 | 36 | /** 37 | * 获取代理方法 38 | * 39 | * @param key 40 | * @return 41 | */ 42 | public ProxyMethodDto getProxyMethod(String key) { 43 | return AsyncProxy.PROXY_METHOD_MAP.get(key); 44 | } 45 | 46 | /** 47 | * 获取异步方法唯一标识 48 | * 49 | * @param bean 50 | * @param method 51 | * @return 52 | */ 53 | public String getAsyncMethodKey(Object bean, Method method) { 54 | if (method.toString().contains(bean.getClass().getName())) { 55 | // 异步执行注解在当前类方法上面 56 | return Md5Util.md5(method.toString()); 57 | } else { 58 | // 异步执行注解在基类方法上面 59 | return Md5Util.md5(bean.getClass().getSimpleName() + "#" + method); 60 | } 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/mq/AsyncConsumer.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.mq; 2 | 3 | import org.springframework.beans.factory.annotation.Autowired; 4 | import org.springframework.beans.factory.annotation.Value; 5 | import org.springframework.kafka.annotation.KafkaListener; 6 | import org.springframework.stereotype.Component; 7 | 8 | import com.xy.async.biz.AsyncBizService; 9 | import com.xy.async.constant.AsyncConstant; 10 | import com.xy.async.dto.AsyncExecDto; 11 | 12 | import lombok.extern.slf4j.Slf4j; 13 | 14 | /** 15 | * 异步执行消费者 16 | * 17 | * @author xiongyan 18 | * @date 2021/01/08 19 | */ 20 | @Slf4j 21 | @Component 22 | public class AsyncConsumer { 23 | 24 | @Autowired 25 | private AsyncBizService asyncBizService; 26 | 27 | /** 28 | * 队列名称前缀:默认是应用名称 29 | */ 30 | @Value("${async.topic:${spring.application.name}}") 31 | private String asyncTopic; 32 | 33 | /** 34 | * 消费消息 35 | * 36 | * @param asyncExecDto 37 | * @return 38 | */ 39 | @KafkaListener(topics = "${async.topic:${spring.application.name}}" + AsyncConstant.QUEUE_SUFFIX,groupId = "${spring.application.name}") 40 | public void onConsume(AsyncExecDto asyncExecDto) { 41 | String queueName = asyncTopic + AsyncConstant.QUEUE_SUFFIX; 42 | try { 43 | log.info("kafka消息开始消费,queueName:'{}',message:{}", queueName, asyncExecDto); 44 | // 执行方法 45 | asyncBizService.invoke(asyncExecDto); 46 | log.info("kafka消息消费成功,queueName:'{}',message:{}", queueName, asyncExecDto); 47 | } catch (Exception e) { 48 | log.error("kafka消息消费失败,queueName:'{}',message:{}", queueName, asyncExecDto, e); 49 | throw e; 50 | } 51 | } 52 | 53 | } 54 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/job/CompJob.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.job; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.CollectionUtils; 8 | 9 | import com.xxl.job.core.handler.annotation.XxlJob; 10 | import com.xy.async.biz.AsyncBizService; 11 | import com.xy.async.domain.entity.AsyncReq; 12 | import com.xy.async.domain.service.AsyncReqService; 13 | 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * 异步执行补偿定时任务 18 | * 19 | * @author xiongyan 20 | * @date 2021/01/02 21 | */ 22 | @Slf4j 23 | @Component 24 | public class CompJob { 25 | 26 | @Autowired 27 | private AsyncReqService asyncReqService; 28 | 29 | @Autowired 30 | private AsyncBizService asyncBizService; 31 | 32 | /** 33 | * name = "CompJob" 34 | * cron = "0 0 0/1 * * ?" 35 | * description = "异步执行补偿定时任务" 36 | */ 37 | @XxlJob("CompJob") 38 | public void execute() { 39 | // 任务开始时间 40 | long start = System.currentTimeMillis(); 41 | try { 42 | log.info("异步补偿定时任务执行开始......"); 43 | // 执行任务 44 | List asyncReqList = asyncReqService.listComp(); 45 | if (CollectionUtils.isEmpty(asyncReqList)) { 46 | return; 47 | } 48 | for (AsyncReq asyncReq : asyncReqList) { 49 | asyncBizService.invoke(asyncReq); 50 | } 51 | } catch (Throwable e) { 52 | log.error("异步补偿定时任务执行失败......", e); 53 | } finally { 54 | // 任务结束时间 55 | long end = System.currentTimeMillis(); 56 | log.info("异步补偿定时任务执行结束...... 用时:{}毫秒", end - start); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/job/AsyncJob.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.job; 2 | 3 | import java.util.List; 4 | 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.util.CollectionUtils; 8 | 9 | import com.xxl.job.core.handler.annotation.XxlJob; 10 | import com.xy.async.biz.AsyncBizService; 11 | import com.xy.async.domain.entity.AsyncReq; 12 | import com.xy.async.domain.service.AsyncReqService; 13 | 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * 异步执行失败重试定时任务 18 | * 19 | * @author xiongyan 20 | * @date 2021/01/02 21 | */ 22 | @Slf4j 23 | @Component 24 | public class AsyncJob { 25 | 26 | @Autowired 27 | private AsyncReqService asyncReqService; 28 | 29 | @Autowired 30 | private AsyncBizService asyncBizService; 31 | 32 | /** 33 | * name = "AsyncJob" 34 | * cron = "0 0/2 * * * ?" 35 | * description = "异步执行失败重试定时任务" 36 | */ 37 | @XxlJob("AsyncJob") 38 | public void execute() { 39 | // 任务开始时间 40 | long start = System.currentTimeMillis(); 41 | try { 42 | log.info("异步重试定时任务执行开始......"); 43 | // 执行任务 44 | List asyncReqList = asyncReqService.listRetry(); 45 | if (CollectionUtils.isEmpty(asyncReqList)) { 46 | return; 47 | } 48 | for (AsyncReq asyncReq : asyncReqList) { 49 | asyncBizService.invoke(asyncReq); 50 | } 51 | } catch (Throwable e) { 52 | log.error("异步重试定时任务执行失败......", e); 53 | } finally { 54 | // 任务结束时间 55 | long end = System.currentTimeMillis(); 56 | log.info("异步重试定时任务执行结束...... 用时:{}毫秒", end - start); 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncConverter.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.util.function.Function; 4 | 5 | import org.springframework.stereotype.Component; 6 | 7 | import com.xy.async.domain.entity.AsyncReq; 8 | import com.xy.async.dto.AsyncExecDto; 9 | 10 | /** 11 | * 数据转换 12 | * 13 | * @author xiongyan 14 | * @date 2021/01/02 15 | */ 16 | @Component 17 | public class AsyncConverter { 18 | 19 | /** 20 | * AsyncReq -> AsyncExecDto 21 | */ 22 | public Function toAsyncExecDto = (asyncReq) -> { 23 | if (null == asyncReq) { 24 | return null; 25 | } 26 | AsyncExecDto asyncExecDto = new AsyncExecDto(); 27 | asyncExecDto.setId(asyncReq.getId()); 28 | asyncExecDto.setApplicationName(asyncReq.getApplicationName()); 29 | asyncExecDto.setSign(asyncReq.getSign()); 30 | asyncExecDto.setClassName(asyncReq.getClassName()); 31 | asyncExecDto.setMethodName(asyncReq.getMethodName()); 32 | asyncExecDto.setAsyncType(asyncReq.getAsyncType()); 33 | asyncExecDto.setParamJson(asyncReq.getParamJson()); 34 | asyncExecDto.setRemark(asyncReq.getRemark()); 35 | return asyncExecDto; 36 | }; 37 | 38 | /** 39 | * AsyncExecDto -> AsyncReq 40 | */ 41 | public Function toAsyncReq = (asyncExecDto) -> { 42 | if (null == asyncExecDto) { 43 | return null; 44 | } 45 | AsyncReq asyncReq = new AsyncReq(); 46 | asyncReq.setApplicationName(asyncExecDto.getApplicationName()); 47 | asyncReq.setSign(asyncExecDto.getSign()); 48 | asyncReq.setClassName(asyncExecDto.getClassName()); 49 | asyncReq.setMethodName(asyncExecDto.getMethodName()); 50 | asyncReq.setAsyncType(asyncExecDto.getAsyncType()); 51 | asyncReq.setParamJson(asyncExecDto.getParamJson()); 52 | asyncReq.setRemark(asyncExecDto.getRemark()); 53 | return asyncReq; 54 | }; 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncInitBean.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.lang.reflect.Method; 4 | 5 | import com.xy.async.dto.ProxyMethodDto; 6 | import org.springframework.beans.BeansException; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.config.BeanPostProcessor; 9 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 10 | import org.springframework.core.annotation.AnnotationUtils; 11 | import org.springframework.core.annotation.Order; 12 | import org.springframework.stereotype.Component; 13 | import org.springframework.util.ReflectionUtils; 14 | 15 | import com.xy.async.annotation.AsyncExec; 16 | 17 | import cn.hutool.core.util.ArrayUtil; 18 | 19 | /** 20 | * 异步执行初始化 21 | * 22 | * @author xiongyan 23 | * @date 2021/6/19 24 | */ 25 | @Component 26 | @Order(value = -1) 27 | @ConditionalOnProperty(prefix = "async", value = "enabled", havingValue = "true") 28 | public class AsyncInitBean implements BeanPostProcessor { 29 | 30 | @Autowired 31 | private AsyncProxy asyncProxy; 32 | 33 | @Override 34 | public Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException { 35 | Method[] methods = ReflectionUtils.getAllDeclaredMethods(bean.getClass()); 36 | if (ArrayUtil.isEmpty(methods)) { 37 | return bean; 38 | } 39 | for (Method method : methods) { 40 | AsyncExec asyncExec = AnnotationUtils.findAnnotation(method, AsyncExec.class); 41 | if (null == asyncExec) { 42 | continue; 43 | } 44 | ProxyMethodDto proxyMethodDto = new ProxyMethodDto(); 45 | proxyMethodDto.setBean(SpringBeanConfig.getBean(beanName)); 46 | proxyMethodDto.setMethod(method); 47 | // 生成方法唯一标识 48 | String key = asyncProxy.getAsyncMethodKey(bean, method); 49 | asyncProxy.setProxyMethod(key, proxyMethodDto); 50 | } 51 | return bean; 52 | } 53 | 54 | 55 | 56 | } 57 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncAspect.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.annotation.Around; 5 | import org.aspectj.lang.annotation.Aspect; 6 | import org.aspectj.lang.reflect.MethodSignature; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 9 | import org.springframework.context.ApplicationEventPublisher; 10 | import org.springframework.stereotype.Component; 11 | 12 | import com.xy.async.annotation.AsyncExec; 13 | import com.xy.async.constant.AsyncConstant; 14 | import com.xy.async.handler.context.AsyncContext; 15 | 16 | import lombok.extern.slf4j.Slf4j; 17 | 18 | /** 19 | * 异步执行切面 20 | * 21 | * @author xiongyan 22 | * @date 2021/01/02 23 | */ 24 | @Slf4j 25 | @Aspect 26 | @Component 27 | @ConditionalOnProperty(prefix = "async", value = "enabled", havingValue = "true") 28 | public class AsyncAspect { 29 | 30 | @Autowired 31 | private ApplicationEventPublisher publisher; 32 | 33 | @Around("@annotation(asyncExec)") 34 | public Object proceed(ProceedingJoinPoint joinPoint, AsyncExec asyncExec) throws Throwable { 35 | if (AsyncConstant.PUBLISH_EVENT.get()) { 36 | try { 37 | // 直接执行 38 | return joinPoint.proceed(); 39 | } finally { 40 | AsyncConstant.PUBLISH_EVENT.remove(); 41 | } 42 | } else { 43 | AsyncContext context = new AsyncContext(); 44 | context.setJoinPoint(joinPoint); 45 | context.setAsyncExec(asyncExec); 46 | // 发布事件 47 | publisher.publishEvent(context); 48 | log.info("异步执行事件发布成功,策略类型:{},业务描述:{}", asyncExec.type(), asyncExec.remark()); 49 | 50 | MethodSignature signature = (MethodSignature) joinPoint.getSignature(); 51 | Class returnType = signature.getMethod().getReturnType(); 52 | if (returnType != Void.TYPE && returnType.isPrimitive()) { 53 | // 8种基本类型需特殊处理(byte、short、char、int、long、float、double、boolean) 54 | return returnType == Boolean.TYPE ? Boolean.TRUE : 1; 55 | } 56 | return null; 57 | } 58 | } 59 | 60 | } 61 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/service/impl/AsyncReqServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.service.impl; 2 | 3 | import java.util.Date; 4 | import java.util.List; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.beans.factory.annotation.Value; 8 | import org.springframework.stereotype.Service; 9 | 10 | import com.xy.async.domain.dao.AsyncReqDao; 11 | import com.xy.async.domain.entity.AsyncReq; 12 | import com.xy.async.domain.service.AsyncReqService; 13 | import com.xy.async.dto.PageInfoDto; 14 | 15 | /** 16 | * 异步执行接口实现 17 | * 18 | * @author xiongyan 19 | * @date 2021/01/08 20 | */ 21 | @Service 22 | public class AsyncReqServiceImpl implements AsyncReqService { 23 | 24 | @Autowired(required = false) 25 | private AsyncReqDao asyncReqDao; 26 | 27 | @Value("${spring.application.name}") 28 | private String applicationName; 29 | 30 | @Override 31 | public void save(AsyncReq asyncReq) { 32 | asyncReq.setCreateTime(new Date()); 33 | asyncReq.setUpdateTime(new Date()); 34 | asyncReqDao.save(asyncReq); 35 | } 36 | 37 | @Override 38 | public void updateStatus(Long id, Integer execStatus) { 39 | AsyncReq update = new AsyncReq(); 40 | update.setId(id); 41 | update.setExecStatus(execStatus); 42 | update.setUpdateTime(new Date()); 43 | asyncReqDao.update(update); 44 | } 45 | 46 | @Override 47 | public void delete(Long id) { 48 | asyncReqDao.delete(id); 49 | } 50 | 51 | @Override 52 | public AsyncReq getById(Long id) { 53 | return asyncReqDao.getById(id); 54 | } 55 | 56 | @Override 57 | public List listRetry() { 58 | return asyncReqDao.listRetry(applicationName); 59 | } 60 | 61 | @Override 62 | public List listComp() { 63 | return asyncReqDao.listComp(applicationName); 64 | } 65 | 66 | @Override 67 | public void listAsyncPage(PageInfoDto pageInfo) { 68 | Integer total = asyncReqDao.countAsync(applicationName); 69 | if (null == total || total == 0) { 70 | return; 71 | } 72 | List list = asyncReqDao.listAsync(applicationName, (pageInfo.getPageNum() - 1) * pageInfo.getPageSize(), pageInfo.getPageSize()); 73 | pageInfo.setTotal(total); 74 | pageInfo.setList(list); 75 | } 76 | 77 | } -------------------------------------------------------------------------------- /src/main/java/com/xy/async/strategy/StrategyFactory.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.strategy; 2 | 3 | import java.util.List; 4 | import java.util.Map; 5 | 6 | import org.springframework.aop.support.AopUtils; 7 | 8 | import com.xy.async.config.SpringBeanConfig; 9 | import com.xy.async.strategy.context.StrategyContext; 10 | 11 | import cn.hutool.core.collection.CollUtil; 12 | import cn.hutool.core.map.MapUtil; 13 | import cn.hutool.core.util.StrUtil; 14 | import lombok.extern.slf4j.Slf4j; 15 | 16 | /** 17 | * 策略工厂类 18 | * 19 | * @author xiongyan 20 | * @date 2019/10/23 21 | */ 22 | @Slf4j 23 | public class StrategyFactory { 24 | 25 | /** 26 | * 执行策略 27 | * 28 | * @param type 29 | * @param clazz 30 | * @return 31 | */ 32 | public static > T doStrategy(String type, Class clazz) { 33 | if (StrUtil.isEmpty(type) || null == clazz) { 34 | return null; 35 | } 36 | 37 | Map beanMap = SpringBeanConfig.getBeansOfType(clazz); 38 | if (MapUtil.isEmpty(beanMap)) { 39 | log.error("策略实现类不存在,type = {},clazz = {}", type, clazz.getName()); 40 | return null; 41 | } 42 | try { 43 | T defaultStrategy = null; 44 | for (Map.Entry entry : beanMap.entrySet()) { 45 | // 默认策略 46 | if (null == defaultStrategy) { 47 | Class targetClass = AopUtils.getTargetClass(entry.getValue()); 48 | DefaultStrategy annotation = targetClass.getAnnotation(DefaultStrategy.class); 49 | if (null != annotation) { 50 | defaultStrategy = entry.getValue(); 51 | } 52 | } 53 | // 策略类型列表 54 | List types = entry.getValue().listType(); 55 | if (CollUtil.isNotEmpty(types) && types.contains(type)) { 56 | return entry.getValue(); 57 | } 58 | } 59 | if (null != defaultStrategy) { 60 | return defaultStrategy; 61 | } 62 | log.error("策略类型不存在,type = {},clazz = {}", type, clazz.getName()); 63 | } catch (Exception e) { 64 | log.error("获取策略实现类失败,type = {},clazz = {}", type, clazz.getName(), e); 65 | } 66 | return null; 67 | } 68 | 69 | } 70 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/SyncSaveHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import java.util.Collections; 4 | import java.util.List; 5 | 6 | import org.springframework.stereotype.Component; 7 | import org.springframework.transaction.PlatformTransactionManager; 8 | import org.springframework.transaction.TransactionDefinition; 9 | import org.springframework.transaction.TransactionStatus; 10 | import org.springframework.transaction.support.DefaultTransactionDefinition; 11 | import org.springframework.transaction.support.TransactionSynchronizationManager; 12 | 13 | import com.xy.async.config.SpringBeanConfig; 14 | import com.xy.async.domain.entity.AsyncReq; 15 | import com.xy.async.enums.AsyncTypeEnum; 16 | import com.xy.async.enums.ExecStatusEnum; 17 | import com.xy.async.handler.context.AsyncContext; 18 | 19 | import lombok.extern.slf4j.Slf4j; 20 | 21 | /** 22 | * 先同步处理失败再保存数据库 23 | * 24 | * @author xiongyan 25 | * @date 2021/11/17 26 | */ 27 | @Slf4j 28 | @Component 29 | public class SyncSaveHandlerService extends AbstractHandlerService { 30 | 31 | @Override 32 | public List listType() { 33 | return Collections.singletonList(AsyncTypeEnum.SYNC_SAVE.name()); 34 | } 35 | 36 | @Override 37 | public boolean execute(AsyncContext context) { 38 | // 同步处理,由于不能影响主线程事务,但是异步方法上面又有事务所有需要开启新事物 39 | TransactionStatus status = null; 40 | PlatformTransactionManager transactionManager = null; 41 | if (TransactionSynchronizationManager.isActualTransactionActive()) { 42 | DefaultTransactionDefinition definition = new DefaultTransactionDefinition(); 43 | definition.setPropagationBehavior(TransactionDefinition.PROPAGATION_REQUIRES_NEW); 44 | transactionManager = SpringBeanConfig.getBean(PlatformTransactionManager.class); 45 | status = transactionManager.getTransaction(definition); 46 | } 47 | try { 48 | // 同步处理 49 | context.getJoinPoint().proceed(); 50 | if (null != status) { 51 | transactionManager.commit(status); 52 | } 53 | } catch (Throwable e) { 54 | log.warn("先同步处理失败:{}", context.getAsyncExecDto(), e); 55 | if (null != status) { 56 | transactionManager.rollback(status); 57 | } 58 | // 保存数据库 59 | AsyncReq asyncReq = this.saveAsyncReq(context.getAsyncExecDto(), ExecStatusEnum.ERROR.getStatus()); 60 | if (null == asyncReq) { 61 | // 降级为仅异步消息处理 62 | asyncProducer.send(context.getAsyncExecDto()); 63 | } 64 | } 65 | return true; 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/AsyncConfig.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.util.concurrent.Executor; 4 | import java.util.concurrent.ThreadPoolExecutor; 5 | 6 | import org.springframework.beans.factory.annotation.Value; 7 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 8 | import org.springframework.boot.context.properties.EnableConfigurationProperties; 9 | import org.springframework.context.annotation.Bean; 10 | import org.springframework.context.annotation.ComponentScan; 11 | import org.springframework.context.annotation.Configuration; 12 | import org.springframework.jdbc.core.JdbcTemplate; 13 | import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor; 14 | 15 | import com.alibaba.druid.pool.DruidDataSourceFactory; 16 | import com.alibaba.ttl.threadpool.TtlExecutors; 17 | 18 | /** 19 | * AsyncConfig 20 | * 21 | * @author xiongyan 22 | * @date 2021/11/15 23 | */ 24 | @Configuration 25 | @ComponentScan({ "com.xy.async" }) 26 | @ConditionalOnProperty(prefix = "async", value = "enabled", havingValue = "true") 27 | @EnableConfigurationProperties(AsyncDataSourceConfig.class) 28 | public class AsyncConfig { 29 | 30 | @Value("${async.executor.thread.corePoolSize:10}") 31 | private int corePoolSize; 32 | 33 | @Value("${async.executor.thread.maxPoolSize:50}") 34 | private int maxPoolSize; 35 | 36 | @Value("${async.executor.thread.queueCapacity:10000}") 37 | private int queueCapacity; 38 | 39 | @Value("${async.executor.thread.keepAliveSeconds:600}") 40 | private int keepAliveSeconds; 41 | 42 | @Bean("asyncJdbcTemplate") 43 | public JdbcTemplate jdbcTemplate(AsyncDataSourceConfig asyncDataSourceConfig) throws Exception { 44 | return new JdbcTemplate(DruidDataSourceFactory.createDataSource(asyncDataSourceConfig)); 45 | } 46 | 47 | @Bean(name = "asyncExecute") 48 | public Executor asyncExecute() { 49 | ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor(); 50 | // 核心线程数 51 | executor.setCorePoolSize(corePoolSize); 52 | // 最大线程数 53 | executor.setMaxPoolSize(maxPoolSize); 54 | // 队列容量 55 | executor.setQueueCapacity(queueCapacity); 56 | // 活跃时间 57 | executor.setKeepAliveSeconds(keepAliveSeconds); 58 | // 线程名字前缀 59 | executor.setThreadNamePrefix("asyncExecute-"); 60 | 61 | // setRejectedExecutionHandler:当pool已经达到max size的时候,如何处理新任务 62 | // CallerRunsPolicy:不在新线程中执行任务,而是由调用者所在的线程来执行 63 | executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy()); 64 | executor.initialize(); 65 | return TtlExecutors.getTtlExecutor(executor); 66 | } 67 | 68 | } 69 | -------------------------------------------------------------------------------- /pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 5 | 4.0.0 6 | 7 | com.xy.async 8 | fc-async 9 | 2.0.4-SNAPSHOT 10 | 11 | 12 | org.springframework.boot 13 | spring-boot-starter-parent 14 | 2.3.12.RELEASE 15 | 16 | 17 | 18 | 19 | org.springframework.boot 20 | spring-boot-autoconfigure 21 | 22 | 23 | org.springframework.boot 24 | spring-boot-starter-aop 25 | 26 | 27 | org.springframework.boot 28 | spring-boot-starter-web 29 | 30 | 31 | 32 | org.projectlombok 33 | lombok 34 | 1.18.20 35 | 36 | 37 | 38 | cn.hutool 39 | hutool-all 40 | 4.1.17 41 | 42 | 43 | 44 | org.apache.commons 45 | commons-lang3 46 | 3.12.0 47 | 48 | 49 | 50 | com.alibaba 51 | druid 52 | 1.2.11 53 | 54 | 55 | 56 | com.alibaba 57 | transmittable-thread-local 58 | 2.12.1 59 | 60 | 61 | 62 | mysql 63 | mysql-connector-java 64 | 8.0.25 65 | 66 | 67 | 68 | org.springframework 69 | spring-jdbc 70 | 5.2.15.RELEASE 71 | 72 | 73 | 74 | com.xuxueli 75 | xxl-job-core 76 | 2.3.0 77 | 78 | 79 | 80 | org.springframework.kafka 81 | spring-kafka 82 | 2.5.14.RELEASE 83 | 84 | 85 | 86 | 87 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/controller/AsyncController.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.controller; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | import org.springframework.beans.factory.annotation.Autowired; 7 | import org.springframework.web.bind.annotation.PathVariable; 8 | import org.springframework.web.bind.annotation.PostMapping; 9 | import org.springframework.web.bind.annotation.RequestBody; 10 | import org.springframework.web.bind.annotation.RequestMapping; 11 | import org.springframework.web.bind.annotation.RestController; 12 | 13 | import com.xy.async.biz.AsyncBizService; 14 | import com.xy.async.constant.AsyncConstant; 15 | import com.xy.async.domain.entity.AsyncReq; 16 | import com.xy.async.domain.service.AsyncLogService; 17 | import com.xy.async.domain.service.AsyncReqService; 18 | import com.xy.async.dto.PageInfoDto; 19 | 20 | import cn.hutool.core.util.StrUtil; 21 | 22 | /** 23 | * 异步执行API 24 | * 25 | * @author xiongyan 26 | * @date 2021/1/17 27 | */ 28 | @RestController 29 | @RequestMapping("/xy/async") 30 | public class AsyncController { 31 | 32 | @Autowired 33 | private AsyncReqService asyncReqService; 34 | 35 | @Autowired 36 | private AsyncLogService asyncLogService; 37 | 38 | @Autowired 39 | private AsyncBizService asyncBizService; 40 | 41 | @PostMapping(value = "/page") 42 | public Map page(@RequestBody PageInfoDto pageInfo) { 43 | asyncReqService.listAsyncPage(pageInfo); 44 | return this.success(pageInfo); 45 | } 46 | 47 | @PostMapping(value = "/detail/{id}") 48 | public Map detail(@PathVariable("id") Long id) { 49 | AsyncReq asyncReq = asyncReqService.getById(id); 50 | if (null == asyncReq) { 51 | return this.error("异步任务不存在"); 52 | } 53 | Map dataMap = new HashMap<>(); 54 | if (StrUtil.isNotEmpty(asyncReq.getParamJson())) { 55 | asyncReq.setParamJson(asyncReq.getParamJson().replace("\"", "")); 56 | } 57 | dataMap.put("req", asyncReq); 58 | dataMap.put("log", asyncLogService.getErrorData(id)); 59 | return this.success(dataMap); 60 | } 61 | 62 | @PostMapping(value = "/exec/{id}") 63 | public Map exec(@PathVariable("id") Long id) { 64 | AsyncReq asyncReq = asyncReqService.getById(id); 65 | if (null == asyncReq) { 66 | return this.error("异步任务不存在"); 67 | } 68 | if (asyncBizService.invoke(asyncReq)) { 69 | return this.success("执行成功"); 70 | } else { 71 | return this.error("执行失败"); 72 | } 73 | } 74 | 75 | @PostMapping(value = "/delete/{id}") 76 | public Map delete(@PathVariable("id") Long id) { 77 | asyncReqService.delete(id); 78 | asyncLogService.delete(id); 79 | return this.success("删除成功"); 80 | } 81 | 82 | private Map success(Object data) { 83 | Map map = new HashMap<>(); 84 | map.put("code", AsyncConstant.SUCCESS); 85 | map.put("data", data); 86 | return map; 87 | } 88 | 89 | private Map error(String msg) { 90 | Map map = new HashMap<>(); 91 | map.put("code", AsyncConstant.ERROR); 92 | map.put("msg", msg); 93 | return map; 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | ## 异步策略 2 | ![image](https://github.com/xiongyanokok/fc-async/assets/11241127/1b7aebf1-d4f7-49ee-8830-bcfc48237ebf) 3 | 4 | 5 | ## 安全级别 6 | ![image](https://github.com/xiongyanokok/fc-async/assets/11241127/12432d25-b910-4475-94f6-177237b41b20) 7 | 8 | 9 | ## 效果展示 10 | ![image](https://github.com/xiongyanokok/fc-async/assets/11241127/27129d28-417d-4d0c-b0b5-6138b26e4c11) 11 | 12 | 13 | ## 数据库脚本 14 | ``` sql 15 | CREATE TABLE `async_req` ( 16 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', 17 | `application_name` varchar(100) NOT NULL DEFAULT '' COMMENT '应用名称', 18 | `sign` varchar(50) NOT NULL DEFAULT '' COMMENT '方法签名', 19 | `class_name` varchar(200) NOT NULL DEFAULT '' COMMENT '全路径类名称', 20 | `method_name` varchar(100) NOT NULL DEFAULT '' COMMENT '方法名称', 21 | `async_type` varchar(50) NOT NULL DEFAULT '' COMMENT '异步策略类型', 22 | `exec_status` tinyint NOT NULL DEFAULT '0' COMMENT '执行状态 0:初始化 1:执行失败 2:执行成功', 23 | `exec_count` int NOT NULL DEFAULT '0' COMMENT '执行次数', 24 | `param_json` longtext COMMENT '请求参数', 25 | `remark` varchar(200) NOT NULL DEFAULT '' COMMENT '业务描述', 26 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 27 | `update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '更新时间', 28 | PRIMARY KEY (`id`) USING BTREE, 29 | KEY `idx_applocation_name` (`application_name`) USING BTREE, 30 | KEY `idx_exec_status` (`exec_status`) USING BTREE 31 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='异步处理请求'; 32 | 33 | CREATE TABLE `async_log` ( 34 | `id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID', 35 | `async_id` bigint NOT NULL DEFAULT '0' COMMENT '异步请求ID', 36 | `error_data` longtext COMMENT '执行错误信息', 37 | `create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间', 38 | PRIMARY KEY (`id`) USING BTREE, 39 | KEY `idx_async_id` (`async_id`) USING BTREE 40 | ) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COMMENT='异步处理日志'; 41 | ``` 42 | 43 | 44 | ## 配置 45 | 46 | #### 开关:默认关闭 47 | async.enabled=true 48 | 49 | #### 数据源 druid 50 | async.datasource.driver-class-name=com.mysql.jdbc.Driver
51 | async.datasource.url=jdbc:mysql://127.0.0.1:3306/fc_async?useUnicode=true&characterEncoding=utf-8&zeroDateTimeBehavior=convertToNull&useSSL=false&allowMultiQueries=true&rewriteBatchedStatements=true
52 | async.datasource.username=user
53 | async.datasource.password=xxxx
54 | async.datasource.filters=config
55 | async.datasource.connectionProperties=config.decrypt=true;config.decrypt.key=yyy 56 | #### 静态地址 57 | spring.resources.add-mappings=true
58 | spring.resources.static-locations=classpath:/static/ 59 | 60 | 61 | ### 以下配置都有默认值 62 | #### 核心线程数 63 | async.executor.thread.corePoolSize=10 64 | #### 最大线程数 65 | async.executor.thread.maxPoolSize=50 66 | #### 队列容量 67 | async.executor.thread.queueCapacity=10000 68 | #### 活跃时间 69 | async.executor.thread.keepAliveSeconds=600 70 | 71 | #### 执行成功是否删除记录:默认删除 72 | async.exec.deleted=true 73 | 74 | #### 自定义队列名称前缀:默认应用名称 75 | async.topic=应用名称 76 | 77 | #### 重试执行次数:默认5次 78 | async.exec.count=5 79 | 80 | #### 重试最大查询数量 81 | async.retry.limit=100 82 | 83 | #### 补偿最大查询数量 84 | async.comp.limit=100 85 | 86 | #### 登录开关:默认关闭 87 | async.login.enabled=false 88 | 89 | #### 登录url 90 | async.login.url=http://xxxx.com 91 | 92 | 93 | ## 用法 94 | #### 1,异步开关 95 | async.enabled=true 96 | 97 | #### 2,在需要异步执行的方法加注解 (必须是spring代理方法) 98 | @AsyncExec(type = AsyncExecEnum.SAVE_ASYNC, remark = "数据字典") 99 | 100 | #### 3,人工处理地址 101 | http://localhost:8004/async/index.html 102 | 103 | 104 | 105 | ## 注意 106 | kafka 和 job 需要自行配置实现
107 | 108 | 当然也可以替换掉这两个组件 109 | 110 | 111 | 112 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/handler/impl/AbstractHandlerService.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.handler.impl; 2 | 3 | import org.aspectj.lang.ProceedingJoinPoint; 4 | import org.aspectj.lang.reflect.MethodSignature; 5 | import org.springframework.beans.factory.annotation.Autowired; 6 | import org.springframework.beans.factory.annotation.Value; 7 | 8 | import com.xy.async.annotation.AsyncExec; 9 | import com.xy.async.config.AsyncConverter; 10 | import com.xy.async.config.AsyncProxy; 11 | import com.xy.async.constant.AsyncConstant; 12 | import com.xy.async.domain.entity.AsyncReq; 13 | import com.xy.async.domain.service.AsyncReqService; 14 | import com.xy.async.dto.AsyncExecDto; 15 | import com.xy.async.handler.HandlerService; 16 | import com.xy.async.handler.context.AsyncContext; 17 | import com.xy.async.mq.AsyncProducer; 18 | import com.xy.async.util.JacksonUtil; 19 | 20 | import lombok.extern.slf4j.Slf4j; 21 | 22 | /** 23 | * AbstractHandlerService 24 | * 25 | * @author xiongyan 26 | * @date 2021/11/17 27 | */ 28 | @Slf4j 29 | public abstract class AbstractHandlerService implements HandlerService { 30 | 31 | @Autowired 32 | private AsyncProxy asyncProxy; 33 | 34 | @Autowired 35 | protected AsyncConverter asyncConverter; 36 | 37 | @Autowired 38 | protected AsyncProducer asyncProducer; 39 | 40 | @Autowired 41 | protected AsyncReqService asyncReqService; 42 | 43 | @Value("${spring.application.name}") 44 | private String applicationName; 45 | 46 | @Override 47 | public boolean handle(AsyncContext context) { 48 | // 异步执行数据 49 | AsyncExecDto asyncExecDto = this.getAsyncExecDto(context); 50 | context.setAsyncExecDto(asyncExecDto); 51 | // 执行异步策略 52 | boolean success = this.execute(context); 53 | if (!success) { 54 | // 最终兜底方案直接执行 55 | try { 56 | context.getJoinPoint().proceed(); 57 | } catch (Throwable e) { 58 | log.error("兜底方案依然执行失败:{}", asyncExecDto, e); 59 | log.error("人工处理,queue:{},message:{}", applicationName + AsyncConstant.QUEUE_SUFFIX, JacksonUtil.toJsonString(asyncExecDto)); 60 | } 61 | } 62 | return true; 63 | } 64 | 65 | /** 66 | * 保存数据库 67 | * 68 | * @param asyncExecDto 69 | * @param execStatus 70 | * @return 71 | */ 72 | public AsyncReq saveAsyncReq(AsyncExecDto asyncExecDto, Integer execStatus) { 73 | AsyncReq asyncReq = asyncConverter.toAsyncReq.apply(asyncExecDto); 74 | try { 75 | asyncReq.setExecStatus(execStatus); 76 | asyncReqService.save(asyncReq); 77 | log.info("异步执行保存数据库成功:{}", asyncReq); 78 | return asyncReq; 79 | } catch (Exception e) { 80 | log.error("异步执行保存数据库失败:{}", asyncReq, e); 81 | return null; 82 | } 83 | } 84 | 85 | /** 86 | * AsyncExecDto 87 | * 88 | * @param context 89 | * @return 90 | */ 91 | private AsyncExecDto getAsyncExecDto(AsyncContext context) { 92 | ProceedingJoinPoint joinPoint = context.getJoinPoint(); 93 | AsyncExec asyncExec = context.getAsyncExec(); 94 | MethodSignature methodSignature = (MethodSignature) joinPoint.getSignature(); 95 | AsyncExecDto asyncExecDto = new AsyncExecDto(); 96 | asyncExecDto.setApplicationName(applicationName); 97 | asyncExecDto.setSign(asyncProxy.getAsyncMethodKey(joinPoint.getTarget(), methodSignature.getMethod())); 98 | asyncExecDto.setClassName(joinPoint.getTarget().getClass().getName()); 99 | asyncExecDto.setMethodName(methodSignature.getMethod().getName()); 100 | asyncExecDto.setAsyncType(asyncExec.type().name()); 101 | asyncExecDto.setParamJson(JacksonUtil.toJsonString(joinPoint.getArgs())); 102 | asyncExecDto.setRemark(asyncExec.remark()); 103 | return asyncExecDto; 104 | } 105 | 106 | } 107 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/config/LoginInterceptor.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.config; 2 | 3 | import java.io.IOException; 4 | 5 | import javax.servlet.http.HttpServletRequest; 6 | 7 | import org.aspectj.lang.ProceedingJoinPoint; 8 | import org.aspectj.lang.annotation.Around; 9 | import org.aspectj.lang.annotation.Aspect; 10 | import org.aspectj.lang.annotation.Pointcut; 11 | import org.springframework.beans.factory.annotation.Value; 12 | import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty; 13 | import org.springframework.stereotype.Component; 14 | import org.springframework.web.context.request.RequestAttributes; 15 | import org.springframework.web.context.request.RequestContextHolder; 16 | import org.springframework.web.context.request.ServletRequestAttributes; 17 | 18 | import com.xy.async.constant.AsyncConstant; 19 | 20 | import cn.hutool.core.util.StrUtil; 21 | import cn.hutool.http.HttpRequest; 22 | import cn.hutool.http.HttpResponse; 23 | import cn.hutool.http.HttpUtil; 24 | import cn.hutool.json.JSONUtil; 25 | import lombok.extern.slf4j.Slf4j; 26 | 27 | /** 28 | * 统一登录拦截器 29 | * 30 | * @author xiongyan 31 | * @date 2022/8/17 32 | */ 33 | @Slf4j 34 | @Aspect 35 | @Component 36 | @ConditionalOnProperty(prefix = "async", value = "enabled", havingValue = "true") 37 | public class LoginInterceptor { 38 | 39 | @Value("${async.login.enabled:false}") 40 | private boolean loginEnabled; 41 | 42 | @Value("${async.login.url:}") 43 | private String loginUrl; 44 | 45 | @Value("${spring.application.name}") 46 | private String applicationName; 47 | 48 | private static final String RESULT = "{\"code\":%s,\"msg\":\"%s\"}"; 49 | 50 | @Pointcut("within(com.xy.async.controller.AsyncController)") 51 | public void pointcut() { 52 | } 53 | 54 | @Around(value = "pointcut()") 55 | public Object around(ProceedingJoinPoint joinPoint) throws Throwable { 56 | if (!loginEnabled) { 57 | return joinPoint.proceed(); 58 | } 59 | 60 | RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes(); 61 | ServletRequestAttributes servletRequestAttributes = (ServletRequestAttributes) requestAttributes; 62 | try { 63 | HttpServletRequest request = servletRequestAttributes.getRequest(); 64 | StringBuilder url = new StringBuilder("登录URL?redirect="); 65 | url.append("应用URL"); 66 | url.append(applicationName); 67 | url.append("/async/index.html"); 68 | String authorization = request.getHeader("authorization"); 69 | if (StrUtil.isEmpty(authorization) || !this.login(authorization)) { 70 | this.print(servletRequestAttributes, AsyncConstant.LOGIN, url.toString()); 71 | return null; 72 | } 73 | return joinPoint.proceed(); 74 | } catch (Exception e) { 75 | log.error("统一认证处理失败", e); 76 | this.print(servletRequestAttributes, AsyncConstant.ERROR, "请联系管理员"); 77 | return null; 78 | } 79 | } 80 | 81 | /** 82 | * 根据token校验是否登录 83 | * 84 | * @param authorization 85 | * @return 86 | */ 87 | private boolean login(String authorization) { 88 | HttpRequest httpRequest = HttpUtil.createPost(loginUrl); 89 | httpRequest.header("authorization", authorization); 90 | HttpResponse response = httpRequest.execute(); 91 | String result = response.body(); 92 | if (StrUtil.isEmpty(result)) { 93 | return false; 94 | } 95 | return JSONUtil.parseObj(result).getBool("success"); 96 | } 97 | 98 | /** 99 | * 失败响应 100 | * 101 | * @param servletRequestAttributes 102 | * @param code 103 | * @param msg 104 | * @throws IOException 105 | */ 106 | private void print(ServletRequestAttributes servletRequestAttributes, Integer code, String msg) throws IOException { 107 | servletRequestAttributes.getResponse().getWriter().print(String.format(RESULT, code, msg)); 108 | } 109 | 110 | } 111 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/domain/dao/impl/AsyncReqDaoImpl.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.domain.dao.impl; 2 | 3 | import java.sql.PreparedStatement; 4 | import java.sql.Statement; 5 | import java.util.List; 6 | import java.util.Objects; 7 | 8 | import org.springframework.beans.factory.annotation.Autowired; 9 | import org.springframework.beans.factory.annotation.Value; 10 | import org.springframework.jdbc.core.BeanPropertyRowMapper; 11 | import org.springframework.jdbc.core.JdbcTemplate; 12 | import org.springframework.jdbc.support.GeneratedKeyHolder; 13 | import org.springframework.jdbc.support.KeyHolder; 14 | import org.springframework.stereotype.Repository; 15 | 16 | import com.xy.async.domain.dao.AsyncReqDao; 17 | import com.xy.async.domain.entity.AsyncReq; 18 | 19 | import cn.hutool.core.collection.CollUtil; 20 | 21 | /** 22 | * 异步执行 dao 23 | * 24 | * @author xiongyan 25 | * @date 2021/01/08 26 | */ 27 | @Repository 28 | public class AsyncReqDaoImpl implements AsyncReqDao { 29 | 30 | @Autowired(required = false) 31 | private JdbcTemplate asyncJdbcTemplate; 32 | 33 | /** 34 | * 最大重试执行次数:默认5次 35 | */ 36 | @Value("${async.exec.count:5}") 37 | private int execCount; 38 | 39 | /** 40 | * 每次执行最大查询数量:默认100 41 | */ 42 | @Value("${async.retry.limit:100}") 43 | private int retryLimit; 44 | 45 | /** 46 | * 每次补偿最大查询数量:默认100 47 | */ 48 | @Value("${async.comp.limit:100}") 49 | private int compLimit; 50 | 51 | @Override 52 | public void save(AsyncReq asyncReq) { 53 | String sql = "insert into async_req(application_name, sign, class_name, method_name, async_type, param_json, remark, exec_status) values (?, ?, ?, ?, ?, ?, ?, ?)"; 54 | KeyHolder keyHolder = new GeneratedKeyHolder(); 55 | asyncJdbcTemplate.update(con -> { 56 | PreparedStatement ps = con.prepareStatement(sql, Statement.RETURN_GENERATED_KEYS); 57 | ps.setString(1, asyncReq.getApplicationName()); 58 | ps.setString(2, asyncReq.getSign()); 59 | ps.setString(3, asyncReq.getClassName()); 60 | ps.setString(4, asyncReq.getMethodName()); 61 | ps.setString(5, asyncReq.getAsyncType()); 62 | ps.setString(6, asyncReq.getParamJson()); 63 | ps.setString(7, asyncReq.getRemark()); 64 | ps.setInt(8, asyncReq.getExecStatus()); 65 | return ps; 66 | }, keyHolder); 67 | asyncReq.setId(Objects.requireNonNull(keyHolder.getKey()).longValue()); 68 | } 69 | 70 | @Override 71 | public void update(AsyncReq asyncReq) { 72 | String sql = "update async_req set exec_status = ?, exec_count = exec_count + 1, update_time = ? where id = ?"; 73 | asyncJdbcTemplate.update(sql, asyncReq.getExecStatus(), asyncReq.getUpdateTime(), asyncReq.getId()); 74 | } 75 | 76 | @Override 77 | public void delete(Long id) { 78 | String sql = "delete from async_req where id = ?"; 79 | asyncJdbcTemplate.update(sql, id); 80 | } 81 | 82 | @Override 83 | public AsyncReq getById(Long id) { 84 | String sql = "select * from async_req where id = ?"; 85 | List list = asyncJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AsyncReq.class), id); 86 | return CollUtil.isEmpty(list) ? null : list.get(0); 87 | } 88 | 89 | @Override 90 | public List listRetry(String applicationName) { 91 | String sql = "select * from async_req where exec_status = 1 and exec_count < ? and application_name = ? order by id limit ?"; 92 | return asyncJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AsyncReq.class), execCount, applicationName, retryLimit); 93 | } 94 | 95 | @Override 96 | public List listComp(String applicationName) { 97 | String sql = "select * from async_req where exec_status = 0 and exec_count = 0 and date_add(create_time, interval 1 hour) < now() and application_name = ? order by id limit ?"; 98 | return asyncJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AsyncReq.class), applicationName, compLimit); 99 | } 100 | 101 | @Override 102 | public Integer countAsync(String applicationName) { 103 | String sql = "select count(*) from async_req where exec_status = 1 and exec_count >= ? and application_name = ?"; 104 | return asyncJdbcTemplate.queryForObject(sql, Integer.class, execCount, applicationName); 105 | } 106 | 107 | @Override 108 | public List listAsync(String applicationName, int pageIndex, int pageSize) { 109 | String sql = "select * from async_req where exec_status = 1 and exec_count >= ? and application_name = ? order by id limit ?, ?"; 110 | return asyncJdbcTemplate.query(sql, new BeanPropertyRowMapper<>(AsyncReq.class), execCount, applicationName, pageIndex, pageSize); 111 | } 112 | } 113 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/util/JacksonUtil.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.util; 2 | 3 | import java.io.Serializable; 4 | import java.lang.reflect.ParameterizedType; 5 | import java.lang.reflect.Type; 6 | import java.text.SimpleDateFormat; 7 | import java.util.ArrayList; 8 | import java.util.List; 9 | 10 | import com.fasterxml.jackson.annotation.JsonInclude; 11 | import com.fasterxml.jackson.core.type.TypeReference; 12 | import com.fasterxml.jackson.databind.DeserializationFeature; 13 | import com.fasterxml.jackson.databind.JsonNode; 14 | import com.fasterxml.jackson.databind.MapperFeature; 15 | import com.fasterxml.jackson.databind.ObjectMapper; 16 | 17 | import cn.hutool.core.util.StrUtil; 18 | 19 | /** 20 | * Jackson 工具类 21 | * 22 | * @author xiongyan 23 | * @date 2020/06/01 24 | */ 25 | public final class JacksonUtil { 26 | 27 | /** 28 | * ObjectMapper 29 | */ 30 | private static final ObjectMapper OBJECT_MAPPER = new ObjectMapper(); 31 | 32 | static { 33 | // 序列化时候统一日期格式 34 | OBJECT_MAPPER.setDateFormat(new SimpleDateFormat("yyyy-MM-dd HH:mm:ss")); 35 | // 设置null时候不序列化(只针对对象属性) 36 | OBJECT_MAPPER.setSerializationInclusion(JsonInclude.Include.NON_NULL); 37 | // 反序列化时,属性不存在的兼容处理 38 | OBJECT_MAPPER.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); 39 | // 单引号处理 40 | OBJECT_MAPPER.configure(com.fasterxml.jackson.core.JsonParser.Feature.ALLOW_SINGLE_QUOTES, true); 41 | // 不区分大小写 42 | OBJECT_MAPPER.configure(MapperFeature.ACCEPT_CASE_INSENSITIVE_PROPERTIES, true); 43 | } 44 | 45 | /** 46 | * 对象序列化json字符串 47 | * 48 | * @param object 49 | * @return 50 | */ 51 | public static String toJsonString(Object object) { 52 | try { 53 | return OBJECT_MAPPER.writeValueAsString(object); 54 | } catch (Exception e) { 55 | throw new RuntimeException(e); 56 | } 57 | } 58 | 59 | /** 60 | * json字符串反序列化JsonNode 61 | * 62 | * @param json 63 | * @return 64 | */ 65 | public static Serializable toJsonNode(String json) { 66 | try { 67 | if (StrUtil.isEmpty(json)) { 68 | return json; 69 | } 70 | return (Serializable) OBJECT_MAPPER.readTree(json); 71 | } catch (Exception e) { 72 | return json; 73 | } 74 | } 75 | 76 | /** 77 | * json字符串反序列化对象 78 | * 79 | * @param json 80 | * @param clazz 81 | * @param 82 | * @return 83 | */ 84 | public static T parseObject(String json, Class clazz) { 85 | try { 86 | return OBJECT_MAPPER.readValue(json, clazz); 87 | } catch (Exception e) { 88 | throw new RuntimeException(e); 89 | } 90 | } 91 | 92 | /** 93 | * json字符串反序列化对象 94 | * 95 | * @param json 96 | * @param typeReference 97 | * @param 98 | * @return 99 | */ 100 | public static T parseObject(String json, TypeReference typeReference) { 101 | try { 102 | return OBJECT_MAPPER.readValue(json, typeReference); 103 | } catch (Exception e) { 104 | throw new RuntimeException(e); 105 | } 106 | } 107 | 108 | /** 109 | * json字符串反序列化对象集合 110 | * 111 | * @param json 112 | * @param clazz 113 | * @param 114 | * @return 115 | */ 116 | public static List parseArray(String json, Class clazz) { 117 | try { 118 | return OBJECT_MAPPER.readValue(json, OBJECT_MAPPER.getTypeFactory().constructCollectionType(ArrayList.class, clazz)); 119 | } catch (Exception e) { 120 | throw new RuntimeException(e); 121 | } 122 | } 123 | 124 | /** 125 | * toObjects 126 | * 127 | * @param json 128 | * @param types 129 | * @return 130 | */ 131 | public static Object[] toObjects(String json, Type[] types) { 132 | try { 133 | Object[] objects = new Object[types.length]; 134 | JsonNode jsonNode = OBJECT_MAPPER.readTree(json); 135 | 136 | for (int i = 0; i < types.length; i++) { 137 | JsonNode pJsonNode = jsonNode.get(i); 138 | Type pType = types[i]; 139 | String pJson = pJsonNode.toString(); 140 | 141 | if (pType instanceof ParameterizedType) { 142 | objects[i] = OBJECT_MAPPER.readValue(pJson, new TypeReferenceExt>(pType)); 143 | } else { 144 | objects[i] = OBJECT_MAPPER.readValue(pJson, (Class) pType); 145 | } 146 | } 147 | return objects; 148 | } catch (Exception e) { 149 | throw new RuntimeException(e); 150 | } 151 | } 152 | 153 | static class TypeReferenceExt extends TypeReference { 154 | 155 | protected Type parameterized; 156 | 157 | public TypeReferenceExt(Type parameterized) { 158 | this.parameterized = parameterized; 159 | } 160 | 161 | @Override 162 | public Type getType() { 163 | return parameterized; 164 | } 165 | } 166 | 167 | } 168 | -------------------------------------------------------------------------------- /src/main/java/com/xy/async/biz/impl/AsyncBizServiceImpl.java: -------------------------------------------------------------------------------- 1 | package com.xy.async.biz.impl; 2 | 3 | import java.lang.reflect.Method; 4 | import java.lang.reflect.Type; 5 | 6 | import org.apache.commons.lang3.exception.ExceptionUtils; 7 | import org.springframework.beans.factory.annotation.Autowired; 8 | import org.springframework.beans.factory.annotation.Value; 9 | import org.springframework.stereotype.Component; 10 | import org.springframework.util.ReflectionUtils; 11 | 12 | import com.xy.async.biz.AsyncBizService; 13 | import com.xy.async.config.AsyncConverter; 14 | import com.xy.async.config.AsyncProxy; 15 | import com.xy.async.constant.AsyncConstant; 16 | import com.xy.async.domain.entity.AsyncLog; 17 | import com.xy.async.domain.entity.AsyncReq; 18 | import com.xy.async.domain.service.AsyncLogService; 19 | import com.xy.async.domain.service.AsyncReqService; 20 | import com.xy.async.dto.AsyncExecDto; 21 | import com.xy.async.dto.ProxyMethodDto; 22 | import com.xy.async.enums.AsyncTypeEnum; 23 | import com.xy.async.enums.ExecStatusEnum; 24 | import com.xy.async.util.JacksonUtil; 25 | 26 | import lombok.extern.slf4j.Slf4j; 27 | 28 | /** 29 | * 异步执行实现 30 | * 31 | * @author xiongyan 32 | * @date 2021/01/08 33 | */ 34 | @Slf4j 35 | @Component 36 | public class AsyncBizServiceImpl implements AsyncBizService { 37 | 38 | @Autowired 39 | private AsyncReqService asyncReqService; 40 | 41 | @Autowired 42 | private AsyncLogService asyncLogService; 43 | 44 | @Autowired 45 | private AsyncConverter asyncConverter; 46 | 47 | @Autowired 48 | private AsyncProxy asyncProxy; 49 | 50 | /** 51 | * 执行成功是否删除:默认是 52 | */ 53 | @Value("${async.exec.deleted:true}") 54 | private boolean deleted; 55 | 56 | /** 57 | * 最大重试执行次数:默认5次 58 | */ 59 | @Value("${async.exec.count:5}") 60 | private int execCount; 61 | 62 | /** 63 | * 执行方法 64 | * 65 | * @param asyncReq 66 | * @return 67 | */ 68 | @Override 69 | public boolean invoke(AsyncReq asyncReq) { 70 | return this.invoke(asyncConverter.toAsyncExecDto.apply(asyncReq)); 71 | } 72 | 73 | /** 74 | * 执行方法 75 | * 76 | * @param asyncExecDto 77 | * @return 78 | */ 79 | @Override 80 | public boolean invoke(AsyncExecDto asyncExecDto) { 81 | if (null == asyncExecDto) { 82 | return true; 83 | } 84 | // 标记 85 | AsyncConstant.PUBLISH_EVENT.set(Boolean.TRUE); 86 | // 获取执行的类和方法 87 | ProxyMethodDto proxyMethodDto = asyncProxy.getProxyMethod(asyncExecDto.getSign()); 88 | if (null == proxyMethodDto) { 89 | log.warn("异步执行代理类方法不存在:{}", asyncExecDto); 90 | return true; 91 | } 92 | 93 | if (null == asyncExecDto.getId()) { 94 | // 直接执行 95 | return this.execute(proxyMethodDto, asyncExecDto); 96 | } else { 97 | // 补偿执行 98 | return this.recoupExecute(proxyMethodDto, asyncExecDto); 99 | } 100 | } 101 | 102 | /** 103 | * 直接执行 104 | * 105 | * @param proxyMethodDto 106 | * @param asyncExecDto 107 | * @return 108 | */ 109 | private boolean execute(ProxyMethodDto proxyMethodDto, AsyncExecDto asyncExecDto) { 110 | try { 111 | // 执行异步方法 112 | this.invokeMethod(proxyMethodDto, asyncExecDto); 113 | return true; 114 | } catch (Exception e) { 115 | if (AsyncTypeEnum.ASYNC.name().equals(asyncExecDto.getAsyncType()) || AsyncTypeEnum.THREAD.name().equals(asyncExecDto.getAsyncType())) { 116 | // 异步消息和异步线程 执行失败 不保存数据库 117 | log.error("【{}】执行失败:{}", AsyncTypeEnum.getDesc(asyncExecDto.getAsyncType()), asyncExecDto, e); 118 | } else { 119 | // 保存异步执行请求 120 | this.saveAsyncReq(asyncExecDto); 121 | } 122 | return false; 123 | } 124 | } 125 | 126 | /** 127 | * 补偿执行 128 | * 129 | * @param proxyMethodDto 130 | * @param asyncExecDto 131 | * @return 132 | */ 133 | private boolean recoupExecute(ProxyMethodDto proxyMethodDto, AsyncExecDto asyncExecDto) { 134 | AsyncReq asyncReq = asyncReqService.getById(asyncExecDto.getId()); 135 | if (null == asyncReq) { 136 | return true; 137 | } 138 | try { 139 | // 执行异步方法 140 | this.invokeMethod(proxyMethodDto, asyncExecDto); 141 | // 更新执行结果 142 | this.updateAsyncReq(asyncReq); 143 | return true; 144 | } catch (Exception e) { 145 | if (asyncReq.getExecCount() + 1 >= execCount) { 146 | log.error("异步执行方法失败超过{}次:{}", execCount, asyncExecDto, e); 147 | } 148 | // 执行失败更新执行次数且记录失败日志 149 | this.saveAsyncLog(asyncReq, e); 150 | return false; 151 | } 152 | } 153 | 154 | /** 155 | * 反射执行异步方法 156 | * 157 | * @param proxyMethodDto 158 | * @param asyncExecDto 159 | */ 160 | private void invokeMethod(ProxyMethodDto proxyMethodDto, AsyncExecDto asyncExecDto) { 161 | log.info("异步执行方法开始:{}", asyncExecDto); 162 | // 获取参数类型 163 | Object[] paramTypes = this.getParamType(proxyMethodDto.getMethod(), asyncExecDto.getParamJson()); 164 | // 执行方法 165 | ReflectionUtils.invokeMethod(proxyMethodDto.getMethod(), proxyMethodDto.getBean(), paramTypes); 166 | log.info("异步执行方法成功:{}", asyncExecDto); 167 | } 168 | 169 | /** 170 | * 获取方法参数 171 | * 172 | * @param method 173 | * @param data 174 | * @return 175 | */ 176 | private Object[] getParamType(Method method, String data) { 177 | Type[] types = method.getGenericParameterTypes(); 178 | if (types.length == 0) { 179 | return null; 180 | } 181 | return JacksonUtil.toObjects(data, types); 182 | } 183 | 184 | /** 185 | * 保存异步执行请求 186 | * 187 | * @param asyncExecDto 188 | */ 189 | private void saveAsyncReq(AsyncExecDto asyncExecDto) { 190 | AsyncReq asyncReq = asyncConverter.toAsyncReq.apply(asyncExecDto); 191 | asyncReq.setExecStatus(ExecStatusEnum.ERROR.getStatus()); 192 | asyncReqService.save(asyncReq); 193 | log.info("处理失败后保存数据库成功:{}", asyncReq); 194 | } 195 | 196 | /** 197 | * 执行失败更新执行次数且记录失败日志 198 | * 199 | * @param asyncReq 200 | * @param e 201 | */ 202 | private void saveAsyncLog(AsyncReq asyncReq, Exception e) { 203 | // 更新状态为失败 204 | asyncReqService.updateStatus(asyncReq.getId(), ExecStatusEnum.ERROR.getStatus()); 205 | // 保存执行失败日志 206 | AsyncLog asyncLog = new AsyncLog(); 207 | asyncLog.setAsyncId(asyncReq.getId()); 208 | asyncLog.setErrorData(ExceptionUtils.getStackTrace(e)); 209 | asyncLogService.save(asyncLog); 210 | log.info("处理失败后保存失败日志成功:{}", asyncReq); 211 | } 212 | 213 | /** 214 | * 更新异步执行请求 215 | * 216 | * @param asyncReq 217 | */ 218 | private void updateAsyncReq(AsyncReq asyncReq) { 219 | if (deleted) { 220 | // 删除异步执行请求 221 | asyncReqService.delete(asyncReq.getId()); 222 | } else { 223 | // 更新状态为成功 224 | asyncReqService.updateStatus(asyncReq.getId(), ExecStatusEnum.SUCCESS.getStatus()); 225 | } 226 | if (asyncReq.getExecStatus() == ExecStatusEnum.ERROR.getStatus()) { 227 | // 删除异步执行日志 228 | asyncLogService.delete(asyncReq.getId()); 229 | } 230 | } 231 | 232 | } 233 | -------------------------------------------------------------------------------- /src/main/resources/static/async/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 24 | 25 | 26 | 27 |
28 | 57 |
58 | 216 | 217 | --------------------------------------------------------------------------------