第8章:常见问题

本文档整理了 WMS API 使用过程中的常见问题及解决方案。


8.1 认证相关

8.1.1 Q1: Token 获取失败,提示 "认证失败"

可能原因:

1. API Key 或 API Secret 错误

2. 请求头格式不正确

3. 环境不匹配(测试环境使用了生产环境的凭证)

解决方案:

1. 检查 API Key 和 API Secret 是否正确

2. 确认请求头格式:X-Api-KeyX-Api-Secret

3. 确认使用的环境与凭证匹配

4. 联系技术支持获取正确的凭证


8.1.2 Q2: Token 过期如何处理?

问题描述:

调用接口时返回 401 错误,提示 Token 过期。

解决方案:

1. Token 有效期为 24 小时(86400 秒)

2. 创建 Token 时,响应中会返回 expiresIn 字段(单位:秒),表示 Token 的有效期

3. 推荐方案:将 Token 缓存到 Redis 或本地缓存中

  • 缓存过期时间 = expiresIn - 5分钟(例如:86400 - 300 = 86100秒)
  • 当缓存过期时,自动重新获取 Token

4. 如果 Token 已过期(返回 401 错误),重新调用 Token 获取接口

示例代码逻辑(使用缓存):

// 示例代码 - 使用 Redis 缓存
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.TimeUnit;

public class TokenService {
    
    private static final String REDIS_KEY = "wms_api_token";
    private static final int CACHE_BUFFER = 5 * 60; // 提前5分钟失效(300秒)
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private TokenApiClient tokenApiClient;
    
    /**
     * 获取 Token,优先从缓存获取,缓存不存在或过期时重新获取
     */
    public String getToken() {
        // 1. 尝试从缓存获取 Token
        String token = redisTemplate.opsForValue().get(REDIS_KEY);
        
        if (token != null && !token.isEmpty()) {
            // 缓存中存在,直接返回
            return token;
        }
        
        // 2. 缓存不存在或已过期,重新获取 Token
        TokenResponse tokenResponse = tokenApiClient.getNewToken();
        token = tokenResponse.getAccessToken();
        Long expiresIn = tokenResponse.getExpiresIn();
        
        // 3. 计算缓存过期时间(提前5分钟失效)
        long cacheExpireTime = expiresIn - CACHE_BUFFER;
        
        // 4. 将 Token 存入缓存,设置过期时间
        redisTemplate.opsForValue().set(REDIS_KEY, token, cacheExpireTime, TimeUnit.SECONDS);
        
        return token;
    }
}

// 使用示例
@Autowired
private TokenService tokenService;

String token = tokenService.getToken();
// 使用 token 调用业务接口

8.1.3 Q3: 如何判断 Token 是否即将过期?

问题描述:

需要判断 Token 是否即将过期,以便提前刷新。

解决方案:

1. 使用缓存机制:将 Token 缓存到 Redis 或本地缓存中

2. 设置缓存过期时间:缓存过期时间 = expiresIn - 5分钟(例如:86400 - 300 = 86100秒)

3. 自动刷新:当缓存过期时,说明 Token 即将失效,自动重新获取 Token

4. 优势:无需手动计算剩余时间,利用缓存机制自动管理 Token 生命周期

示例代码(Redis 缓存):

// 示例代码
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import java.util.concurrent.TimeUnit;

public class TokenService {
    
    private static final String REDIS_KEY = "wms_api_token";
    private static final int CACHE_BUFFER = 5 * 60; // 提前5分钟失效(300秒)
    
    @Autowired
    private StringRedisTemplate redisTemplate;
    
    @Autowired
    private TokenApiClient tokenApiClient;
    
    /**
     * 获取 Token,优先从缓存获取,缓存不存在或过期时重新获取
     */
    public String getToken() {
        // 1. 尝试从缓存获取 Token
        String token = redisTemplate.opsForValue().get(REDIS_KEY);
        
        if (token != null && !token.isEmpty()) {
            // 缓存中存在,直接返回
            return token;
        }
        
        // 2. 缓存不存在或已过期,重新获取 Token
        TokenResponse tokenResponse = tokenApiClient.getNewToken();
        token = tokenResponse.getAccessToken();
        Long expiresIn = tokenResponse.getExpiresIn();
        
        // 3. 计算缓存过期时间(提前5分钟失效)
        long cacheExpireTime = expiresIn - CACHE_BUFFER;
        
        // 4. 将 Token 存入缓存,设置过期时间
        redisTemplate.opsForValue().set(REDIS_KEY, token, cacheExpireTime, TimeUnit.SECONDS);
        
        return token;
    }
}

// 使用示例
@Autowired
private TokenService tokenService;

String token = tokenService.getToken();
// 使用 token 调用业务接口

示例代码(本地缓存):

// 示例代码
import java.util.concurrent.atomic.AtomicLong;

public class TokenCacheService {
    
    private static final int CACHE_BUFFER = 5 * 60; // 提前5分钟失效(300秒)
    
