收藏
回答

生成运单 接口生成云单后无法在快递管家中查询到?

我使用 生成运单接口生成运单后
https://api.weixin.qq.com/cgi-bin/express/business/order/add?access_token=ACCESS_TOKEN

1: 该运单无法在快递管家中查询到 https://vip.zto.com/login

73588021027366

2:我找到我们签约的中通快递员,说明运单号,他们的系统可以查询到

3:但是无法在无法在快递管家中查询到 https://vip.zto.com/login

4:网点面单也确认了是同一个

分别是 java中的配置,快递员查询到的信息,快递管家中的面单信息,小程序物流助手配置


所以这到底是为什么?

```java
@PostMapping("/create-order/{orderId}")
public ReturnBean> createLogisticsOrder(
        @PathVariable("orderId") Long orderId,
        @RequestBody(required = false) Map shopData) {
    
    log.info("创建物流订单请求,orderId={}", orderId);


    // 步骤1:验证订单状态
    AccountsOrderEntity order = accountsOrderService.getById(orderId);
    if (order == null) {
        throw SystemException.error(ErrorCodeEnum.DATA_NOT_FOUND, "订单不存在");
    }


    if (!"pending_shipment".equals(order.getStatus())) {
        throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                "只有待发货的订单才能创建物流订单,当前状态: " + order.getStatus());
    }


    // 步骤2:检查是否已有物流订单
    LambdaQueryWrapper logisticsWrapper = new LambdaQueryWrapper<>();
    logisticsWrapper.eq(LogisticsOrderEntity::getOrderId, orderId);
    LogisticsOrderEntity existingLogisticsOrder = logisticsOrderService.getOne(logisticsWrapper);
    
    if (existingLogisticsOrder != null) {
        // 如果运单已取消,清除Redis缓存并允许重新创建
        if ("cancelled".equals(existingLogisticsOrder.getStatus())) {
            log.warn("订单已存在但运单已取消,清除缓存并允许重新创建,orderId={}, waybillId={}", 
                    orderId, existingLogisticsOrder.getWaybillId());
            String cacheKey = LOGISTICS_REDIS_ADD_ORDER + orderId;
            try {
                RedisUtil.delete(cacheKey);
                log.info("已清除已取消运单的Redis缓存,orderId={}", orderId);
            } catch (Exception e) {
                log.warn("清除Redis缓存失败,但不影响业务,orderId={}, error={}", orderId, e.getMessage());
            }
            // 继续执行,允许重新创建物流订单
        } else {
            // 运单未取消,返回现有运单信息
            log.info("订单已存在物流订单,orderId={}, waybillId={}", orderId, existingLogisticsOrder.getWaybillId());
            Map result = new HashMap<>();
            result.put("waybill_id", existingLogisticsOrder.getWaybillId());
            result.put("company_name", existingLogisticsOrder.getCompanyName());
            result.put("status", existingLogisticsOrder.getStatus());
            result.put("is_existing", true);
            return ReturnBean.success("该订单已存在物流订单", result);
        }
    }


    // 步骤3:验证收货信息
    List missingFields = new ArrayList<>();
    if (StringUtils.isBlank(order.getShippingName())) {
        missingFields.add("收货人姓名");
    }
    if (StringUtils.isBlank(order.getShippingPhone())) {
        missingFields.add("收货人电话");
    }
    if (StringUtils.isBlank(order.getShippingAddress())) {
        missingFields.add("收货地址");
    }


    if (!missingFields.isEmpty()) {
        throw SystemException.error(ErrorCodeEnum.PARAM_NOT_NULL, 
                "订单缺少以下信息:" + String.join("、", missingFields));
    }


    // 步骤4:获取用户OpenID
    String openid = getUserOpenid(order);


    if (StringUtils.isBlank(openid)) {
        throw SystemException.error(ErrorCodeEnum.DATA_NOT_FOUND, 
                "无法获取用户OpenID,请确保订单已支付");
    }


    // 步骤5:查询订单商品项
    LambdaQueryWrapper itemWrapper = new LambdaQueryWrapper<>();
    itemWrapper.eq(AccountsOrderitemEntity::getOrderId, orderId);
    List orderItems = accountsOrderitemService.list(itemWrapper);


    if (orderItems == null || orderItems.isEmpty()) {
        throw SystemException.error(ErrorCodeEnum.DATA_NOT_FOUND, "订单商品项为空");
    }


    // 步骤6:构建物流订单数据
    Map logisticsData = buildLogisticsOrderData(order, orderItems, openid, shopData);


    // 步骤7:检查Redis缓存,处理可能的异常情况(微信已创建但数据库未保存)
    String cacheKey = LOGISTICS_REDIS_ADD_ORDER + orderId;
    String cachedResult = RedisUtil.get(cacheKey);
    
    Map createResult;
    boolean isFromCache = false;
    
    if (StringUtils.isNotBlank(cachedResult)) {
        // Redis中有缓存,说明之前可能调用过微信API但数据库保存失败
        log.warn("检测到Redis缓存,orderId={},可能之前创建过但数据库保存失败,尝试恢复", orderId);
        
        try {
            @SuppressWarnings("unchecked")
            Map parsedResult = JSON.parseObject(cachedResult, Map.class);
            
            // 检查缓存中的运单号是否已在数据库中存在
            String cachedWaybillId = (String) parsedResult.get("waybill_id");
            if (StringUtils.isNotBlank(cachedWaybillId)) {
                LambdaQueryWrapper waybillWrapper = new LambdaQueryWrapper<>();
                waybillWrapper.eq(LogisticsOrderEntity::getWaybillId, cachedWaybillId);
                LogisticsOrderEntity existingByWaybill = logisticsOrderService.getOne(waybillWrapper);
                
                if (existingByWaybill != null) {
                    // 检查运单状态:如果已取消,清除缓存并重新创建
                    if ("cancelled".equals(existingByWaybill.getStatus())) {
                        log.warn("检测到缓存中的运单已取消,清除缓存并重新创建,orderId={}, waybillId={}", 
                                orderId, cachedWaybillId);
                        RedisUtil.delete(cacheKey);
                        // 继续执行,重新创建物流订单
                        createResult = weChatLogisticsService.createOrder(logisticsData);
                    } else {
                        // 数据库已存在该运单号且未取消,说明数据已恢复,直接返回
                        log.info("数据库已存在该运单号,数据已恢复,orderId={}, waybillId={}", orderId, cachedWaybillId);
                        Map result = new HashMap<>();
                        result.put("waybill_id", existingByWaybill.getWaybillId());
                        result.put("company_name", existingByWaybill.getCompanyName());
                        result.put("status", existingByWaybill.getStatus());
                        result.put("is_existing", true);
                        result.put("is_recovered", true);
                        return ReturnBean.success("该订单已存在物流订单(已恢复)", result);
                    }
                } else {
                    // 数据库不存在,但缓存中有数据,可能是之前创建失败,使用缓存继续处理
                    createResult = parsedResult;
                    isFromCache = true;
                    log.info("使用Redis缓存数据继续处理,orderId={}, waybillId={}", orderId, cachedWaybillId);
                }
            } else {
                // 缓存中没有运单号,删除无效缓存并重新创建
                log.warn("Redis缓存中没有运单号,删除无效缓存并重新创建,orderId={}", orderId);
                RedisUtil.delete(cacheKey);
                createResult = weChatLogisticsService.createOrder(logisticsData);
            }
            
        } catch (Exception e) {
            // 缓存数据解析失败,说明缓存可能损坏,删除缓存并重新创建
            log.error("解析Redis缓存数据失败,删除损坏的缓存并重新创建,orderId={}, error={}", orderId, e.getMessage(), e);
            RedisUtil.delete(cacheKey);
            createResult = weChatLogisticsService.createOrder(logisticsData);
        }
    } else {
        // Redis中没有缓存,说明是正常的新订单,可以安全创建
        log.info("Redis无缓存,正常创建物流订单,orderId={}", orderId);
        createResult = weChatLogisticsService.createOrder(logisticsData);
    }


    // 步骤8:如果调用微信API成功,立即保存到Redis(4天过期,预留bug修复时间)
    if (Boolean.TRUE.equals(createResult.get("success"))) {
        try {
            String resultJson = JSON.toJSONString(createResult);
            RedisUtil.saveString(cacheKey, resultJson, LOGISTICS_CACHE_EXPIRE_TIME_BUG_FIX);
            log.info("微信API调用成功,结果已保存到Redis缓存(4天),orderId={}, waybillId={}", 
                    orderId, createResult.get("waybill_id"));
        } catch (Exception e) {
            log.warn("保存物流订单创建结果到Redis失败,但不影响业务,orderId={}, error={}", orderId, e.getMessage());
        }
    } else {
        // API调用失败,抛出异常
        throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                "创建物流订单失败: " + createResult.get("error"));
    }


    // 步骤9:保存物流订单到数据库
    String waybillId = (String) createResult.get("waybill_id");
    String companyCode = (String) createResult.get("company_code");
    String companyName = (String) createResult.get("company_name");


    LogisticsOrderEntity logisticsOrder = new LogisticsOrderEntity();
    logisticsOrder.setOrderId(orderId);
    logisticsOrder.setWaybillId(waybillId);
    logisticsOrder.setCompanyCode(companyCode);
    logisticsOrder.setCompanyName(companyName);
    logisticsOrder.setStatus("created");
    logisticsOrder.setWaybillData(JSON.toJSONString(createResult));
    logisticsOrder.setCreatedAt(LocalDateTime.now());
    // 设置物流费用字段(数据库字段不允许为NULL,必须设置默认值)
    logisticsOrder.setLogisticsFee(BigDecimal.ZERO); // 实际费用,创建时默认为0
    logisticsOrder.setEstimatedFee(BigDecimal.ZERO); // 预估费用,创建时默认为0
    // 设置错误信息字段(数据库字段不允许为NULL,必须设置默认值)
    logisticsOrder.setErrorMessage(""); // 错误信息,创建时默认为空字符串
    
    try {
        logisticsOrderService.save(logisticsOrder);
    } catch (Exception e) {
        // 数据库保存失败,但微信已创建,Redis中已有4天缓存,可以后续恢复
        log.error("数据库保存失败,但微信已创建订单,Redis缓存保留4天用于恢复,orderId={}, waybillId={}, error={}", 
                orderId, waybillId, e.getMessage(), e);
        throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                "物流订单在微信中已创建,但数据库保存失败,请稍后重试或联系管理员恢复。运单号: " + waybillId);
    }


    // 步骤10:更新订单状态和物流信息
    order.setLogisticsCompany(companyName);
    order.setLogisticsNumber(waybillId);
    order.setLogisticsStatus("created");
    order.setStatus("shipped");
    order.setShippedAt(LocalDateTime.now());
    order.setUpdatedAt(LocalDateTime.now());
    
    try {
        accountsOrderService.updateById(order);
    } catch (Exception e) {
        // 订单更新失败,但物流订单已保存,记录警告
        log.warn("订单状态更新失败,但物流订单已保存,orderId={}, waybillId={}, error={}", 
                orderId, waybillId, e.getMessage());
    }


    // 步骤11:数据库保存成功,将Redis缓存过期时间改为30分钟(或删除)
    try {
        // 方案1:缩短过期时间为30分钟(推荐,保留短期缓存提升性能)
        String resultJson = JSON.toJSONString(createResult);
        RedisUtil.saveString(cacheKey, resultJson, LOGISTICS_CACHE_EXPIRE_TIME_NORMAL);
        log.info("数据库保存成功,Redis缓存过期时间已更新为30分钟,orderId={}, waybillId={}", orderId, waybillId);
    } catch (Exception e) {
        log.warn("更新Redis缓存过期时间失败,但不影响业务,orderId={}, error={}", orderId, e.getMessage());
    }


    log.info("物流订单创建成功,orderId={}, waybillId={}, isFromCache={}", orderId, waybillId, isFromCache);


    Map result = new HashMap<>();
    result.put("logistics_order_id", logisticsOrder.getId());
    result.put("waybill_id", waybillId);
    result.put("company_name", companyName);
    result.put("status", "created");


    return ReturnBean.success("物流订单创建成功", result);
}
```


