feat: 新增退单操作接口及相关逻辑

主要修改:
1. 在LinkController中新增退单操作接口,支持用户对指定链接进行退单。
2. 在LinkStatusService中实现退单逻辑,确保用户只能退单自己的链接,并更新链接状态。
3. 在ScriptClient中新增调用退单接口的方法,处理与外部系统的交互。

技术细节:
- 通过新增的退单功能,提升了用户对链接的管理能力,确保操作的安全性和有效性。
This commit is contained in:
zyh
2025-08-27 19:07:37 +08:00
parent 1377c25847
commit 02c64b3a38
6 changed files with 543 additions and 1 deletions

View File

@@ -253,6 +253,49 @@ public Mono<Boolean> deleteLink(@PathVariable("codeNo") String codeNo, Authentic
});
}
@PostMapping("/{codeNo}/refund")
@Operation(summary = "退单操作", description = "对指定链接进行退单操作,会调用外部退单接口并更新数据库状态")
public Mono<Boolean> refundOrder(@PathVariable("codeNo") String codeNo, Authentication authentication) {
log.info("=== 开始退单操作 ===");
log.info("链接编号: {}", codeNo);
if (authentication == null) {
log.error("=== 认证失败Authentication为空 ===");
return Mono.error(new IllegalArgumentException("用户未认证Authentication为空"));
}
// 获取用户ID
Claims claims = (Claims) authentication.getDetails();
if (claims == null) {
log.error("=== 认证失败Claims为空 ===");
log.error("Authentication details: {}", authentication.getDetails());
return Mono.error(new IllegalArgumentException("用户未认证Claims为空"));
}
Long agentId = claims.get("userId", Long.class);
String userType = claims.get("userType", String.class);
log.info("用户信息: agentId={}, userType={}", agentId, userType);
if (agentId == null) {
log.error("=== 无法获取用户ID ===");
return Mono.error(new IllegalArgumentException("无法获取用户ID"));
}
return linkStatusService.refundOrder(codeNo, agentId)
.doOnSuccess(success -> {
if (success) {
log.info("退单操作成功: codeNo={}, agentId={}", codeNo, agentId);
} else {
log.warn("退单操作失败: codeNo={}, agentId={}", codeNo, agentId);
}
})
.doOnError(error -> {
log.error("退单操作时发生错误: codeNo={}, agentId={}, error={}",
codeNo, agentId, error.getMessage(), error);
});
}
@PostMapping("/batch-delete")
@Operation(summary = "批量删除链接", description = "批量删除指定的链接用户只能删除自己创建的链接最多一次删除100个")
public Mono<BatchDeleteResponse> batchDeleteLinks(@RequestBody BatchDeleteRequest request, Authentication authentication) {

View File

@@ -359,6 +359,29 @@ public class ScriptClient {
log.warn("获取设备状态失败: 设备={}, 错误={}", machineId, e.toString());
});
}
/**
* 调用退单接口
* @param machineId 真实设备编号 (如 f1, ss9)
* @return 退单操作结果
*/
public Mono<String> refundOrder(String machineId) {
String url = String.format(apiBaseUrl + "/yijianwan_netfile/saveMsg?文件名=判断退单&cc2=%s", machineId);
log.info("调用退单接口: 设备={}, url={}", machineId, url);
return webClient.get()
.uri(url)
.accept(MediaType.TEXT_PLAIN)
.retrieve()
.bodyToMono(String.class)
.timeout(Duration.ofSeconds(10))
.doOnSuccess(result -> {
log.info("退单接口调用成功: 设备={}, 结果={}", machineId, result);
})
.doOnError(e -> {
log.error("退单接口调用失败: 设备={}, 错误={}", machineId, e.toString());
});
}
}

View File

@@ -145,6 +145,93 @@ public class LinkStatusService {
.onErrorReturn(false);
}
/**
* 退单操作
* @param codeNo 链接编号
* @param agentId 代理ID确保用户只能退单自己的链接
* @return 退单结果
*/
@Transactional
public Mono<Boolean> refundOrder(String codeNo, Long agentId) {
return Mono.fromCallable(() -> doRefundOrder(codeNo, agentId))
.subscribeOn(Schedulers.boundedElastic());
}
private Boolean doRefundOrder(String codeNo, Long agentId) {
log.info("=== 开始处理退单操作 ===");
log.info("链接编号: {}, 代理ID: {}", codeNo, agentId);
try {
// 1. 查询链接任务
LinkTask linkTask = linkTaskMapper.findByCodeNo(codeNo);
if (linkTask == null) {
log.error("链接任务不存在: codeNo={}", codeNo);
throw new IllegalArgumentException("链接不存在");
}
// 2. 验证代理ID确保用户只能退单自己的链接
if (!linkTask.getAgentId().equals(agentId)) {
log.error("权限不足: 用户{}无权退单链接{}", agentId, codeNo);
throw new IllegalArgumentException("权限不足:您无权操作此链接");
}
// 3. 检查链接状态是否允许退单
String currentStatus = linkTask.getStatus();
if ("REFUNDED".equals(currentStatus)) {
log.warn("链接已经退过单: codeNo={}, status={}", codeNo, currentStatus);
throw new IllegalStateException("链接已经退过单");
}
if ("EXPIRED".equals(currentStatus)) {
log.warn("过期链接不允许退单: codeNo={}, status={}", codeNo, currentStatus);
throw new IllegalStateException("过期链接不允许退单");
}
if ("COMPLETED".equals(currentStatus)) {
log.warn("已完成链接不允许退单: codeNo={}, status={}", codeNo, currentStatus);
throw new IllegalStateException("已完成链接不允许退单");
}
// 4. 如果链接有关联的设备,调用脚本端退单接口
String machineId = linkTask.getMachineId();
if (machineId != null && !machineId.trim().isEmpty()) {
log.info("链接关联设备: {}, 调用脚本端退单接口", machineId);
try {
// 同步调用脚本端退单接口
String refundResult = scriptClient.refundOrder(machineId).block();
log.info("脚本端退单接口调用成功: 设备={}, 结果={}", machineId, refundResult);
} catch (Exception e) {
log.error("脚本端退单接口调用失败: 设备={}, 错误={}", machineId, e.getMessage());
// 即使脚本端调用失败,我们仍然继续更新数据库状态
// 这样可以确保用户能够看到退单状态,避免重复退单
}
} else {
log.info("链接未关联设备,跳过脚本端退单调用");
}
// 5. 更新链接状态为REFUNDED
linkTask.setStatus("REFUNDED");
linkTask.setRefundAt(LocalDateTime.now());
linkTask.setUpdatedAt(LocalDateTime.now());
int updateResult = linkTaskMapper.update(linkTask);
if (updateResult <= 0) {
log.error("更新链接状态失败: codeNo={}", codeNo);
throw new RuntimeException("更新链接状态失败");
}
log.info("退单操作成功: codeNo={}, 设备={}, 状态更新为REFUNDED", codeNo, machineId);
log.info("=== 退单操作完成 ===");
return true;
} catch (Exception e) {
log.error("=== 退单操作失败 ===");
log.error("codeNo={}, agentId={}, 错误详情: {}", codeNo, agentId, e.getMessage(), e);
throw e;
}
}
/**
* 删除链接(确保用户只能删除自己的链接)
*/

View File

@@ -70,7 +70,7 @@ script:
# 服务器配置
app:
base-url: "http://192.140.164.137:18080" # 生产环境需要配置为实际域名
base-url: "https://2.uzi0.cc" # 生产环境需要配置为实际域名
image-save-path: "./images" # 图片保存路径
link: