第三方和审核场景回调还是主动查询
博主介绍:✌全网粉丝5W+,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验✌
博主作品:《Java项目案例》主要基于SpringBoot+MyBatis/MyBatis-plus+MySQL+Vue等前后端分离项目,可以在左边的分类专栏找到更多项目。《Uniapp项目案例》有几个有uniapp教程,企业实战开发。《微服务实战》专栏是本人的实战经验总结,《Spring家族及微服务系列》专注Spring、SpringMVC、SpringBoot、SpringCloud系列、Nacos等源码解读、热门面试题、架构设计等。除此之外还有不少文章等你来细细品味,更多惊喜等着你哦
🍅uniapp微信小程序🍅面试题软考题免费使用,还可以使用微信支付,扫码加群。由于维护成本问题得不到解决,可能将停止线上维护。
🍅文末获取联系🍅精彩专栏推荐订阅👇🏻👇🏻 不然下次找不到哟
Java项目案例《100套》
https://blog.csdn.net/qq_57756904/category_12173599.html
uniapp小程序《100套》https://blog.csdn.net/qq_57756904/category_12173599.html
有需求代码永远写不完,而方法才是破解之道,抖音有实战视频课程,某马某千等培训都是2万左右,甚至广东有本科院校单单一年就得3万4年就12万学费,而且还没有包括吃饭的钱。所以很划算了。另外博客左侧有源码阅读专栏,对于求职有很大帮助,当然对于工作也是有指导意义等。在大城市求职,你面试来回一趟多多少少都在12块左右,而且一般不会一次性就通过,还得面试几家。而如果你对源码以及微服务等有深度认识,这无疑给你的面试添砖加瓦更上一层楼。
最后再送一句:最好是学会了,而不是学废了!!
2
我们面临的需求是:第三方推送商品信息到我们平台,我们需要审核后才能上架。第三方想要百分百知道商品是否成功上架,但审核时间不确定。因此,我们需要设计一个机制,让第三方能够准确地得知商品最终的审核状态。
设计思路:
-
第三方推送商品数据到我们系统,我们将其保存为待审核状态,并返回一个唯一标识(比如商品ID)给第三方。
-
由于审核是人工或异步过程,我们不能立即返回审核结果。因此,我们需要提供一种方式让第三方可以查询商品审核状态。
-
另外,我们也可以提供回调通知(webhook)的方式,当审核完成后,主动通知第三方审核结果。
两种方式可以同时提供,也可以根据实际情况选择。下面分别描述:
方式一:提供状态查询接口
-
第三方在推送商品后,会收到一个商品ID(或审核任务ID)。第三方可以定期使用这个ID来查询审核状态。
方式二:审核结果回调
-
第三方在推送商品时,需要提供一个回调地址(callback URL)。当审核完成后,我们向这个地址发送审核结果。
考虑到第三方可能两种方式都需要,我们可以同时实现。
具体实现:
-
数据库设计:
商品表(product)需要包含以下字段(至少):-
id: 商品ID(主键)
-
third_party_id: 第三方商品ID(可选,用于第三方标识自己的商品)
-
name: 商品名称
-
description: 商品描述
-
status: 状态(待审核:PENDING,审核通过:APPROVED,审核拒绝:REJECTED)
-
create_time: 创建时间
-
update_time: 更新时间
-
callback_url: 第三方回调地址(可选,如果使用回调方式则需存储)
-
-
接口设计:
a. 商品推送接口:
- 请求方式:POST
- 路径:/api/product/push
- 请求体:包含商品信息(如名称、描述等)和回调地址(可选)
- 响应:返回商品ID(或审核任务ID)和当前状态(一般为PENDING)b. 审核状态查询接口:
- 请求方式:GET
- 路径:/api/product/{id}/status
- 响应:返回商品ID和当前状态c. 审核处理(内部使用):
- 提供一个内部接口或管理界面,供审核人员更改商品状态。当状态更改时,如果该商品有回调地址,则触发回调通知。 -
回调通知实现:
-
当商品状态变为审核通过或拒绝时,检查是否有回调地址。如果有,则异步发送HTTP请求到回调地址,通知审核结果。
-
-
注意事项:
-
回调通知可能会失败,因此需要实现重试机制。例如,使用消息队列来异步处理回调,并记录重试次数,如果多次失败则记录日志并人工处理。
-
技术实现:
我们使用Spring Boot来构建项目。
步骤:
-
创建Product实体类。
-
创建ProductRepository,使用JPA或MyBatis等持久层框架。
-
创建ProductService,包含推送商品、查询状态、更新审核状态等方法。
-
创建ProductController,提供推送接口和状态查询接口。
-
创建审核回调的组件,可以使用Spring的异步功能或消息队列(如RabbitMQ、Kafka)来异步发送回调通知。
下面是一个简单的代码框架:
实体类(Product):
针对您描述的场景,我设计一个完整的审核流程方案,包含状态管理和回调机制:
1. 数据库设计
CREATE TABLE product_audit (id BIGINT PRIMARY KEY AUTO_INCREMENT,third_party_id VARCHAR(64) NOT NULL COMMENT '第三方商品ID',product_data JSON NOT NULL COMMENT '商品完整数据',audit_status TINYINT NOT NULL DEFAULT 0 COMMENT '审核状态:0-待审核,1-审核通过,2-审核拒绝',audit_remark VARCHAR(500) COMMENT '审核备注',audit_time DATETIME COMMENT '审核时间',auditor VARCHAR(64) COMMENT '审核人',push_time DATETIME NOT NULL COMMENT '推送时间',push_success TINYINT NOT NULL DEFAULT 1 COMMENT '推送是否成功',callback_status TINYINT NOT NULL DEFAULT 0 COMMENT '回调状态:0-未回调,1-回调成功,2-回调失败',callback_time DATETIME COMMENT '最后一次回调时间',callback_url VARCHAR(500) COMMENT '第三方回调地址',retry_count INT DEFAULT 0 COMMENT '回调重试次数',version INT DEFAULT 0 COMMENT '乐观锁版本',created_time DATETIME DEFAULT CURRENT_TIMESTAMP,updated_time DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,INDEX idx_third_party_id (third_party_id),INDEX idx_audit_status (audit_status),INDEX idx_push_time (push_time)
);
2. 核心枚举定义
public enum AuditStatus {PENDING(0, "待审核"),APPROVED(1, "审核通过"),REJECTED(2, "审核拒绝");private final int code;private final String desc;// getter, constructor...
}public enum CallbackStatus {NOT_CALLED(0, "未回调"),SUCCESS(1, "回调成功"),FAILED(2, "回调失败");private final int code;private final String desc;// getter, constructor...
}
3. 实体类
@Entity
@Table(name = "product_audit")
public class ProductAudit {@Id@GeneratedValue(strategy = GenerationType.IDENTITY)private Long id;private String thirdPartyId;@Column(columnDefinition = "json")private String productData;private Integer auditStatus;private String auditRemark;private LocalDateTime auditTime;private String auditor;private LocalDateTime pushTime;private Boolean pushSuccess;private Integer callbackStatus;private LocalDateTime callbackTime;private String callbackUrl;private Integer retryCount;@Versionprivate Integer version;// getters and setters...
}
4. 服务层实现
4.1 审核服务
@Service
@Transactional
public class ProductAuditService {@Autowiredprivate ProductAuditRepository auditRepository;@Autowiredprivate CallbackService callbackService;/*** 接收第三方推送*/public PushResult pushProduct(ProductPushRequest request) {ProductAudit audit = new ProductAudit();audit.setThirdPartyId(request.getThirdPartyId());audit.setProductData(JSON.toJSONString(request.getProductData()));audit.setPushTime(LocalDateTime.now());audit.setPushSuccess(true);audit.setCallbackUrl(request.getCallbackUrl());audit.setAuditStatus(AuditStatus.PENDING.getCode());audit.setCallbackStatus(CallbackStatus.NOT_CALLED.getCode());auditRepository.save(audit);return new PushResult(true, "推送成功", audit.getId().toString());}/*** 审核操作*/public void auditProduct(Long auditId, boolean approved, String remark, String auditor) {ProductAudit audit = auditRepository.findById(auditId).orElseThrow(() -> new RuntimeException("审核记录不存在"));if (!AuditStatus.PENDING.getCode().equals(audit.getAuditStatus())) {throw new RuntimeException("当前状态不可审核");}audit.setAuditStatus(approved ? AuditStatus.APPROVED.getCode() : AuditStatus.REJECTED.getCode());audit.setAuditRemark(remark);audit.setAuditor(auditor);audit.setAuditTime(LocalDateTime.now());auditRepository.save(audit);// 异步触发回调callbackService.triggerCallback(audit);}/*** 查询审核状态*/public AuditStatusVO getAuditStatus(String thirdPartyId) {return auditRepository.findByThirdPartyId(thirdPartyId).map(this::convertToVO).orElseThrow(() -> new RuntimeException("商品记录不存在"));}
}
4.2 回调服务
@Service
@Slf4j
public class CallbackService {@Autowiredprivate ProductAuditRepository auditRepository;@Autowiredprivate RestTemplate restTemplate;@Async("callbackExecutor")public void triggerCallback(ProductAudit audit) {if (StringUtils.isEmpty(audit.getCallbackUrl())) {return;}CallbackRequest callbackRequest = buildCallbackRequest(audit);boolean success = doCallback(audit.getCallbackUrl(), callbackRequest);updateCallbackStatus(audit, success);}private boolean doCallback(String callbackUrl, CallbackRequest request) {int maxRetries = 3;for (int i = 0; i < maxRetries; i++) {try {ResponseEntity<String> response = restTemplate.postForEntity(callbackUrl, request, String.class);if (response.getStatusCode().is2xxSuccessful()) {log.info("回调成功: {}", callbackUrl);return true;}} catch (Exception e) {log.warn("回调失败, 第{}次重试: {}", i + 1, e.getMessage());if (i < maxRetries - 1) {try {Thread.sleep(2000 * (i + 1)); // 递增重试间隔} catch (InterruptedException ie) {Thread.currentThread().interrupt();}}}}return false;}@Transactionalpublic void updateCallbackStatus(ProductAudit audit, boolean success) {auditRepository.updateCallbackStatus(audit.getId(),success ? CallbackStatus.SUCCESS.getCode() : CallbackStatus.FAILED.getCode(),LocalDateTime.now(),audit.getRetryCount() + 1);}
}
5. 控制器层
@RestController
@RequestMapping("/api/product")
@Validated
public class ProductAuditController {@Autowiredprivate ProductAuditService auditService;/*** 第三方推送商品接口*/@PostMapping("/push")public Result<PushResult> pushProduct(@Valid @RequestBody ProductPushRequest request) {PushResult result = auditService.pushProduct(request);return Result.success(result);}/*** 状态查询接口*/@GetMapping("/status/{thirdPartyId}")public Result<AuditStatusVO> getStatus(@PathVariable String thirdPartyId) {AuditStatusVO status = auditService.getAuditStatus(thirdPartyId);return Result.success(status);}/*** 内部审核接口*/@PostMapping("/audit")public Result<Void> auditProduct(@Valid @RequestBody AuditRequest request) {auditService.auditProduct(request.getAuditId(), request.isApproved(), request.getRemark(), request.getAuditor());return Result.success();}
}
6. 回调请求体
@Data
public class CallbackRequest {private String thirdPartyId;private Integer auditStatus;private String auditRemark;private LocalDateTime auditTime;private Long auditRecordId;private String sign; // 签名,用于安全验证
}
7. 配置类
@Configuration
@EnableAsync
public class AsyncConfig {@Bean("callbackExecutor")public Executor callbackExecutor() {ThreadPoolTaskExecutor executor = new ThreadPoolTaskExecutor();executor.setCorePoolSize(5);executor.setMaxPoolSize(10);executor.setQueueCapacity(100);executor.setThreadNamePrefix("callback-");executor.setRejectedExecutionHandler(new ThreadPoolExecutor.CallerRunsPolicy());executor.initialize();return executor;}
}
8. 定时任务处理回调失败
@Component
@Slf4j
public class CallbackRetryTask {@Autowiredprivate ProductAuditRepository auditRepository;@Autowiredprivate CallbackService callbackService;/*** 每5分钟执行一次,重试失败的回调*/@Scheduled(fixedRate = 300000)public void retryFailedCallbacks() {List<ProductAudit> failedAudits = auditRepository.findByCallbackStatusAndRetryCountLessThan(CallbackStatus.FAILED.getCode(), 5);for (ProductAudit audit : failedAudits) {log.info("重试回调: {}", audit.getThirdPartyId());callbackService.triggerCallback(audit);}}
}
9. 第三方集成方式
方式一:主动查询(推荐)
// 第三方在推送后定期查询状态
@Scheduled(fixedRate = 60000) // 每分钟查询一次
public void checkAuditStatus() {// 调用 GET /api/product/status/{thirdPartyId}
}
方式二:被动接收回调
// 第三方提供回调接口
@PostMapping("/third-party/callback")
public Result<Void> handleAuditCallback(@RequestBody CallbackRequest request) {// 验证签名if (!verifySign(request)) {return Result.error("签名验证失败");}// 处理审核结果processAuditResult(request);return Result.success();
}
系统特点
-
状态明确:清晰区分推送成功和审核成功
-
异步处理:审核和回调异步执行,不影响主流程
-
重试机制:失败回调自动重试,提高可靠性
-
可追溯:完整记录审核流程和操作日志
-
灵活集成:支持主动查询和被动回调两种方式
-
线程安全:使用乐观锁防止并发问题
这样设计既满足了审核流程的不确定性需求,又保证了第三方能够及时准确地获取审核结果。