---


## 调用的私有方法


### 2. getUserOpenid - 获取用户OpenID


**位置**: `org.example.huolingjava.logistics.controller.LogisticsController`


```java
/**
 * 获取用户OpenID
 * 优先从payment_transactions表获取,其次从订单表获取
 */
private String getUserOpenid(AccountsOrderEntity order) {
    // 1. 优先从payment_transactions表获取(微信官方数据)
    if (order.getPaymentOrderId() != null) {
        PaymentOrdersEntity paymentOrder = paymentOrdersService.getById(order.getPaymentOrderId());
        if (paymentOrder != null && StringUtils.isNotBlank(paymentOrder.getOpenid())) {
            return paymentOrder.getOpenid();
        }
    }


    // 2. 从payment_transactions表查询(根据订单号)
    if (StringUtils.isNotBlank(order.getOrderNumber())) {
        LambdaQueryWrapper transactionWrapper = new LambdaQueryWrapper<>();
        transactionWrapper.like(PaymentTransactionsEntity::getOutTradeNo, order.getOrderNumber())
                .orderByDesc(PaymentTransactionsEntity::getCreatedTime)
                .last("LIMIT 1");
        PaymentTransactionsEntity transaction = paymentTransactionsService.getOne(transactionWrapper);
        if (transaction != null && StringUtils.isNotBlank(transaction.getOpenid())) {
            return transaction.getOpenid();
        }
    }


    // 3. 使用订单保存的信息
    if (StringUtils.isNotBlank(order.getOrderOpenid())) {
        return order.getOrderOpenid();
    }
    if (StringUtils.isNotBlank(order.getPaymentOpenid())) {
        return order.getPaymentOpenid();
    }


    return null;
}
```