    private String token;
    private AtomicLong expireTime = new AtomicLong(0);
    private final Object lock = new Object();
    
    @Autowired
    private TokenApiClient tokenApiClient;
    
    /**
     * 获取 Token,优先从本地缓存获取,缓存过期时重新获取
     */
    public String getToken() {
        long now = System.currentTimeMillis() / 1000; // 当前时间戳(秒)
        
        // 1. 检查缓存是否有效
        if (token != null && now < expireTime.get()) {
            return token;
        }
        
        // 2. 缓存无效,重新获取 Token(加锁防止并发)
        synchronized (lock) {
            // 双重检查,避免重复获取
            if (token != null && now < expireTime.get()) {
                return token;
            }
            
            TokenResponse tokenResponse = tokenApiClient.getNewToken();
            Long expiresIn = tokenResponse.getExpiresIn();
            
            // 3. 计算缓存过期时间(提前5分钟失效)
            long cacheExpireTime = now + expiresIn - CACHE_BUFFER;
            
            // 4. 更新缓存
            this.token = tokenResponse.getAccessToken();
            this.expireTime.set(cacheExpireTime);
            
            return this.token;
        }
    }
}

// 使用示例
@Autowired
private TokenCacheService tokenCacheService;

String token = tokenCacheService.getToken();
// 使用 token 调用业务接口

8.2 接口调用相关

8.2.1 Q4: 批量创建接口,部分成功部分失败如何处理?

问题描述:

调用批量创建接口(如创建商品、创建出库订单),返回的响应中部分成功部分失败。

解决方案:

1. 检查返回结果,查看每个条目的处理状态

2. 对于失败的条目,根据错误信息修正后重新提交

3. 建议分批提交,每批不超过 100 条

4. 实现重试机制,对失败的条目进行重试


8.2.2 Q5: 接口返回 400 错误,提示参数错误

可能原因:

1. 必填参数缺失

2. 参数格式不正确(如日期格式、枚举值)

3. 参数值超出范围(如数量为负数)

4. 参数长度超出限制

解决方案:

1. 检查请求参数是否完整

2. 确认参数格式是否符合要求(参考接口文档)

3. 检查参数值是否在有效范围内

4. 查看错误信息中的具体字段提示

常见错误:

  • SKU 格式错误:仅支持大写字母、数字、下划线、横线,长度1-20
  • 日期格式错误:应使用 MM/dd/yyyy 格式
  • 枚举值错误:库存类型应为 1、2、3 等

8.2.3 Q6: 接口返回 500 错误,服务器内部错误

问题描述:

调用接口时返回 500 错误。

解决方案:

1. 500 错误通常是服务器端问题

2. 可以适当重试(建议实现指数退避)

3. 如果持续出现,联系技术支持

4. 检查请求参数是否异常(如数据量过大)

重试建议:

  • 首次重试:等待 1 秒
  • 第二次重试:等待 2 秒
  • 第三次重试:等待 4 秒
  • 最多重试 3 次

8.2.4 Q7: 分页查询如何获取所有数据?

问题描述:

需要获取所有数据,但接口只支持分页查询。

解决方案:

1. 从第一页开始查询(current=1)

2. 根据返回的 pagination.total 计算总页数

3. 循环调用接口,逐页获取数据

4. 直到获取完所有数据

示例逻辑:

// 示例代码
import java.util.ArrayList;
import java.util.List;

int current = 1;
List<Object> allData = new ArrayList<>();
boolean hasMore = true;

while (hasMore) {
    Map<String, Object> response = queryData(current);
    List<Object> resultList = (List<Object>) response.get("result");
    allData.addAll(resultList);
    
    Map<String, Object> pagination = (Map<String, Object>) response.get("pagination");
    int totalPages = (int) Math.ceil((double) ((Long) pagination.get("total")) / ((Integer) pagination.get("pageSize")));
    
    if (current >= totalPages) {
        hasMore = false;
    } else {
        current++;
    }
}

8.3 业务逻辑相关

8.3.1 Q8: 为什么出库订单更新失败,提示只能更新 Pending 或 Special 状态的订单?

问题描述:

尝试更新出库订单,但返回错误提示只能更新 Pending 或 Special 状态的订单。

解决方案:

1. 出库订单更新仅支持 PendingSpecial 状态

2. 如果订单已进入其他状态(如 WorkingFulfiled),无法更新

3. 可以先取消订单,然后重新创建

4. 或者联系技术支持处理

订单状态流转:

Pending → Processing → Shipped → Delivered
         ↓
      Cancelled

8.3.2 Q9: 创建入库预报时,SKU 不存在怎么办?

问题描述:

创建入库预报时,提示 SKU 不存在。

解决方案:

1. SKU 必须先在商品库中创建

2. 先调用商品创建接口创建商品

3. 然后再创建入库预报

4. 建议在创建入库预报前,先查询商品是否存在

正确流程:

1. 创建商品(POST /onixport/api/wms/product/create)
2. 创建入库预报(POST /onixport/api/wms/inbound/create)

8.3.3 Q10: 序列号格式要求是什么?

