You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
163 lines
6.2 KiB
163 lines
6.2 KiB
|
4 weeks ago
|
package com.project.receive.controller;
|
||
|
|
|
||
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||
|
|
import com.project.receive.dto.*;
|
||
|
|
import com.project.receive.utils.SfDecryptUtil;
|
||
|
|
import lombok.extern.slf4j.Slf4j;
|
||
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
||
|
|
import org.springframework.web.bind.annotation.*;
|
||
|
|
|
||
|
|
import java.io.FileOutputStream;
|
||
|
|
import java.nio.file.Path;
|
||
|
|
import java.nio.file.Paths;
|
||
|
|
|
||
|
|
@RestController
|
||
|
|
@Slf4j
|
||
|
|
@RequestMapping("/api/")
|
||
|
|
public class ReceiveController {
|
||
|
|
|
||
|
|
private static final String SF_CHECKWORD = "ZXmoWOQdSd2UTBmSP6Kv3VW9Q4N5dJqz";
|
||
|
|
/**
|
||
|
|
* 接收顺丰订单状态推送接口
|
||
|
|
* 对应文档 2.6 节 JSON 示例
|
||
|
|
*/
|
||
|
|
@PostMapping("/pushOrderState")
|
||
|
|
public SfPushResponse receiveOrderState(@RequestBody SfPushRequest request) {
|
||
|
|
// 1. 打印接收到的原始数据日志
|
||
|
|
log.info("==== 收到顺丰状态推送 ====");
|
||
|
|
log.info("Request ID: {}", request.getRequestId());
|
||
|
|
log.info("Timestamp: {}", request.getTimestamp());
|
||
|
|
|
||
|
|
if (request.getOrderState() != null) {
|
||
|
|
request.getOrderState().forEach(state -> {
|
||
|
|
log.info("订单号: {}, 运单号: {}, 状态码: {}, 描述: {}",
|
||
|
|
state.getOrderNo(),
|
||
|
|
state.getWaybillNo(),
|
||
|
|
state.getOrderStateCode(),
|
||
|
|
state.getOrderStateDesc());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 2. 根据文档 2.7 节,返回成功响应
|
||
|
|
return SfPushResponse.ok();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 接收顺丰路由推送
|
||
|
|
* 对应文档 2.5 节 JSON 示例
|
||
|
|
*/
|
||
|
|
@PostMapping("/pushRoute")
|
||
|
|
public SfRouteResponse receiveRoute(@RequestBody SfRoutePushRequest request) {
|
||
|
|
log.info(">>>> 收到顺丰路由信息推送 <<<<");
|
||
|
|
|
||
|
|
if (request.getBody() != null && request.getBody().getWaybillRoute() != null) {
|
||
|
|
for (WaybillRoute route : request.getBody().getWaybillRoute()) {
|
||
|
|
log.info("运单号: {}, 订单号: {}, 时间: {}, 状态: {}, 备注: {}",
|
||
|
|
route.getMailno(),
|
||
|
|
route.getOrderid(),
|
||
|
|
route.getAcceptTime(),
|
||
|
|
route.getOpCode(),
|
||
|
|
route.getRemark());
|
||
|
|
}
|
||
|
|
} else {
|
||
|
|
log.warn("收到的路由数据内容为空");
|
||
|
|
}
|
||
|
|
|
||
|
|
// 根据文档 2.6,必须返回 0000 告知顺丰接收成功,否则顺丰会重复推送
|
||
|
|
return SfRouteResponse.ok();
|
||
|
|
}
|
||
|
|
|
||
|
|
|
||
|
|
@RequestMapping("/pushOrderStateRaw")
|
||
|
|
public String receiveRaw(@RequestBody String rawBody) {
|
||
|
|
log.info("收到原始报文: {}", rawBody);
|
||
|
|
// 返回 JSON 格式的成功字符串
|
||
|
|
return "{\"success\":\"true\",\"code\":\"0\",\"msg\":\"\"}";
|
||
|
|
}
|
||
|
|
|
||
|
|
@Autowired
|
||
|
|
private ObjectMapper objectMapper;
|
||
|
|
|
||
|
|
/**
|
||
|
|
* 接收顺丰运费推送
|
||
|
|
* 报文类型: application/x-www-form-urlencoded
|
||
|
|
*/
|
||
|
|
@PostMapping(value = "/pushFee", consumes = "application/x-www-form-urlencoded")
|
||
|
|
public SfFeeResponse receiveFee(
|
||
|
|
@RequestParam("content") String content,
|
||
|
|
@RequestParam(value = "sign", required = false) String sign) {
|
||
|
|
|
||
|
|
log.info(">>>> 收到顺丰运费推送 <<<<");
|
||
|
|
log.info("签名(sign): {}", sign);
|
||
|
|
log.info("原始内容(content): {}", content);
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 将 content 字符串解析为 Java 对象
|
||
|
|
SfFeeContent feeData = objectMapper.readValue(content, SfFeeContent.class);
|
||
|
|
|
||
|
|
log.info("解析成功 - 运单号: {}, 订单号: {}, 计费重量: {}",
|
||
|
|
feeData.getWaybillNo(),
|
||
|
|
feeData.getOrderNo(),
|
||
|
|
feeData.getMeterageWeightQty());
|
||
|
|
|
||
|
|
if (feeData.getFeeList() != null) {
|
||
|
|
feeData.getFeeList().forEach(fee -> {
|
||
|
|
log.info("费用项 - 类型: {}, 金额: {}", fee.getFeeTypeCode(), fee.getFeeAmt());
|
||
|
|
});
|
||
|
|
}
|
||
|
|
|
||
|
|
// 返回成功响应 (code 必须为 200)
|
||
|
|
return SfFeeResponse.ok("your_partner_code");
|
||
|
|
|
||
|
|
} catch (Exception e) {
|
||
|
|
log.error("解析顺丰运费推送失败", e);
|
||
|
|
SfFeeResponse error = new SfFeeResponse();
|
||
|
|
error.setCode(400);
|
||
|
|
error.setMessage("解析异常");
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
|
||
|
|
@PostMapping("/pushElectronicReceipt")
|
||
|
|
public SfPicturePushResponse receiveReceipt(@RequestBody SfPicturePushRequest request) {
|
||
|
|
log.info(">>>> 收到顺丰电子回单(IN149)推送, 运单号: {} <<<<", request.getWaybillNo());
|
||
|
|
|
||
|
|
try {
|
||
|
|
// 步骤 1:解密最外层的 content 得到内部 JSON 字符串
|
||
|
|
byte[] firstLevelDecrypted = SfDecryptUtil.decrypt(request.getContent(), SF_CHECKWORD);
|
||
|
|
String innerJsonStr = new String(firstLevelDecrypted, "UTF-8");
|
||
|
|
log.info("第一层解密成功");
|
||
|
|
|
||
|
|
// 步骤 2:解析内部 JSON 获取文件密文
|
||
|
|
SfInnerContent innerContent = objectMapper.readValue(innerJsonStr, SfInnerContent.class);
|
||
|
|
String encryptedFileBase64 = innerContent.getContent();
|
||
|
|
|
||
|
|
// 步骤 3:对文件密文进行第二层 AES 解密 (根据文档示例,文件内容也是加密的)
|
||
|
|
// 先 Base64 解码,再 AES 解密
|
||
|
|
byte[] pdfBytes = SfDecryptUtil.decrypt(encryptedFileBase64, SF_CHECKWORD);
|
||
|
|
log.info("第二层解密(文件流)成功,大小: {} bytes", pdfBytes.length);
|
||
|
|
|
||
|
|
// 步骤 4:保存为 PDF 文件到当前目录
|
||
|
|
String fileName = request.getWaybillNo() + "_receipt.pdf";
|
||
|
|
Path path = Paths.get(System.getProperty("user.dir"), fileName);
|
||
|
|
|
||
|
|
try (FileOutputStream fos = new FileOutputStream(path.toFile())) {
|
||
|
|
fos.write(pdfBytes);
|
||
|
|
fos.flush();
|
||
|
|
}
|
||
|
|
|
||
|
|
log.info("电子回单已保存至: {}", path.toAbsolutePath());
|
||
|
|
|
||
|
|
return SfPicturePushResponse.ok();
|
||
|
|
|
||
|
|
} catch (Exception e) {
|
||
|
|
log.error("处理电子回单推送失败", e);
|
||
|
|
SfPicturePushResponse error = new SfPicturePushResponse();
|
||
|
|
error.setReturnCode("1000");
|
||
|
|
error.setReturnMsg("解析保存失败: " + e.getMessage());
|
||
|
|
return error;
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|