### 3. buildLogisticsOrderData - 构建物流订单数据


**位置**: `org.example.huolingjava.logistics.controller.LogisticsController`


```java
/**
 * 构建物流订单数据
 */
private Map buildLogisticsOrderData(
        AccountsOrderEntity order,
        List orderItems,
        String openid,
        Map shopData) {
    
    Map logisticsData = new HashMap<>();
    logisticsData.put("orderId", order.getId());
    logisticsData.put("orderNumber", order.getOrderNumber());
    logisticsData.put("customerName", order.getShippingName());
    logisticsData.put("customerPhone", order.getShippingPhone());
    logisticsData.put("customerAddress", order.getShippingAddress());
    logisticsData.put("openid", openid);
    logisticsData.put("totalAmount", order.getActualAmount());


    // 构建商品列表
    List> products = new ArrayList<>();
    for (AccountsOrderitemEntity item : orderItems) {
        Map product = new HashMap<>();
        product.put("id", item.getProductId());
        product.put("name", item.getProductName());
        product.put("quantity", item.getQuantity());
        product.put("category", item.getProductCategory());
        products.add(product);
    }
    logisticsData.put("products", products);


    // 计算总重量(简化处理,默认0.1kg)
    logisticsData.put("totalWeight", 0.1);


    // 店铺信息(如果有)
    if (shopData != null) {
        logisticsData.put("shop", shopData);
    }


    return logisticsData;
}
```