问题描述:

创建序列号预报时,序列号格式验证失败。

解决方案:

序列号格式要求:

1. 首字符:必须是大写字母(A-Z)

2. 后续字符:可以是大写字母(A-Z)、数字(0-9)、下划线(_)、横线(-)

3. 长度限制:最大 32 个字符

正确示例:

  • SN001234567890
  • AUSD124254253223
  • PROD-2024-001

错误示例:

  • sn001234567890(首字母必须大写)
  • 1234567890(首字符必须是字母)
  • SN 001234567890(不能包含空格)

8.3.4 Q11: 库存查询返回的数据为什么和实际不一致?

问题描述:

查询库存时,返回的数据与实际库存不一致。

可能原因:

1. 数据同步延迟(通常不超过 5 分钟)

2. 查询条件不正确(如仓库编码、库存类型)

3. 库存被其他订单占用(frozenQty)

解决方案:

1. 等待几分钟后重新查询

2. 检查查询条件是否正确

3. 查看 frozenQty(占用库存)和 availableQty(可用库存)

4. 总库存 = 可用库存 + 占用库存


8.4 数据格式相关

8.4.1 Q12: 日期时间格式是什么?

问题描述:

接口中日期时间字段的格式要求。

解决方案:

1. 日期格式MM/dd/yyyy(如:10/10/2025)

2. 时间格式HH:mm:ss(如:17:00:00)

3. 日期时间格式MM/dd/yyyy HH:mm:ss(如:10/10/2025 17:00:00)

4. 时区:所有时间均为仓库所在地时区


8.4.2 Q13: 数值精度要求是什么?

问题描述:

金额、重量等数值字段的精度要求。

解决方案:

1. 金额(declaredValue):USD,保留两位小数(如:29.99)

2. 重量(weight):Lbs(磅),支持小数(如:0.5)

3. 尺寸(dimensions):Inch(英寸),整数(如:6)

4. 数量(quantity):整数,必须大于 0


8.5 性能与限流相关

8.5.1 Q14: 接口调用频率有限制吗?

问题描述:

担心接口调用频率过高被限制。

解决方案:

1. 单个 API Key 的 QPS 限制:100 次/秒

2. 单个 IP 的 QPS 限制:200 次/秒

3. 超过限制会返回 429 错误

4. 建议实现请求队列和限流控制

建议:

  • 批量操作优先使用批量接口
  • 避免频繁轮询,使用合理的查询间隔
  • 实现请求缓存机制

8.5.2 Q15: 接口响应时间一般是多少?

问题描述:

接口响应时间过长。

解决方案:

1. 正常响应时间:通常 < 1 秒

2. 批量操作:可能达到 3-5 秒

3. 超时设置:建议设置 30 秒超时

4. 如果超过 60 秒未响应,可以重试或联系技术支持


8.6 环境相关

8.6.1 Q16: 测试环境和生产环境的区别?

问题描述:

不清楚两个环境的区别和使用场景。

解决方案:

1. 测试环境(Sandbox)

  • 用于开发调试
  • 数据不影响正式业务
  • 可能定期清理数据
  • 性能可能较低

2. 生产环境(Production)

  • 用于正式业务
  • 数据真实有效
  • 数据永久保存
  • 生产级性能

建议:

  • 开发阶段使用测试环境
  • 验收通过后切换到生产环境
  • 两个环境的凭证不互通

8.7 技术支持

8.7.1 Q17: 如何获取技术支持?

联系方式:

1. 技术支持邮箱:[待补充]

2. 技术支持电话:[待补充]

3. 在线支持:[待补充]

反馈内容:

  • 问题描述
  • 请求参数(脱敏处理)
  • 错误信息
  • 复现步骤
  • 环境信息(测试/生产)

8.8 其他问题

8.8.1 Q18: 如何查看接口调用日志?

问题描述:

需要查看接口调用历史记录。

解决方案:

1. 目前系统不提供调用日志查询接口

2. 建议在调用方记录请求和响应日志

3. 如有需要,联系技术支持查询


8.8.2 Q19: 支持哪些编程语言?

问题描述:

是否有特定语言的 SDK。

解决方案:

1. 目前提供 RESTful API,支持所有支持 HTTP 的编程语言

2. 暂无官方 SDK,但可以基于 HTTP 客户端自行封装

3. 常见语言示例:

  • Java:使用 OkHttp、HttpClient
  • Python:使用 requests
  • PHP:使用 cURL、Guzzle
  • JavaScript:使用 axios、fetch

8.8.3 Q20: 数据安全如何保障?

问题描述:

担心数据传输和存储的安全性。

解决方案:

1. 传输安全:所有接口使用 HTTPS 加密传输

2. 认证安全:使用 Token 机制,Token 有时效性

3. 数据隔离:不同客户的数据完全隔离

4. 访问控制:基于 API Key 的访问控制


反馈与建议

如果您遇到的问题不在上述列表中,或者有改进建议,请联系技术支持团队。

我们会持续更新本文档,添加更多常见问题和解决方案。