解析Java商城项目集成快递100实现物流追踪和运费计算的方法

0 阅读4分钟

Java商城项目集成快递100物流追踪与运费计算方案

一、准备工作

  1. 注册快递100账号

    • 访问快递100官网注册企业账号
    • 获取API密钥(API Key)和客户编码(Customer)
  2. 添加依赖

<!-- Apache HttpClient -->
<dependency>
    <groupId>org.apache.httpcomponents</groupId>
    <artifactId>httpclient</artifactId>
    <version>4.5.13</version>
</dependency>

<!-- JSON处理 -->
<dependency>
    <groupId>com.fasterxml.jackson.core</groupId>
    <artifactId>jackson-databind</artifactId>
    <version>2.13.1</version>
</dependency>

二、物流追踪实现

1. 创建配置类
@Component
public class Kuaidi100Config {
    @Value("${kuaidi100.customer}")
    private String customer;
    @Value("${kuaidi100.key}")
    private String apiKey;
    
    // Getter方法
}
2. 物流查询服务实现
@Service
public class LogisticsService {
    
    @Autowired
    private Kuaidi100Config config;
    
    private static final String QUERY_URL = "https://poll.kuaidi100.com/poll/query.do";
    
    public LogisticsResponse queryLogistics(String com, String num) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 1. 准备请求参数
            Map<String, String> paramMap = new HashMap<>();
            paramMap.put("com", com);  // 快递公司编码
            paramMap.put("num", num);  // 快递单号
            paramMap.put("phone", ""); // 收/寄件人手机号(可选)
            
            String param = new ObjectMapper().writeValueAsString(paramMap);
            String sign = DigestUtils.md5Hex(param + config.getApiKey() + config.getCustomer()).toUpperCase();
            
            // 2. 构建POST请求
            HttpPost httpPost = new HttpPost(QUERY_URL);
            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair("customer", config.getCustomer()));
            params.add(new BasicNameValuePair("sign", sign));
            params.add(new BasicNameValuePair("param", param));
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
            
            // 3. 执行请求并解析响应
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String json = EntityUtils.toString(response.getEntity());
                return new ObjectMapper().readValue(json, LogisticsResponse.class);
            }
        } catch (Exception e) {
            throw new RuntimeException("物流查询失败", e);
        }
    }
    
    // 响应DTO
    @Data
    public static class LogisticsResponse {
        private String message;
        private String state;
        private String status;
        private String condition;
        private String ischeck;
        private List<LogisticsDetail> data;
        
        @Data
        public static class LogisticsDetail {
            private String time;
            private String ftime;
            private String context;
            private String location;
        }
    }
}

三、运费计算实现

1. 运费计算服务
@Service
public class FreightService {
    
    @Autowired
    private Kuaidi100Config config;
    
    private static final String PRICE_URL = "https://www.kuaidi100.com/freight/query.do";
    
    public FreightResponse calculateFreight(FreightRequest request) {
        try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
            // 1. 准备请求参数
            String param = new ObjectMapper().writeValueAsString(request);
            String sign = DigestUtils.md5Hex(param + config.getApiKey() + config.getCustomer()).toUpperCase();
            
            // 2. 构建POST请求
            HttpPost httpPost = new HttpPost(PRICE_URL);
            List<NameValuePair> params = new ArrayList<>();
            params.add(new BasicNameValuePair("customer", config.getCustomer()));
            params.add(new BasicNameValuePair("sign", sign));
            params.add(new BasicNameValuePair("param", param));
            httpPost.setEntity(new UrlEncodedFormEntity(params, "UTF-8"));
            
            // 3. 执行请求并解析
            try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
                String json = EntityUtils.toString(response.getEntity());
                return new ObjectMapper().readValue(json, FreightResponse.class);
            }
        } catch (Exception e) {
            throw new RuntimeException("运费计算失败", e);
        }
    }
    
    // 请求DTO
    @Data
    public static class FreightRequest {
        private String sendProv;   // 发货省
        private String sendCity;  // 发货市
        private String recivProv; // 收货省
        private String recivCity; // 收货市
        private BigDecimal weight; // 重量(kg)
        private String serviceType; // 服务类型 (标准快递/顺丰特惠等)
        private String expressType; // 快递公司编码
    }
    
    // 响应DTO
    @Data
    public static class FreightResponse {
        private String status;
        private String message;
        private List<FreightResult> data;
        
        @Data
        public static class FreightResult {
            private String expressName; // 快递公司
            private BigDecimal price;   // 运费
            private Integer time;       // 预计时效(小时)
        }
    }
}