---


## 调用的Service方法


### 4. WeChatLogisticsService.createOrder - 创建物流订单(微信API)


**位置**: `org.example.huolingjava.logistics.service.WeChatLogisticsService`


```java
/**
 * 创建物流订单
 * 
 * @param orderData 订单数据,包含:
 *                  - orderId: 订单ID
 *                  - orderNumber: 订单号
 *                  - customerName: 收货人姓名
 *                  - customerPhone: 收货人电话
 *                  - customerAddress: 收货地址
 *                  - openid: 用户微信OpenID
 *                  - products: 商品列表
 *                  - totalWeight: 总重量(kg)
 *                  - totalAmount: 订单总金额(元)
 * @return 创建结果,包含waybill_id、company_code、company_name等
 */
public Map createOrder(Map orderData) {
    if (orderData == null) {
        throw SystemException.error(ErrorCodeEnum.PARAM_NOT_NULL, "订单数据不能为空");
    }


    // 获取access_token
    String accessToken = getAccessToken();


    // 构建请求数据
    Map requestData = buildCreateOrderRequest(orderData);


    // 调用微信API(使用官方新接口路径)
    // 官方文档:https://developers.weixin.qq.com/miniprogram/dev/OpenApiDoc/express/express-by-business/addOrder.html
    String url = properties.getBaseUrl() + "/order/add";
    url += "?access_token=" + accessToken;


    try {
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.set("User-Agent", "HuolingMP/1.0");


        HttpEntity requestEntity = new HttpEntity<>(
                JSON.toJSONString(requestData), headers);


        ResponseEntity response = restTemplate.postForEntity(
                url, requestEntity, String.class);


        JSONObject result = JSON.parseObject(response.getBody());


        if (result.getIntValue("errcode") == 0) {
            String waybillId = result.getString("waybill_id");
            String orderId = result.getString("order_id");


            log.info("创建物流订单成功,waybillId={}, orderId={}", waybillId, orderId);


            Map responseData = new HashMap<>();
            responseData.put("success", true);
            responseData.put("waybill_id", waybillId);
            responseData.put("company_code", properties.getZtoCompanyCode());
            responseData.put("company_name", properties.getZtoCompanyName());
            responseData.put("order_id", orderId);
            return responseData;
        } else {
            int errcode = result.getIntValue("errcode");
            String errmsg = result.getString("errmsg");
            log.error("创建物流订单失败,errcode={}, errmsg={}", errcode, errmsg);
            
            // 处理特殊错误码
            if (errcode == 40066) {
                throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                        "物流订单创建失败:回调URL未在微信后台配置。回调URL: " + properties.getCallbackUrl());
            }
            
            throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                    "创建物流订单失败: " + errmsg);
        }
    } catch (SystemException e) {
        throw e;
    } catch (Exception e) {
        log.error("创建物流订单异常", e);
        throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                "创建物流订单异常: " + e.getMessage());
    }
}
```


