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