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

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;
}
}
}