### 5. WeChatLogisticsService.getAccessToken - 获取微信access_token


**位置**: `org.example.huolingjava.logistics.service.WeChatLogisticsService`


```java
/**
 * 获取微信access_token
 * 
 * @return access_token字符串
 */
public String getAccessToken() {
    String url = "https://api.weixin.qq.com/cgi-bin/token";
    
    Map params = new HashMap<>();
    params.put("grant_type", "client_credential");
    params.put("appid", properties.getAppId());
    params.put("secret", properties.getAppSecret());


    try {
        StringBuilder urlBuilder = new StringBuilder(url);
        urlBuilder.append("?grant_type=client_credential");
        urlBuilder.append("&appid=").append(properties.getAppId());
        urlBuilder.append("&secret=").append(properties.getAppSecret());


        ResponseEntity response = restTemplate.getForEntity(urlBuilder.toString(), String.class);
        
        JSONObject result = JSON.parseObject(response.getBody());
        
        if (result.containsKey("access_token")) {
            String accessToken = result.getString("access_token");
            log.info("获取微信access_token成功");
            return accessToken;
        } else {
            String errmsg = result.getString("errmsg");
            int errcode = result.getIntValue("errcode");
            log.error("获取微信access_token失败,errcode={}, errmsg={}", errcode, errmsg);
            throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                    "获取微信access_token失败: " + errmsg);
        }
    } catch (Exception e) {
        log.error("获取微信access_token异常", e);
        throw SystemException.error(ErrorCodeEnum.OPERATION_FAILED, 
                "获取微信access_token异常: " + e.getMessage());
    }
}
```


### 6. WeChatLogisticsService.buildCreateOrderRequest - 构建创建物流订单的请求数据


