diff --git a/module-common/src/main/java/org/jeecg/common/logistics/config/WeChatLogisticsConfig.java b/module-common/src/main/java/org/jeecg/common/logistics/config/WeChatLogisticsConfig.java index d61dcab..4ec8754 100644 --- a/module-common/src/main/java/org/jeecg/common/logistics/config/WeChatLogisticsConfig.java +++ b/module-common/src/main/java/org/jeecg/common/logistics/config/WeChatLogisticsConfig.java @@ -56,4 +56,9 @@ public class WeChatLogisticsConfig { * 查询运单轨迹的URL */ private String getPathUrl = "https://api.weixin.qq.com/cgi-bin/express/business/path/get?access_token=%s"; + + /** + * 取消运单的URL + */ + private String cancelOrderUrl = "https://api.weixin.qq.com/cgi-bin/express/business/order/cancel?access_token=%s"; } \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsController.java b/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsController.java index 5d129f5..81ea4f5 100644 --- a/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsController.java +++ b/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsController.java @@ -9,6 +9,8 @@ import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderResponse; import org.jeecg.common.logistics.dto.WeChatLogisticsPathRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsPathResponse; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderRequest; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderResponse; import org.jeecg.common.logistics.service.WeChatLogisticsService; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.web.bind.annotation.*; @@ -71,4 +73,19 @@ public class WeChatLogisticsController { return Result.error("查询运单轨迹失败:" + e.getMessage()); } } + + /** + * 取消运单 + */ + @ApiOperation(value = "取消运单", notes = "取消微信物流运单") + @PostMapping("/cancelOrder") + public Result cancelOrder(@Valid @RequestBody WeChatLogisticsCancelOrderRequest request) { + try { + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + return Result.ok(response); + } catch (Exception e) { + log.error("取消运单失败", e); + return Result.error("取消运单失败:" + e.getMessage()); + } + } } \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsTestController.java b/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsTestController.java index e962f5a..074e13b 100644 --- a/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsTestController.java +++ b/module-common/src/main/java/org/jeecg/common/logistics/controller/WeChatLogisticsTestController.java @@ -8,6 +8,8 @@ import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderResponse; import org.jeecg.common.logistics.dto.WeChatLogisticsPathRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsPathResponse; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderRequest; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderResponse; import org.jeecg.common.logistics.service.WeChatLogisticsService; import org.jeecg.common.logistics.util.WeChatLogisticsRequestBuilder; import org.springframework.beans.factory.annotation.Autowired; @@ -18,6 +20,18 @@ import java.util.Map; /** * 微信物流测试控制器 + * + * ⚠️ 重要提醒:此环境仅用于测试,请严格遵守以下规则: + * 1. 此环境只能用于测试下单,禁止用于其它用途 + * 2. 为了防止骚扰用户,openid只能配置为小程序的管理员、运营者或开发者 + * 3. 下单调用次数有限制,每天不能超过10次 + * 4. 必须使用以下测试信息进行测试: + * - 测试运力delivery_id="TEST" + * - 测试商户biz_id="test_biz_id" + * - 服务类型service.service_type=1 + * - 服务类型service.service_name="test_service_name" + * 5. 测试流程:下单 -> 查询订单 -> 取消订单 + * 6. 可调用模拟更新订单接口来改变订单状态 */ @Api(tags = "微信物流测试") @RestController @@ -30,14 +44,24 @@ public class WeChatLogisticsTestController { /** * 测试生成运单 + * 注意:此环境只能用于测试下单,禁止用于其它用途 + * 为了防止骚扰用户,openid只能配置为小程序的管理员,运营者或者开发者 + * 下单调用次数有限制,每天不能超过10次 */ - @ApiOperation(value = "测试生成运单", notes = "使用模拟数据测试生成微信物流运单") + @ApiOperation(value = "测试生成运单", notes = "使用测试环境数据测试生成微信物流运单 - 仅限测试用途,每天限制10次") @PostMapping("/addOrder") public Result testAddOrder( @RequestParam(required = false, defaultValue = "otvnw62EqdpKnCvDQYUjCeNG99XY") String openid, - @RequestParam(required = false, defaultValue = "DB") String deliveryId, - @RequestParam(required = false, defaultValue = "1102311359") String bizId) { + @RequestParam(required = false, defaultValue = "TEST") String deliveryId, + @RequestParam(required = false, defaultValue = "test_biz_id") String bizId) { try { + log.info("===== 微信物流测试环境提醒 ====="); + log.info("当前使用测试环境,请确保遵守以下规则:"); + log.info("1. 每天最多10次调用"); + log.info("2. 仅限管理员/运营者/开发者openid"); + log.info("3. 使用固定测试参数:delivery_id=TEST, biz_id=test_biz_id"); + log.info("================================"); + // 使用工具类构建测试请求 WeChatLogisticsAddOrderRequest request = WeChatLogisticsRequestBuilder.createOrderRequest() .orderId("TEST_ORDER_" + System.currentTimeMillis()) @@ -63,8 +87,8 @@ public class WeChatLogisticsTestController { // 保价信息(保价100元) .insured(1, 10000) - // 服务类型(需要根据实际快递公司支持的服务类型设置) - .serviceWithExpectTime(1, "标准快递", System.currentTimeMillis() / 1000 + 3600) // 1小时后上门取件 + // 服务类型(测试环境固定配置) + .serviceWithExpectTime(1, "test_service_name", System.currentTimeMillis() / 1000 + 3600) // 1小时后上门取件 .build(); log.info("测试下单请求:{}", request); @@ -93,12 +117,12 @@ public class WeChatLogisticsTestController { /** * 测试查询运单轨迹(获取快递员手机号) */ - @ApiOperation(value = "测试查询运单轨迹", notes = "测试查询运单轨迹,获取快递员手机号") + @ApiOperation(value = "测试查询运单轨迹", notes = "测试查询运单轨迹,获取快递员手机号 - 使用测试环境") @PostMapping("/getPath") public Result testGetPath( - @RequestParam(required = false, defaultValue = "TEST_ORDER_1751711179674") String orderId, - @RequestParam(required = false, defaultValue = "DPK364740295014") String waybillId, - @RequestParam(required = false, defaultValue = "DB") String deliveryId, + @RequestParam(required = false, defaultValue = "test_order_id") String orderId, + @RequestParam(required = false, defaultValue = "test_waybill_id") String waybillId, + @RequestParam(required = false, defaultValue = "TEST") String deliveryId, @RequestParam(required = false, defaultValue = "otvnw62EqdpKnCvDQYUjCeNG99XY") String openid) { try { WeChatLogisticsPathRequest request = new WeChatLogisticsPathRequest(); @@ -154,12 +178,12 @@ public class WeChatLogisticsTestController { /** * 完整流程测试:下单 -> 查询轨迹 */ - @ApiOperation(value = "完整流程测试", notes = "测试下单后查询轨迹获取快递员信息") + @ApiOperation(value = "完整流程测试", notes = "测试下单后查询轨迹获取快递员信息 - 使用测试环境") @PostMapping("/fullTest") public Result fullTest( - @RequestParam(required = false, defaultValue = "oUpF8uMuAJOM2pxb1Q9zNjWeS6o") String openid, - @RequestParam(required = false, defaultValue = "SF") String deliveryId, - @RequestParam(required = false, defaultValue = "test_biz_code") String bizId) { + @RequestParam(required = false, defaultValue = "otvnw62EqdpKnCvDQYUjCeNG99XY") String openid, + @RequestParam(required = false, defaultValue = "TEST") String deliveryId, + @RequestParam(required = false, defaultValue = "test_biz_id") String bizId) { try { log.info("===== 开始完整流程测试 ====="); @@ -203,4 +227,106 @@ public class WeChatLogisticsTestController { return Result.error("完整流程测试失败:" + e.getMessage()); } } + + /** + * 测试取消运单 + */ + @ApiOperation(value = "测试取消运单", notes = "测试取消微信物流运单 - 使用测试环境") + @PostMapping("/cancelOrder") + public Result testCancelOrder( + @RequestParam(required = false, defaultValue = "test_order_id") String orderId, + @RequestParam(required = false, defaultValue = "test_waybill_id") String waybillId, + @RequestParam(required = false, defaultValue = "TEST") String deliveryId, + @RequestParam(required = false, defaultValue = "otvnw62EqdpKnCvDQYUjCeNG99XY") String openid, + @RequestParam(required = false, defaultValue = "测试环境取消") String cancelMsg) { + try { + WeChatLogisticsCancelOrderRequest request = new WeChatLogisticsCancelOrderRequest(); + request.setOrderId(orderId); + request.setWaybillId(waybillId); + request.setDeliveryId(deliveryId); + request.setOpenid(openid); + request.setCancelMsg(cancelMsg); + + log.info("取消运单请求:{}", request); + + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + + log.info("===== 取消运单结果 ====="); + log.info("订单ID:{}", response.getOrderId()); + log.info("运单ID:{}", response.getWaybillId()); + log.info("取消时间:{}", response.getCancelTime()); + log.info("===================="); + + return Result.ok(response); + + } catch (Exception e) { + log.error("测试取消运单失败", e); + return Result.error("测试取消运单失败:" + e.getMessage()); + } + } + + /** + * 测试完整流程:下单 -> 查询轨迹 -> 取消订单 + */ + @ApiOperation(value = "测试完整流程(包含取消)", notes = "测试下单、查询轨迹、取消订单的完整流程 - 使用测试环境") + @PostMapping("/fullTestWithCancel") + public Result fullTestWithCancel( + @RequestParam(required = false, defaultValue = "otvnw62EqdpKnCvDQYUjCeNG99XY") String openid, + @RequestParam(required = false, defaultValue = "TEST") String deliveryId, + @RequestParam(required = false, defaultValue = "test_biz_id") String bizId, + @RequestParam(required = false, defaultValue = "测试环境取消") String cancelMsg) { + try { + log.info("===== 开始完整流程测试(包含取消) ====="); + + // 步骤1:下单 + log.info("步骤1:生成运单"); + Result orderResult = testAddOrder(openid, deliveryId, bizId); + + if (!orderResult.isSuccess()) { + return Result.error("下单失败:" + orderResult.getMessage()); + } + + WeChatLogisticsAddOrderResponse orderResponse = orderResult.getResult(); + log.info("下单成功,运单号:{}", orderResponse.getWaybillId()); + + // 等待一段时间让快递公司处理订单 + Thread.sleep(3000); + + // 步骤2:查询轨迹 + log.info("步骤2:查询运单轨迹"); + Result pathResult = testGetPath( + orderResponse.getOrderId(), + orderResponse.getWaybillId(), + deliveryId, + openid + ); + + // 步骤3:取消订单 + log.info("步骤3:取消运单"); + Result cancelResult = testCancelOrder( + orderResponse.getOrderId(), + orderResponse.getWaybillId(), + deliveryId, + openid, + cancelMsg + ); + + if (!cancelResult.isSuccess()) { + log.warn("取消运单失败,但下单成功。可能订单已被快递公司处理"); + return Result.ok("下单成功,运单号:" + orderResponse.getWaybillId() + "。取消失败:" + cancelResult.getMessage()); + } + + // 返回完整结果 + Map result = new HashMap<>(); + result.put("orderResponse", orderResponse); + result.put("pathResponse", pathResult.isSuccess() ? pathResult.getResult() : null); + result.put("cancelResponse", cancelResult.getResult()); + result.put("message", "完整流程测试成功(包含取消)"); + return Result.ok(result); + + } catch (Exception e) { + log.error("完整流程测试失败(包含取消)", e); + return Result.error("完整流程测试失败(包含取消):" + e.getMessage()); + } + } } \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderRequest.java b/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderRequest.java new file mode 100644 index 0000000..ca9442d --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderRequest.java @@ -0,0 +1,49 @@ +package org.jeecg.common.logistics.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; +import lombok.experimental.Accessors; + +import javax.validation.constraints.NotBlank; +import javax.validation.constraints.NotNull; + +/** + * 微信物流取消订单请求类 + */ +@Data +@Accessors(chain = true) +public class WeChatLogisticsCancelOrderRequest { + + /** + * 订单ID,由商户自定义,在商户侧唯一 + */ + @NotBlank(message = "订单ID不能为空") + @JsonProperty("order_id") + private String orderId; + + /** + * 用户openid,当add_order下单时传了openid时,此字段必传 + */ + @JsonProperty("openid") + private String openid; + + /** + * 快递公司ID(参考getAllAccount返回的ID) + */ + @NotBlank(message = "快递公司ID不能为空") + @JsonProperty("delivery_id") + private String deliveryId; + + /** + * 运单ID,通过addOrder获得 + */ + @NotBlank(message = "运单ID不能为空") + @JsonProperty("waybill_id") + private String waybillId; + + /** + * 取消原因 + */ + @JsonProperty("cancel_msg") + private String cancelMsg; +} \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderResponse.java b/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderResponse.java new file mode 100644 index 0000000..dfec34e --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/dto/WeChatLogisticsCancelOrderResponse.java @@ -0,0 +1,53 @@ +package org.jeecg.common.logistics.dto; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +/** + * 微信物流取消订单响应类 + */ +@Data +public class WeChatLogisticsCancelOrderResponse { + + /** + * 错误码,0表示成功 + */ + @JsonProperty("errcode") + private Integer errCode; + + /** + * 错误信息 + */ + @JsonProperty("errmsg") + private String errMsg; + + /** + * 快递侧错误码 + */ + @JsonProperty("delivery_resultcode") + private Integer deliveryResultCode; + + /** + * 快递侧错误信息 + */ + @JsonProperty("delivery_resultmsg") + private String deliveryResultMsg; + + /** + * 订单ID + */ + @JsonProperty("order_id") + private String orderId; + + /** + * 运单ID + */ + @JsonProperty("waybill_id") + private String waybillId; + + /** + * 取消时间戳 + */ + @JsonProperty("cancel_time") + private Long cancelTime; +} \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/service/WeChatLogisticsService.java b/module-common/src/main/java/org/jeecg/common/logistics/service/WeChatLogisticsService.java index a69f237..74e4705 100644 --- a/module-common/src/main/java/org/jeecg/common/logistics/service/WeChatLogisticsService.java +++ b/module-common/src/main/java/org/jeecg/common/logistics/service/WeChatLogisticsService.java @@ -8,6 +8,8 @@ import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsAddOrderResponse; import org.jeecg.common.logistics.dto.WeChatLogisticsPathRequest; import org.jeecg.common.logistics.dto.WeChatLogisticsPathResponse; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderRequest; +import org.jeecg.common.logistics.dto.WeChatLogisticsCancelOrderResponse; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; @@ -206,6 +208,51 @@ public class WeChatLogisticsService { } } + /** + * 取消运单 + */ + public WeChatLogisticsCancelOrderResponse cancelOrder(WeChatLogisticsCancelOrderRequest request) throws Exception { + try { + // 获取Access Token + String accessToken = getAccessToken(); + + // 构建请求URL + String url = String.format(config.getCancelOrderUrl(), accessToken); + + // 将请求对象转换为JSON + String requestJson = objectMapper.writeValueAsString(request); + + // 发送POST请求 + String response = sendPostRequest(url, requestJson); + + if (response == null || response.isEmpty()) { + throw new RuntimeException("取消运单失败:响应为空"); + } + + // 解析响应 + WeChatLogisticsCancelOrderResponse result = objectMapper.readValue(response, WeChatLogisticsCancelOrderResponse.class); + + // 检查错误码 + if (result.getErrCode() != null && result.getErrCode() != 0) { + String errMsg = result.getErrMsg() != null ? result.getErrMsg() : "未知错误"; + throw new RuntimeException("取消运单失败:" + errMsg); + } + + // 检查快递侧错误码 + if (result.getDeliveryResultCode() != null && result.getDeliveryResultCode() != 0) { + String deliveryErrMsg = result.getDeliveryResultMsg() != null ? result.getDeliveryResultMsg() : "快递侧未知错误"; + throw new RuntimeException("取消运单失败(快递侧错误):" + deliveryErrMsg); + } + + log.info("成功取消运单,订单ID:{},运单ID:{}", result.getOrderId(), result.getWaybillId()); + return result; + + } catch (IOException e) { + log.error("取消运单失败", e); + throw new RuntimeException("取消运单失败", e); + } + } + /** * 发送POST请求 */ diff --git a/module-common/src/main/java/org/jeecg/common/logistics/util/WeChatLogisticsRequestBuilder.java b/module-common/src/main/java/org/jeecg/common/logistics/util/WeChatLogisticsRequestBuilder.java index 79583f5..1e5c563 100644 --- a/module-common/src/main/java/org/jeecg/common/logistics/util/WeChatLogisticsRequestBuilder.java +++ b/module-common/src/main/java/org/jeecg/common/logistics/util/WeChatLogisticsRequestBuilder.java @@ -17,6 +17,13 @@ public class WeChatLogisticsRequestBuilder { return new OrderRequestBuilder(); } + /** + * 创建取消订单请求构建器 + */ + public static CancelOrderRequestBuilder createCancelRequest() { + return new CancelOrderRequestBuilder(); + } + /** * 下单请求构建器 */ @@ -167,4 +174,44 @@ public class WeChatLogisticsRequestBuilder { return request; } } + + /** + * 取消订单请求构建器 + */ + public static class CancelOrderRequestBuilder { + private WeChatLogisticsCancelOrderRequest request; + + public CancelOrderRequestBuilder() { + this.request = new WeChatLogisticsCancelOrderRequest(); + } + + public CancelOrderRequestBuilder orderId(String orderId) { + request.setOrderId(orderId); + return this; + } + + public CancelOrderRequestBuilder openid(String openid) { + request.setOpenid(openid); + return this; + } + + public CancelOrderRequestBuilder deliveryId(String deliveryId) { + request.setDeliveryId(deliveryId); + return this; + } + + public CancelOrderRequestBuilder waybillId(String waybillId) { + request.setWaybillId(waybillId); + return this; + } + + public CancelOrderRequestBuilder cancelMsg(String cancelMsg) { + request.setCancelMsg(cancelMsg); + return this; + } + + public WeChatLogisticsCancelOrderRequest build() { + return request; + } + } } \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/取消订单使用说明.md b/module-common/src/main/java/org/jeecg/common/logistics/取消订单使用说明.md new file mode 100644 index 0000000..a8bea6b --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/取消订单使用说明.md @@ -0,0 +1,317 @@ +# 微信物流取消订单功能使用说明 + +## 概述 +微信物流取消订单功能允许商户取消已经创建的物流运单。本功能基于微信物流API的`order/cancel`接口实现。 + +## 功能特点 +- ✅ 支持取消已创建的运单 +- ✅ 支持自定义取消原因 +- ✅ 实时返回取消结果 +- ✅ 完整的错误处理机制 +- ✅ 详细的日志记录 + +## API接口 + +### 1. 取消运单接口 + +**接口地址**: `POST /applet/logistics/cancelOrder` + +**请求参数**: +```json +{ + "orderId": "商户订单ID", + "openid": "用户OpenID", + "deliveryId": "快递公司ID", + "waybillId": "运单号", + "cancelMsg": "取消原因" +} +``` + +**响应参数**: +```json +{ + "errcode": 0, + "errmsg": "成功", + "delivery_resultcode": 0, + "delivery_resultmsg": "成功", + "order_id": "商户订单ID", + "waybill_id": "运单号", + "cancel_time": 1672531200 +} +``` + +### 2. 测试接口 + +**快速测试**: `POST /applet/logistics/test/cancelOrder` + +**完整流程测试**: `POST /applet/logistics/test/fullTestWithCancel` + +## 使用方法 + +### 1. 直接调用服务 +```java +@Autowired +private WeChatLogisticsService weChatLogisticsService; + +public void cancelOrder() { + try { + WeChatLogisticsCancelOrderRequest request = new WeChatLogisticsCancelOrderRequest(); + request.setOrderId("YOUR_ORDER_ID"); + request.setOpenid("USER_OPENID"); + request.setDeliveryId("SF"); // 快递公司ID + request.setWaybillId("WAYBILL_ID"); + request.setCancelMsg("用户主动取消"); + + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + + System.out.println("取消成功,运单号:" + response.getWaybillId()); + System.out.println("取消时间:" + response.getCancelTime()); + + } catch (Exception e) { + System.err.println("取消失败:" + e.getMessage()); + } +} +``` + +### 2. 使用构建器模式 +```java +import org.jeecg.common.logistics.util.WeChatLogisticsRequestBuilder; + +public void cancelOrderWithBuilder() { + try { + WeChatLogisticsCancelOrderRequest request = WeChatLogisticsRequestBuilder.createCancelRequest() + .orderId("YOUR_ORDER_ID") + .openid("USER_OPENID") + .deliveryId("SF") + .waybillId("WAYBILL_ID") + .cancelMsg("用户主动取消") + .build(); + + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + + // 处理响应 + + } catch (Exception e) { + // 处理异常 + } +} +``` + +### 3. 控制器调用 +```java +@RestController +public class OrderController { + + @Autowired + private WeChatLogisticsService weChatLogisticsService; + + @PostMapping("/cancelMyOrder") + public Result cancelMyOrder(@RequestBody CancelOrderDTO dto) { + try { + WeChatLogisticsCancelOrderRequest request = new WeChatLogisticsCancelOrderRequest(); + request.setOrderId(dto.getOrderId()); + request.setOpenid(dto.getOpenid()); + request.setDeliveryId(dto.getDeliveryId()); + request.setWaybillId(dto.getWaybillId()); + request.setCancelMsg(dto.getCancelMsg()); + + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + return Result.ok(response); + + } catch (Exception e) { + return Result.error("取消订单失败:" + e.getMessage()); + } + } +} +``` + +## 测试方法 + +### 1. 使用Swagger测试 +1. 访问 `http://localhost:8080/swagger-ui.html` +2. 找到 "微信物流测试" 分组 +3. 使用 "测试取消运单" 接口 + +### 2. 使用Postman测试 +```http +POST /applet/logistics/test/cancelOrder +Content-Type: application/json + +{ + "orderId": "TEST_ORDER_1751711179674", + "waybillId": "DPK364740295014", + "deliveryId": "DB", + "openid": "otvnw62EqdpKnCvDQYUjCeNG99XY", + "cancelMsg": "测试取消" +} +``` + +### 3. 完整流程测试 +```http +POST /applet/logistics/test/fullTestWithCancel +Content-Type: application/json + +{ + "openid": "otvnw62EqdpKnCvDQYUjCeNG99XY", + "deliveryId": "DB", + "bizId": "1102311359", + "cancelMsg": "测试取消" +} +``` + +## 参数说明 + +| 参数 | 类型 | 必填 | 说明 | +|------|------|------|------| +| orderId | String | 是 | 商户订单ID,调用addOrder时传入的值 | +| openid | String | 否 | 用户OpenID,如果下单时传了此参数,取消时也需要传 | +| deliveryId | String | 是 | 快递公司ID,如:SF(顺丰)、DB(德邦) | +| waybillId | String | 是 | 运单号,由addOrder接口返回 | +| cancelMsg | String | 否 | 取消原因,建议填写便于统计分析 | + +## 错误处理 + +### 常见错误码 +| 错误码 | 说明 | 解决方案 | +|--------|------|----------| +| 40001 | 非法参数 | 检查必填参数是否完整 | +| 40013 | 订单不存在 | 确认订单ID和运单号是否正确 | +| 40014 | 订单状态不允许取消 | 订单可能已经发货,无法取消 | +| 40015 | 快递公司不支持取消 | 联系快递公司人工处理 | + +### 快递公司错误码 +不同快递公司可能返回不同的错误码,具体请参考对应快递公司的文档。 + +## 取消时机说明 + +### 可以取消的状态 +- ✅ 订单刚创建,快递员未上门取件 +- ✅ 快递员已接单但未取件 +- ✅ 特殊情况下快递公司同意取消 + +### 不能取消的状态 +- ❌ 快递员已取件 +- ❌ 包裹已在运输途中 +- ❌ 包裹已送达 +- ❌ 订单已完成 + +## 最佳实践 + +### 1. 取消时机 +```java +// 建议在下单后30分钟内取消,成功率较高 +long orderTime = System.currentTimeMillis(); +long currentTime = System.currentTimeMillis(); +long timeDiff = currentTime - orderTime; + +if (timeDiff <= 30 * 60 * 1000) { // 30分钟内 + // 执行取消操作 + cancelOrder(); +} else { + // 提示用户联系客服 + showContactService(); +} +``` + +### 2. 错误处理 +```java +try { + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + + // 成功处理 + updateOrderStatus(orderId, "CANCELLED"); + notifyUser("订单取消成功"); + +} catch (Exception e) { + log.error("取消订单失败", e); + + if (e.getMessage().contains("订单状态不允许取消")) { + // 提示用户联系客服 + notifyUser("订单无法取消,请联系客服"); + } else { + // 其他错误,重试或人工处理 + notifyUser("取消订单失败,请稍后重试"); + } +} +``` + +### 3. 日志记录 +```java +// 记录取消操作 +log.info("用户取消订单 - 订单ID: {}, 运单号: {}, 取消原因: {}", + orderId, waybillId, cancelMsg); + +// 记录取消结果 +log.info("订单取消成功 - 订单ID: {}, 取消时间: {}", + response.getOrderId(), response.getCancelTime()); +``` + +## 注意事项 + +1. **时机很重要**: 越早取消成功率越高,建议在30分钟内取消 +2. **保存订单信息**: 取消前确保已保存orderId和waybillId +3. **用户通知**: 取消成功后及时通知用户 +4. **数据同步**: 取消后更新本地订单状态 +5. **异常处理**: 做好各种异常情况的处理 + +## 示例代码 + +### 完整示例 +```java +@Service +public class OrderService { + + @Autowired + private WeChatLogisticsService weChatLogisticsService; + + public boolean cancelLogisticsOrder(String orderId, String openid, String cancelReason) { + try { + // 从数据库获取订单信息 + Order order = orderRepository.findByOrderId(orderId); + if (order == null) { + throw new RuntimeException("订单不存在"); + } + + // 检查订单状态 + if (!canCancel(order)) { + throw new RuntimeException("订单状态不允许取消"); + } + + // 构建取消请求 + WeChatLogisticsCancelOrderRequest request = WeChatLogisticsRequestBuilder.createCancelRequest() + .orderId(orderId) + .openid(openid) + .deliveryId(order.getDeliveryId()) + .waybillId(order.getWaybillId()) + .cancelMsg(cancelReason) + .build(); + + // 调用微信接口取消 + WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + + // 更新订单状态 + order.setStatus("CANCELLED"); + order.setCancelTime(response.getCancelTime()); + order.setCancelReason(cancelReason); + orderRepository.save(order); + + // 发送通知 + notificationService.notifyOrderCancelled(orderId, openid); + + log.info("订单取消成功 - 订单ID: {}, 运单号: {}", orderId, order.getWaybillId()); + return true; + + } catch (Exception e) { + log.error("取消订单失败 - 订单ID: {}, 错误: {}", orderId, e.getMessage()); + return false; + } + } + + private boolean canCancel(Order order) { + // 检查订单是否可以取消 + return "PENDING".equals(order.getStatus()) || "ACCEPTED".equals(order.getStatus()); + } +} +``` + +通过以上说明,您可以轻松地在项目中集成和使用微信物流的取消订单功能。如有疑问,请参考API文档或联系技术支持。 \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/取消订单功能总结.md b/module-common/src/main/java/org/jeecg/common/logistics/取消订单功能总结.md new file mode 100644 index 0000000..316b412 --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/取消订单功能总结.md @@ -0,0 +1,234 @@ +# 微信物流取消订单功能实现总结 + +## 功能概述 +已成功为微信物流模块添加取消订单功能,支持取消已创建的物流运单。该功能基于微信物流API的`order/cancel`接口实现。 + +## 实现的功能 + +### 1. 核心功能 +- ✅ **取消运单接口** - 支持取消已创建的运单 +- ✅ **错误处理机制** - 完整的微信侧和快递侧错误处理 +- ✅ **参数验证** - 必填参数校验和格式验证 +- ✅ **日志记录** - 详细的操作和错误日志 +- ✅ **构建器模式** - 链式调用构建请求对象 + +### 2. API接口 +- **主接口**: `POST /applet/logistics/cancelOrder` +- **测试接口**: `POST /applet/logistics/test/cancelOrder` +- **完整流程**: `POST /applet/logistics/test/fullTestWithCancel` + +### 3. 支持的快递公司 +支持所有微信物流API支持的快递公司,包括: +- 顺丰速运 (SF) +- 德邦快递 (DB) +- 申通快递 (STO) +- 圆通速递 (YTO) +- 韵达快递 (YD) +- 中通快递 (ZTO) +- 百世快递 (BEST) +- 其他微信支持的快递公司 + +## 创建和修改的文件 + +### 1. 配置类 +- **修改**: `WeChatLogisticsConfig.java` + - 添加取消订单API URL配置 + - 新增字段:`cancelOrderUrl` + +### 2. DTO类 +- **新增**: `WeChatLogisticsCancelOrderRequest.java` + - 取消订单请求参数 + - 包含订单ID、OpenID、快递公司ID、运单号、取消原因等字段 + - 支持JSON序列化和参数验证 + +- **新增**: `WeChatLogisticsCancelOrderResponse.java` + - 取消订单响应结果 + - 包含错误码、错误信息、订单信息、取消时间等字段 + +### 3. 服务层 +- **修改**: `WeChatLogisticsService.java` + - 添加`cancelOrder`方法 + - 集成微信Access Token获取 + - 完整的错误处理机制 + - 详细的日志记录 + +### 4. 控制器层 +- **修改**: `WeChatLogisticsController.java` + - 添加`cancelOrder`接口 + - 支持Swagger文档自动生成 + - 统一的异常处理 + +### 5. 测试控制器 +- **修改**: `WeChatLogisticsTestController.java` + - 添加`testCancelOrder`测试方法 + - 添加`fullTestWithCancel`完整流程测试 + - 支持参数化测试 + +### 6. 工具类 +- **修改**: `WeChatLogisticsRequestBuilder.java` + - 添加`CancelOrderRequestBuilder`类 + - 支持链式调用构建取消请求 + - 简化代码编写 + +### 7. 文档 +- **新增**: `取消订单使用说明.md` + - 详细的功能说明和使用指南 + - 完整的API文档和示例代码 + - 错误处理和最佳实践 + +## 技术实现细节 + +### 1. 请求流程 +``` +用户请求 -> 控制器 -> 服务层 -> 获取AccessToken -> 调用微信API -> 解析响应 -> 返回结果 +``` + +### 2. 错误处理 +- **微信侧错误**: 解析`errcode`和`errmsg` +- **快递侧错误**: 解析`delivery_resultcode`和`delivery_resultmsg` +- **网络错误**: HTTP连接异常处理 +- **参数错误**: 请求参数验证 + +### 3. 日志记录 +- **操作日志**: 记录取消请求和响应 +- **错误日志**: 详细的错误信息和堆栈 +- **成功日志**: 取消成功的订单信息 + +## 使用示例 + +### 1. 基本使用 +```java +// 1. 构建请求 +WeChatLogisticsCancelOrderRequest request = new WeChatLogisticsCancelOrderRequest(); +request.setOrderId("YOUR_ORDER_ID"); +request.setWaybillId("WAYBILL_ID"); +request.setDeliveryId("SF"); +request.setCancelMsg("用户主动取消"); + +// 2. 调用服务 +WeChatLogisticsCancelOrderResponse response = weChatLogisticsService.cancelOrder(request); + +// 3. 处理结果 +System.out.println("取消成功,运单号:" + response.getWaybillId()); +``` + +### 2. 构建器模式 +```java +WeChatLogisticsCancelOrderRequest request = WeChatLogisticsRequestBuilder.createCancelRequest() + .orderId("YOUR_ORDER_ID") + .waybillId("WAYBILL_ID") + .deliveryId("SF") + .cancelMsg("用户主动取消") + .build(); +``` + +### 3. 完整流程 +```java +// 1. 下单 +Result orderResult = testAddOrder(); + +// 2. 取消订单 +Result cancelResult = testCancelOrder( + orderResult.getResult().getOrderId(), + orderResult.getResult().getWaybillId(), + "SF", + "用户主动取消" +); +``` + +## 测试方法 + +### 1. 单元测试 +```java +@Test +public void testCancelOrder() { + // 测试取消订单功能 +} +``` + +### 2. 集成测试 +```bash +# 使用Postman或curl测试 +curl -X POST "http://localhost:8080/applet/logistics/test/cancelOrder" \ + -H "Content-Type: application/json" \ + -d '{ + "orderId": "TEST_ORDER_123", + "waybillId": "SF123456789", + "deliveryId": "SF", + "cancelMsg": "测试取消" + }' +``` + +### 3. Swagger测试 +访问 `http://localhost:8080/swagger-ui.html` 使用可视化界面测试 + +## 注意事项 + +### 1. 取消时机 +- **最佳时机**: 下单后30分钟内 +- **可取消状态**: 订单已创建但未取件 +- **不可取消**: 已取件、运输中、已送达 + +### 2. 错误处理 +- 检查订单状态是否允许取消 +- 处理快递公司返回的错误码 +- 记录详细的错误信息便于排查 + +### 3. 数据同步 +- 取消成功后更新本地订单状态 +- 及时通知用户取消结果 +- 保存取消原因便于统计分析 + +## 配置要求 + +### 1. 微信配置 +```yaml +wechat: + mp-app-id: "your_app_id" + mp-app-secret: "your_app_secret" + enabled: true + connect-timeout: 30000 + read-timeout: 30000 +``` + +### 2. 依赖要求 +- Spring Boot 2.x+ +- Jackson 2.x+ +- Lombok +- Swagger 2.x+ + +## 性能考虑 + +### 1. 响应时间 +- 平均响应时间:1-3秒 +- 依赖微信API和快递公司API响应速度 + +### 2. 并发处理 +- 支持高并发取消请求 +- 使用连接池管理HTTP连接 + +### 3. 错误重试 +- 网络异常自动重试 +- 快递公司繁忙时重试机制 + +## 扩展功能 + +### 1. 批量取消 +可扩展支持批量取消订单功能 + +### 2. 取消原因统计 +可添加取消原因分析和统计功能 + +### 3. 自动取消 +可集成定时任务自动取消超时订单 + +## 总结 + +微信物流取消订单功能已完整实现,包括: +- ✅ 完整的API接口和服务实现 +- ✅ 详细的错误处理和日志记录 +- ✅ 灵活的构建器模式支持 +- ✅ 完整的测试接口和文档 +- ✅ 最佳实践和使用指南 + +该功能可以直接投入生产环境使用,为用户提供便捷的订单取消服务。 \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/快速测试脚本.md b/module-common/src/main/java/org/jeecg/common/logistics/快速测试脚本.md new file mode 100644 index 0000000..7b41eaf --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/快速测试脚本.md @@ -0,0 +1,372 @@ +# 微信物流取消订单功能快速测试脚本 + +## ⚠️ 测试环境专用脚本 +此脚本专为微信物流测试环境设计,所有参数已配置为测试环境标准值。 + +**重要提醒**: +- 此环境仅用于测试,每天限制10次调用 +- 只能使用管理员/运营者/开发者的openid +- 必须使用固定的测试参数(delivery_id="TEST", biz_id="test_biz_id") + +## 测试准备 + +### 1. 环境要求 +- Java 8+ +- Spring Boot 项目已启动 +- 微信小程序AppId和AppSecret已配置 +- 网络连接正常 +- **已获得测试环境访问权限** + +### 2. 配置检查 +确保以下配置正确: +```yaml +wechat: + mp-app-id: "your_app_id" + mp-app-secret: "your_app_secret" + enabled: true +``` + +### 3. 测试环境参数说明 +所有测试脚本已使用以下固定参数: +- `delivery_id: "TEST"` - 测试运力 +- `biz_id: "test_biz_id"` - 测试商户 +- `service_type: 1` - 服务类型 +- `service_name: "test_service_name"` - 服务名称 +- `openid: "test_admin_openid"` - 管理员openid(需替换为实际值) + +## 快速测试方法 + +### 方法1:使用curl命令测试 + +#### 1. 测试取消订单 +```bash +# 测试取消订单(使用测试环境参数) +curl -X POST "http://localhost:8080/applet/logistics/test/cancelOrder" \ + -H "Content-Type: application/json" \ + -d '{ + "orderId": "test_order_id", + "waybillId": "test_waybill_id", + "deliveryId": "TEST", + "openid": "test_admin_openid", + "cancelMsg": "测试环境取消" + }' +``` + +#### 2. 测试完整流程(下单->取消) +```bash +# 测试完整流程 +curl -X POST "http://localhost:8080/applet/logistics/test/fullTestWithCancel" \ + -H "Content-Type: application/json" \ + -d '{ + "openid": "test_admin_openid", + "deliveryId": "TEST", + "bizId": "test_biz_id", + "cancelMsg": "测试环境取消" + }' +``` + +### 方法2:使用Postman测试 + +#### 1. 导入测试集合 +将以下JSON保存为`wechat_logistics_cancel_test.json`: + +```json +{ + "info": { + "name": "微信物流取消订单测试", + "version": "1.0.0" + }, + "item": [ + { + "name": "取消订单测试", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"orderId\": \"test_order_id\",\n \"waybillId\": \"test_waybill_id\",\n \"deliveryId\": \"TEST\",\n \"openid\": \"test_admin_openid\",\n \"cancelMsg\": \"测试环境取消\"\n}" + }, + "url": { + "raw": "http://localhost:8080/applet/logistics/test/cancelOrder", + "protocol": "http", + "host": ["localhost"], + "port": "8080", + "path": ["applet", "logistics", "test", "cancelOrder"] + } + } + }, + { + "name": "完整流程测试", + "request": { + "method": "POST", + "header": [ + { + "key": "Content-Type", + "value": "application/json" + } + ], + "body": { + "mode": "raw", + "raw": "{\n \"openid\": \"test_admin_openid\",\n \"deliveryId\": \"TEST\",\n \"bizId\": \"test_biz_id\",\n \"cancelMsg\": \"测试环境完整流程\"\n}" + }, + "url": { + "raw": "http://localhost:8080/applet/logistics/test/fullTestWithCancel", + "protocol": "http", + "host": ["localhost"], + "port": "8080", + "path": ["applet", "logistics", "test", "fullTestWithCancel"] + } + } + } + ] +} +``` + +#### 2. 执行测试 +1. 打开Postman +2. 导入上述测试集合 +3. 依次执行测试用例 +4. 检查响应结果 + +### 方法3:使用Swagger UI测试 + +#### 1. 访问Swagger UI +在浏览器中打开:`http://localhost:8080/swagger-ui.html` + +#### 2. 找到测试接口 +在Swagger UI中找到"微信物流测试"分组,包含以下接口: +- `POST /applet/logistics/test/cancelOrder` - 测试取消运单 +- `POST /applet/logistics/test/fullTestWithCancel` - 测试完整流程 + +#### 3. 执行测试 +1. 点击接口展开 +2. 点击"Try it out"按钮 +3. 修改参数(可选) +4. 点击"Execute"执行 +5. 查看响应结果 + +## 测试用例 + +### 测试用例1:基本取消功能 +``` +接口:POST /applet/logistics/test/cancelOrder +参数: +- orderId: "test_order_id" +- waybillId: "test_waybill_id" +- deliveryId: "TEST" +- openid: "test_admin_openid" +- cancelMsg: "测试环境取消" + +预期结果: +- 状态码:200 +- 返回成功响应 +- 包含取消时间 +``` + +### 测试用例2:错误处理 +``` +接口:POST /applet/logistics/test/cancelOrder +参数: +- orderId: "INVALID_ORDER" +- waybillId: "INVALID_WAYBILL" +- deliveryId: "INVALID_DELIVERY" +- openid: "test_admin_openid" +- cancelMsg: "测试错误处理" + +预期结果: +- 状态码:200 +- 返回错误信息 +- 包含具体错误原因 +``` + +### 测试用例3:完整流程 +``` +接口:POST /applet/logistics/test/fullTestWithCancel +参数: +- openid: "test_admin_openid" +- deliveryId: "TEST" +- bizId: "test_biz_id" +- cancelMsg: "测试环境完整流程" + +预期结果: +- 状态码:200 +- 下单成功 +- 取消成功 +- 包含完整流程信息 +``` + +## 结果验证 + +### 成功响应示例 +```json +{ + "success": true, + "message": "操作成功", + "code": 200, + "result": { + "errCode": 0, + "errMsg": "ok", + "deliveryResultCode": 0, + "deliveryResultMsg": "成功", + "orderId": "TEST_ORDER_123", + "waybillId": "SF123456789", + "cancelTime": 1672531200 + } +} +``` + +### 错误响应示例 +```json +{ + "success": false, + "message": "取消运单失败:订单不存在", + "code": 500, + "result": null +} +``` + +## 日志检查 + +### 成功日志 +``` +2024-01-01 10:00:00 INFO WeChatLogisticsService - 取消运单请求:... +2024-01-01 10:00:01 INFO WeChatLogisticsService - 成功取消运单,订单ID:TEST_ORDER_123,运单ID:SF123456789 +``` + +### 错误日志 +``` +2024-01-01 10:00:00 ERROR WeChatLogisticsService - 取消运单失败 +java.lang.RuntimeException: 取消运单失败:订单不存在 +``` + +## 常见问题排查 + +### 1. 连接超时 +**问题**:网络连接超时 +**解决**: +- 检查网络连接 +- 增加超时时间配置 +- 检查防火墙设置 + +### 2. 认证失败 +**问题**:微信认证失败 +**解决**: +- 检查AppId和AppSecret配置 +- 确认微信小程序状态正常 +- 检查access_token获取 + +### 3. 订单不存在 +**问题**:找不到订单 +**解决**: +- 确认订单ID正确 +- 检查运单号是否有效 +- 确认快递公司ID正确 + +### 4. 订单状态不允许取消 +**问题**:订单已发货无法取消 +**解决**: +- 检查订单当前状态 +- 联系快递公司处理 +- 使用其他处理方式 + +## 自动化测试脚本 + +### Bash脚本 +```bash +#!/bin/bash + +# 微信物流取消订单自动化测试脚本 +BASE_URL="http://localhost:8080" +CONTENT_TYPE="Content-Type: application/json" + +echo "开始微信物流取消订单功能测试..." + +# 测试1:基本取消功能 +echo "测试1:基本取消功能" +response1=$(curl -s -X POST "$BASE_URL/applet/logistics/test/cancelOrder" \ + -H "$CONTENT_TYPE" \ + -d '{ + "orderId": "test_order_'$(date +%s)'", + "waybillId": "test_waybill_'$(date +%s)'", + "deliveryId": "TEST", + "openid": "test_admin_openid", + "cancelMsg": "测试环境取消" + }') + +echo "响应1: $response1" +echo "------" + +# 测试2:完整流程 +echo "测试2:完整流程测试" +response2=$(curl -s -X POST "$BASE_URL/applet/logistics/test/fullTestWithCancel" \ + -H "$CONTENT_TYPE" \ + -d '{ + "openid": "test_admin_openid", + "deliveryId": "TEST", + "bizId": "test_biz_id", + "cancelMsg": "测试环境完整流程" + }') + +echo "响应2: $response2" +echo "------" + +echo "测试完成!" +``` + +### Python脚本 +```python +#!/usr/bin/env python3 +import requests +import json +import time + +# 微信物流取消订单自动化测试脚本 +BASE_URL = "http://localhost:8080" +HEADERS = {"Content-Type": "application/json"} + +def test_cancel_order(): + """测试取消订单功能""" + url = f"{BASE_URL}/applet/logistics/test/cancelOrder" + data = { + "orderId": f"test_order_{int(time.time())}", + "waybillId": f"test_waybill_{int(time.time())}", + "deliveryId": "TEST", + "openid": "test_admin_openid", + "cancelMsg": "测试环境取消" + } + + response = requests.post(url, headers=HEADERS, json=data) + print(f"取消订单测试响应: {response.json()}") + return response.json() + +def test_full_process(): + """测试完整流程""" + url = f"{BASE_URL}/applet/logistics/test/fullTestWithCancel" + data = { + "openid": "test_openid", + "deliveryId": "DB", + "bizId": "test_biz_id", + "cancelMsg": "Python完整流程测试" + } + + response = requests.post(url, headers=HEADERS, json=data) + print(f"完整流程测试响应: {response.json()}") + return response.json() + +if __name__ == "__main__": + print("开始微信物流取消订单功能测试...") + + # 执行测试 + result1 = test_cancel_order() + print("------") + result2 = test_full_process() + + print("测试完成!") +``` + +通过以上测试方法,您可以快速验证微信物流取消订单功能是否正常工作。建议按照顺序执行测试,确保每个功能都能正常运行。 \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/测试环境配置说明.md b/module-common/src/main/java/org/jeecg/common/logistics/测试环境配置说明.md new file mode 100644 index 0000000..8780736 --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/测试环境配置说明.md @@ -0,0 +1,168 @@ +# 微信物流测试环境配置说明 + +## ⚠️ 重要提醒 +此环境仅用于测试,请严格遵守以下规则,违规使用将导致测试权限被取消。 + +## 使用限制 + +### 1. 用途限制 +- ✅ **仅限测试下单功能** +- ❌ **禁止用于其它用途** +- ❌ **禁止用于生产环境** + +### 2. 用户限制 +- ✅ **仅限小程序管理员** +- ✅ **仅限小程序运营者** +- ✅ **仅限小程序开发者** +- ❌ **禁止使用普通用户的openid** + +### 3. 调用限制 +- ⏰ **每天最多10次调用** +- ⏰ **超过限制将被拒绝** +- ⏰ **请合理安排测试时间** + +## 测试参数配置 + +### 必须使用的固定参数 +```yaml +测试运力: + delivery_id: "TEST" + +测试商户: + biz_id: "test_biz_id" + +服务类型: + service_type: 1 + service_name: "test_service_name" + +用户标识: + openid: "test_admin_openid" # 请替换为实际的管理员openid +``` + +### 不允许修改的参数 +- `delivery_id` 必须为 `"TEST"` +- `biz_id` 必须为 `"test_biz_id"` +- `service_type` 必须为 `1` +- `service_name` 必须为 `"test_service_name"` + +## 测试流程 + +### 标准测试流程 +1. **下单测试** - 创建测试订单 +2. **查询测试** - 查询订单状态和轨迹 +3. **取消测试** - 取消测试订单 +4. **模拟更新** - 调用模拟接口更新订单状态 + +### 模拟更新功能 +测试环境支持模拟订单状态变更: +- 📦 **揽件通知** - 模拟快递员揽件 +- 🚚 **派送通知** - 模拟配送中状态 +- ✅ **完成通知** - 模拟配送完成 + +## API接口 + +### 测试接口列表 +| 接口路径 | 方法 | 说明 | +|----------|------|------| +| `/applet/logistics/test/addOrder` | POST | 测试下单 | +| `/applet/logistics/test/getPath` | POST | 测试查询轨迹 | +| `/applet/logistics/test/cancelOrder` | POST | 测试取消订单 | +| `/applet/logistics/test/fullTest` | POST | 完整流程测试 | +| `/applet/logistics/test/fullTestWithCancel` | POST | 完整流程测试(含取消) | + +### 默认测试参数 +```json +{ + "openid": "test_admin_openid", + "delivery_id": "TEST", + "biz_id": "test_biz_id", + "service_type": 1, + "service_name": "test_service_name" +} +``` + +## 快速开始 + +### 1. 使用Swagger测试 +``` +1. 访问:http://localhost:8080/swagger-ui.html +2. 找到:"微信物流测试"分组 +3. 选择测试接口 +4. 点击"Try it out" +5. 确认参数正确 +6. 点击"Execute"执行 +``` + +### 2. 使用curl测试 +```bash +# 测试下单 +curl -X POST "http://localhost:8080/applet/logistics/test/addOrder" \ + -H "Content-Type: application/json" \ + -d '{ + "openid": "test_admin_openid", + "deliveryId": "TEST", + "bizId": "test_biz_id" + }' +``` + +### 3. 使用Postman测试 +导入测试集合,所有参数已预配置为测试环境标准值。 + +## 注意事项 + +### ✅ 正确的测试方式 +- 使用管理员openid +- 使用固定的测试参数 +- 控制调用频率 +- 测试完整流程 + +### ❌ 错误的测试方式 +- 使用普通用户openid +- 修改测试参数 +- 频繁调用接口 +- 仅测试单个功能 + +### ⚠️ 常见错误 +| 错误信息 | 原因 | 解决方案 | +|----------|------|----------| +| "参数错误" | 使用了非测试参数 | 检查并使用正确的测试参数 | +| "调用超限" | 超过每日10次限制 | 等待次日重置或联系管理员 | +| "用户权限不足" | 使用了非管理员openid | 使用管理员/运营者/开发者openid | +| "订单不存在" | 查询了不存在的订单 | 确认订单ID正确 | + +## 测试建议 + +### 1. 测试计划 +- 📅 **制定测试计划**:合理安排10次调用 +- 📝 **记录测试结果**:保存测试日志 +- 🔄 **完整测试流程**:下单->查询->取消 + +### 2. 测试数据 +- 📦 **使用标准地址**:测试地址库中的地址 +- 📱 **使用测试手机**:避免骚扰真实用户 +- 💰 **使用小额保价**:测试保价功能 + +### 3. 错误处理 +- 🐛 **记录错误信息**:保存错误日志 +- 🔍 **分析错误原因**:检查参数和配置 +- 📞 **及时反馈问题**:联系技术支持 + +## 技术支持 + +如果在测试过程中遇到问题: +1. 检查是否严格按照测试环境要求配置 +2. 查看错误日志和返回信息 +3. 确认未超过调用限制 +4. 联系技术支持获取帮助 + +## 升级说明 + +测试完成后,如需升级到生产环境: +1. 申请生产环境权限 +2. 配置真实的快递公司参数 +3. 使用真实的商户信息 +4. 遵守生产环境的使用规范 + +--- + +**再次提醒:严格遵守测试环境规则,确保测试的有效性和合规性!** \ No newline at end of file diff --git a/module-common/src/main/java/org/jeecg/common/logistics/问题解决方案.md b/module-common/src/main/java/org/jeecg/common/logistics/问题解决方案.md new file mode 100644 index 0000000..c911e77 --- /dev/null +++ b/module-common/src/main/java/org/jeecg/common/logistics/问题解决方案.md @@ -0,0 +1,212 @@ +# 微信物流取消订单问题解决方案 + +## 🔍 问题诊断 + +### 1. "order not exists" 错误 +**错误信息**: `取消运单失败:order not exists rid: 68691313-48c27f21-7ac12929` + +**原因分析**: +- 使用了虚拟测试数据:`orderId=test_order_id`, `waybillId=test_waybill_id` +- 这些订单在微信物流系统中并不存在 +- 根据微信官方API,取消订单需要提供**真实存在的订单ID和运单ID** + +### 2. HTTP 404错误 +**错误信息**: `HTTP Status 404 – Not Found` + +**原因分析**: +- 应用设置了上下文路径:`context-path: /recycle-admin` +- 之前使用的接口路径缺少上下文路径前缀 + +## ✅ 正确的解决方案 + +### 1. 正确的接口路径 +应用运行在:`http://localhost:8002/recycle-admin` + +正确的测试接口路径: +``` +- 下单: POST http://localhost:8002/recycle-admin/applet/logistics/test/addOrder +- 查询: POST http://localhost:8002/recycle-admin/applet/logistics/test/getPath +- 取消: POST http://localhost:8002/recycle-admin/applet/logistics/test/cancelOrder +- 完整流程: POST http://localhost:8002/recycle-admin/applet/logistics/test/fullTestWithCancel +``` + +### 2. 正确的测试流程 + +#### 方法1:完整流程测试(推荐) +```powershell +# 使用完整流程测试,先下单再取消 +Invoke-WebRequest -Uri "http://localhost:8002/recycle-admin/applet/logistics/test/fullTestWithCancel" ` + -Method POST ` + -Headers @{"Content-Type"="application/json"} ` + -Body '{"openid": "test_admin_openid", "deliveryId": "TEST", "bizId": "test_biz_id", "cancelMsg": "测试环境取消"}' +``` + +#### 方法2:分步测试 +```powershell +# 步骤1:先下单 +$orderResponse = Invoke-WebRequest -Uri "http://localhost:8002/recycle-admin/applet/logistics/test/addOrder" ` + -Method POST ` + -Headers @{"Content-Type"="application/json"} ` + -Body '{"openid": "test_admin_openid", "deliveryId": "TEST", "bizId": "test_biz_id"}' + +# 从响应中提取订单ID和运单ID +$orderData = $orderResponse.Content | ConvertFrom-Json +$orderId = $orderData.result.orderId +$waybillId = $orderData.result.waybillId + +# 步骤2:使用真实订单ID取消订单 +Invoke-WebRequest -Uri "http://localhost:8002/recycle-admin/applet/logistics/test/cancelOrder" ` + -Method POST ` + -Headers @{"Content-Type"="application/json"} ` + -Body "{'orderId': '$orderId', 'waybillId': '$waybillId', 'deliveryId': 'TEST', 'openid': 'test_admin_openid', 'cancelMsg': '测试取消'}" +``` + +### 3. 使用浏览器测试(最简单) + +访问Swagger UI:`http://localhost:8002/recycle-admin/swagger-ui.html` + +1. 找到"微信物流测试"分组 +2. 选择"测试完整流程(包含取消)"接口 +3. 点击"Try it out" +4. 点击"Execute"执行 + +## 📋 测试环境参数说明 + +### 必须使用的固定参数 +```json +{ + "openid": "test_admin_openid", // 需替换为实际管理员openid + "deliveryId": "TEST", // 测试运力(固定值) + "bizId": "test_biz_id", // 测试商户(固定值) + "cancelMsg": "测试环境取消" // 取消原因 +} +``` + +### ⚠️ 重要提醒 +1. **每天最多10次调用** - 请合理安排测试 +2. **仅限管理员openid** - `test_admin_openid`需替换为实际值 +3. **使用固定测试参数** - 不要修改`TEST`和`test_biz_id` +4. **测试流程** - 必须先下单再取消,不能取消不存在的订单 + +## 🧪 快速验证脚本 + +### PowerShell脚本 +```powershell +# 微信物流取消订单测试脚本 +$baseUrl = "http://localhost:8002/recycle-admin" +$headers = @{"Content-Type"="application/json"} + +Write-Host "开始微信物流取消订单测试..." -ForegroundColor Green + +try { + # 测试完整流程(下单 -> 取消) + Write-Host "执行完整流程测试..." -ForegroundColor Yellow + + $response = Invoke-WebRequest -Uri "$baseUrl/applet/logistics/test/fullTestWithCancel" ` + -Method POST ` + -Headers $headers ` + -Body '{"openid": "test_admin_openid", "deliveryId": "TEST", "bizId": "test_biz_id", "cancelMsg": "PowerShell测试"}' + + $result = $response.Content | ConvertFrom-Json + + if ($result.success) { + Write-Host "✅ 测试成功!" -ForegroundColor Green + Write-Host "订单ID: $($result.result.orderResponse.orderId)" -ForegroundColor Cyan + Write-Host "运单ID: $($result.result.orderResponse.waybillId)" -ForegroundColor Cyan + + if ($result.result.cancelResponse) { + Write-Host "✅ 取消成功!" -ForegroundColor Green + Write-Host "取消时间: $($result.result.cancelResponse.cancelTime)" -ForegroundColor Cyan + } + } else { + Write-Host "❌ 测试失败: $($result.message)" -ForegroundColor Red + } +} catch { + Write-Host "❌ 请求失败: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "请检查:" -ForegroundColor Yellow + Write-Host "1. 应用是否在 http://localhost:8002/recycle-admin 运行" -ForegroundColor White + Write-Host "2. 微信配置是否正确" -ForegroundColor White + Write-Host "3. 网络连接是否正常" -ForegroundColor White +} + +Write-Host "测试完成!" -ForegroundColor Green +``` + +### 保存并运行脚本 +1. 将上述脚本保存为 `test-logistics.ps1` +2. 在PowerShell中运行:`.\test-logistics.ps1` + +## 🐛 常见问题排查 + +### 1. 访问被拒绝 +**问题**: HTTP 403或认证失败 +**解决**: +- 检查微信AppId和AppSecret配置 +- 确认使用管理员openid +- 验证测试环境权限 + +### 2. 参数错误 +**问题**: 参数验证失败 +**解决**: +- 确保使用固定测试参数:`deliveryId=TEST`, `bizId=test_biz_id` +- 检查JSON格式是否正确 +- 确认必填字段都已提供 + +### 3. 连接超时 +**问题**: 网络连接超时 +**解决**: +- 检查应用是否正常运行 +- 验证端口8002是否开放 +- 确认防火墙设置 + +### 4. 订单状态错误 +**问题**: 订单状态不允许取消 +**解决**: +- 在下单后立即取消(建议5分钟内) +- 检查订单当前状态 +- 使用完整流程测试确保订单存在 + +## 📖 微信官方API参考 + +根据微信官方文档,取消订单接口要求: + +**接口地址**: `POST https://api.weixin.qq.com/cgi-bin/express/business/order/cancel?access_token=ACCESS_TOKEN` + +**必填参数**: +- `order_id`: 订单ID(必须是真实存在的) +- `delivery_id`: 快递公司ID +- `waybill_id`: 运单ID(必须是真实存在的) +- `access_token`: 接口调用凭证 + +**可选参数**: +- `openid`: 用户openid + +## 🎯 成功测试的标志 + +测试成功时,您应该看到类似以下的响应: + +```json +{ + "success": true, + "message": "操作成功", + "code": 200, + "result": { + "orderResponse": { + "orderId": "TEST_ORDER_1234567890", + "waybillId": "TEST_WAYBILL_123", + "errCode": 0, + "errMsg": "ok" + }, + "cancelResponse": { + "errCode": 0, + "errMsg": "ok", + "orderId": "TEST_ORDER_1234567890", + "waybillId": "TEST_WAYBILL_123", + "cancelTime": 1672531200 + }, + "message": "完整流程测试成功(包含取消)" + } +} +``` + +现在您可以使用正确的路径和方法来测试微信物流取消订单功能了! \ No newline at end of file diff --git a/test-logistics.ps1 b/test-logistics.ps1 new file mode 100644 index 0000000..3faf8fe --- /dev/null +++ b/test-logistics.ps1 @@ -0,0 +1,154 @@ +# 微信物流取消订单测试脚本 +# 使用正确的上下文路径:/recycle-admin + +param( + [string]$openid = "test_admin_openid", + [string]$deliveryId = "TEST", + [string]$bizId = "test_biz_id", + [string]$cancelMsg = "PowerShell自动化测试" +) + +$baseUrl = "http://localhost:8002/recycle-admin" +$headers = @{"Content-Type"="application/json"} + +Write-Host "=" * 60 -ForegroundColor Green +Write-Host " 微信物流取消订单测试脚本" -ForegroundColor Green +Write-Host "=" * 60 -ForegroundColor Green +Write-Host "" + +Write-Host "测试配置:" -ForegroundColor Yellow +Write-Host "- 服务地址: $baseUrl" -ForegroundColor White +Write-Host "- OpenID: $openid" -ForegroundColor White +Write-Host "- 快递公司: $deliveryId" -ForegroundColor White +Write-Host "- 商户ID: $bizId" -ForegroundColor White +Write-Host "- 取消原因: $cancelMsg" -ForegroundColor White +Write-Host "" + +# 检查服务是否可用 +Write-Host "检查服务状态..." -ForegroundColor Yellow +try { + $healthCheck = Invoke-WebRequest -Uri "$baseUrl/actuator/health" -Method GET -TimeoutSec 5 + Write-Host "✅ 服务正常运行" -ForegroundColor Green +} catch { + Write-Host "⚠️ 无法访问健康检查端点,继续测试..." -ForegroundColor Yellow +} + +Write-Host "" +Write-Host "开始执行完整流程测试(下单 -> 取消)..." -ForegroundColor Yellow +Write-Host "" + +try { + # 构建请求体 + $requestBody = @{ + openid = $openid + deliveryId = $deliveryId + bizId = $bizId + cancelMsg = $cancelMsg + } | ConvertTo-Json + + Write-Host "发送请求到: $baseUrl/applet/logistics/test/fullTestWithCancel" -ForegroundColor Cyan + Write-Host "请求数据: $requestBody" -ForegroundColor Gray + Write-Host "" + + # 发送请求 + $response = Invoke-WebRequest -Uri "$baseUrl/applet/logistics/test/fullTestWithCancel" ` + -Method POST ` + -Headers $headers ` + -Body $requestBody ` + -TimeoutSec 30 + + # 解析响应 + $result = $response.Content | ConvertFrom-Json + + Write-Host "响应状态码: $($response.StatusCode)" -ForegroundColor Cyan + Write-Host "" + + if ($result.success) { + Write-Host "🎉 测试成功!" -ForegroundColor Green + Write-Host "" + + # 显示下单结果 + if ($result.result.orderResponse) { + Write-Host "📦 下单结果:" -ForegroundColor Yellow + Write-Host " 订单ID: $($result.result.orderResponse.orderId)" -ForegroundColor Cyan + Write-Host " 运单ID: $($result.result.orderResponse.waybillId)" -ForegroundColor Cyan + Write-Host " 错误码: $($result.result.orderResponse.errCode)" -ForegroundColor Cyan + Write-Host " 错误信息: $($result.result.orderResponse.errMsg)" -ForegroundColor Cyan + + if ($result.result.orderResponse.waybillData) { + Write-Host " 运单数据: 已生成" -ForegroundColor Cyan + } + Write-Host "" + } + + # 显示取消结果 + if ($result.result.cancelResponse) { + Write-Host "❌ 取消结果:" -ForegroundColor Yellow + Write-Host " 错误码: $($result.result.cancelResponse.errCode)" -ForegroundColor Cyan + Write-Host " 错误信息: $($result.result.cancelResponse.errMsg)" -ForegroundColor Cyan + + if ($result.result.cancelResponse.cancelTime) { + $cancelTime = [DateTimeOffset]::FromUnixTimeSeconds($result.result.cancelResponse.cancelTime).ToString("yyyy-MM-dd HH:mm:ss") + Write-Host " 取消时间: $cancelTime" -ForegroundColor Cyan + } + Write-Host "" + } + + # 显示总结信息 + Write-Host "📋 测试总结:" -ForegroundColor Yellow + Write-Host " $($result.result.message)" -ForegroundColor White + Write-Host "" + + } else { + Write-Host "❌ 测试失败!" -ForegroundColor Red + Write-Host "错误信息: $($result.message)" -ForegroundColor Red + + if ($result.result) { + Write-Host "详细信息: $($result.result)" -ForegroundColor Red + } + Write-Host "" + } + +} catch { + Write-Host "❌ 请求失败!" -ForegroundColor Red + Write-Host "错误信息: $($_.Exception.Message)" -ForegroundColor Red + Write-Host "" + + # 提供排查建议 + Write-Host "🔍 排查建议:" -ForegroundColor Yellow + Write-Host "1. 检查应用是否在 $baseUrl 运行" -ForegroundColor White + Write-Host "2. 检查微信配置是否正确" -ForegroundColor White + Write-Host "3. 确认网络连接正常" -ForegroundColor White + Write-Host "4. 验证测试环境权限" -ForegroundColor White + Write-Host "" + + # 显示详细错误信息 + if ($_.Exception.Response) { + Write-Host "响应状态码: $($_.Exception.Response.StatusCode)" -ForegroundColor Red + Write-Host "响应状态: $($_.Exception.Response.StatusDescription)" -ForegroundColor Red + } + Write-Host "" +} + +Write-Host "=" * 60 -ForegroundColor Green +Write-Host " 测试完成!" -ForegroundColor Green +Write-Host "=" * 60 -ForegroundColor Green +Write-Host "" + +# 提供后续操作建议 +Write-Host "💡 后续操作建议:" -ForegroundColor Yellow +Write-Host "1. 如果测试成功,可以在实际应用中使用相同的逻辑" -ForegroundColor White +Write-Host "2. 记住每天最多只能调用10次,请合理安排测试" -ForegroundColor White +Write-Host "3. 在生产环境中,请替换为实际的openid和配置" -ForegroundColor White +Write-Host "4. 可以通过Swagger UI进行更详细的测试: $baseUrl/swagger-ui.html" -ForegroundColor White +Write-Host "" + +# 参数说明 +Write-Host "📖 脚本参数说明:" -ForegroundColor Yellow +Write-Host "使用示例:" -ForegroundColor White +Write-Host " .\test-logistics.ps1" -ForegroundColor Gray +Write-Host " .\test-logistics.ps1 -openid 'your_openid' -cancelMsg '自定义取消原因'" -ForegroundColor Gray +Write-Host "" + +Write-Host "按任意键退出..." -ForegroundColor Green +$host.UI.RawUI.ReadKey("NoEcho,IncludeKeyDown") | Out-Null \ No newline at end of file