四、控制器层实现

@RestController
@RequestMapping("/logistics")
public class LogisticsController {
    
    @Autowired
    private LogisticsService logisticsService;
    
    @Autowired
    private FreightService freightService;
    
    // 物流查询
    @GetMapping("/track")
    public LogisticsResponse track(
            @RequestParam String expressCode,
            @RequestParam String trackingNumber) {
        return logisticsService.queryLogistics(expressCode, trackingNumber);
    }
    
    // 运费计算
    @PostMapping("/calculate")
    public FreightResponse calculate(@RequestBody FreightRequest request) {
        return freightService.calculateFreight(request);
    }
}

五、快递公司编码管理

@Service
public class ExpressCompanyService {
    
    // 快递公司编码缓存(实际应从数据库读取)
    private static final Map<String, String> COMPANY_CODES = Map.of(
        "顺丰", "sf",
        "中通", "zto",
        "圆通", "yuantong",
        "韵达", "yunda",
        "京东", "jd"
    );
    
    public List<String> getAllCompanies() {
        return new ArrayList<>(COMPANY_CODES.keySet());
    }
    
    public String getCompanyCode(String companyName) {
        return COMPANY_CODES.get(companyName);
    }
}

六、数据库设计建议

CREATE TABLE orders (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_no VARCHAR(50) NOT NULL,
    express_company VARCHAR(20) COMMENT '快递公司名称',
    tracking_no VARCHAR(50) COMMENT '快递单号',
    freight DECIMAL(10,2) COMMENT '运费'
);

CREATE TABLE logistics_record (
    id BIGINT PRIMARY KEY AUTO_INCREMENT,
    order_id BIGINT NOT NULL,
    event_time DATETIME NOT NULL,
    event_desc VARCHAR(200) NOT NULL,
    location VARCHAR(100)
);

七、关键配置(application.yml)

kuaidi100:
  customer: YOUR_CUSTOMER_CODE
  key: YOUR_API_KEY

八、异常处理增强

@ControllerAdvice
public class GlobalExceptionHandler {
    
    @ResponseStatus(HttpStatus.BAD_REQUEST)
    @ExceptionHandler(RuntimeException.class)
    @ResponseBody
    public Map<String, Object> handleServiceException(RuntimeException ex) {
        return Map.of(
            "code", 400,
            "message", ex.getMessage(),
            "timestamp", System.currentTimeMillis()
        );
    }
}

九、安全与优化措施

  1. 请求参数验证

    public FreightResponse calculate(@Valid @RequestBody FreightRequest request) {
        // ...
    }
    
  2. 结果缓存

    @Cacheable(value = "freightCache", key = "#request.hashCode()")
    public FreightResponse calculateFreight(FreightRequest request) {
        // ...
    }
    
  3. 限流保护

    @RateLimiter(name = "kuaidi100Api", rate = 10) // 每秒10次
    public LogisticsResponse queryLogistics(String com, String num) {
        // ...
    }
    
  4. 异步处理

    @Async
    public CompletableFuture<LogisticsResponse> asyncQuery(String com, String num) {
        return CompletableFuture.completedFuture(queryLogistics(com, num));
    }
    

十、前端集成示例

// 物流查询
axios.get('/logistics/track', {
  params: {
    expressCode: 'sf',
    trackingNumber: 'SF123456789'
  }
}).then(response => {
  console.log('物流轨迹:', response.data);
});

// 运费计算
axios.post('/logistics/calculate', {
  sendProv: '广东省',
  sendCity: '深圳市',
  recivProv: '浙江省',
  recivCity: '杭州市',
  weight: 1.5,
  serviceType: '标准快递',
  expressType: 'sf'
}).then(response => {
  console.log('运费结果:', response.data);
});

实施注意事项:

  1. 快递公司编码:使用前需在快递100官网查询正确的快递公司编码
  2. 错误处理:正确处理API返回的status字段(如200表示成功)
  3. 超时设置:HttpClient需配置合理的连接超时和读取超时
  4. 敏感信息:API密钥应存储在配置中心或环境变量中
  5. 计费限制:注意快递100的API调用计费策略(免费额度+超额收费)
  6. 物流订阅:对大业务量建议使用订阅推送模式

提示:实际部署前需在测试环境充分验证,处理快递100返回的各种异常状态码(如501单号不存在、403验证失败等)