**位置**: `org.example.huolingjava.logistics.service.WeChatLogisticsService`


```java
/**
 * 构建创建物流订单的请求数据
 */
@SuppressWarnings("unchecked")
private Map buildCreateOrderRequest(Map orderData) {
    Map requestData = new HashMap<>();
    
    // 基础信息
    requestData.put("add_source", 2); // 2表示小程序
    requestData.put("order_id", String.valueOf(orderData.get("orderNumber")));
    requestData.put("delivery_id", properties.getZtoDeliveryId());
    requestData.put("biz_id", properties.getZtoBizId());
    requestData.put("openid", String.valueOf(orderData.get("openid")));
    requestData.put("wx_appid", properties.getAppId());
    requestData.put("notify_url", properties.getCallbackUrl());


    // 发货人信息
    WeChatLogisticsProperties.SenderInfo sender = properties.getSender();
    Map senderMap = new HashMap<>();
    senderMap.put("name", sender.getName());
    senderMap.put("tel", sender.getTel());
    senderMap.put("mobile", sender.getMobile());
    senderMap.put("company", sender.getCompany());
    senderMap.put("province", sender.getProvince());
    senderMap.put("city", sender.getCity());
    senderMap.put("area", sender.getArea());
    senderMap.put("address", sender.getAddress());
    requestData.put("sender", senderMap);


    // 收货人信息
    Map receiverMap = new HashMap<>();
    String customerName = String.valueOf(orderData.get("customerName"));
    String customerPhone = String.valueOf(orderData.get("customerPhone"));
    String customerAddress = String.valueOf(orderData.get("customerAddress"));
    
    receiverMap.put("name", customerName.length() > 20 ? customerName.substring(0, 20) : customerName);
    receiverMap.put("tel", customerPhone);
    receiverMap.put("mobile", customerPhone);
    
    // 解析地址(简化处理,实际应该解析省市区)
    receiverMap.put("province", "广东省");
    receiverMap.put("city", "中山市");
    receiverMap.put("area", "中山港街道");
    receiverMap.put("address", customerAddress.length() > 50 ? customerAddress.substring(0, 50) : customerAddress);
    requestData.put("receiver", receiverMap);


    // 货物信息
    Map cargoMap = new HashMap<>();
    java.util.List> products = (java.util.List>) orderData.get("products");
    if (products == null || products.isEmpty()) {
        throw SystemException.error(ErrorCodeEnum.PARAM_NOT_NULL, "商品列表不能为空");
    }
    
    cargoMap.put("count", products.size());
    
    // 计算总重量
    Double totalWeight = orderData.get("totalWeight") != null ? 
            Double.parseDouble(String.valueOf(orderData.get("totalWeight"))) : 0.1;
    if (totalWeight <= 0) {
        totalWeight = 0.1; // 默认0.1kg
    }
    cargoMap.put("weight", totalWeight);


    // 商品详情列表
    java.util.List> detailList = new java.util.ArrayList<>();
    for (Map product : products) {
        Map detail = new HashMap<>();
        String productName = String.valueOf(product.get("name"));
        // 移除括号,限制长度
        productName = productName.replace("(", "").replace(")", "");
        detail.put("name", productName.length() > 30 ? productName.substring(0, 30) : productName);
        detail.put("count", product.get("quantity"));
        detailList.add(detail);
    }
    cargoMap.put("detail_list", detailList);
    requestData.put("cargo", cargoMap);


    // 店铺信息(使用第一个商品)
    Map shopMap = new HashMap<>();
    Map firstProduct = products.get(0);
    String goodsName = String.valueOf(firstProduct.get("name"));
    goodsName = goodsName.replace("(", "").replace(")", "");
    shopMap.put("goods_name", goodsName.length() > 30 ? goodsName.substring(0, 30) : goodsName);
    shopMap.put("goods_count", products.size());
    requestData.put("shop", shopMap);


    return requestData;
}


最后一次编辑于  2025-12-30
回答关注问题邀请回答
收藏
登录 后发表内容