15 changed files with 591 additions and 45 deletions
@ -0,0 +1,165 @@ |
|||||
|
package com.project.receive.strategy.handler; |
||||
|
|
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.project.logistics.config.FixedRuleProperties; |
||||
|
import com.project.logistics.config.SfApiProperties; |
||||
|
import com.project.logistics.config.WebDavProperties; |
||||
|
import com.project.logistics.domain.entity.ApiRetryTaskEntity; |
||||
|
import com.project.logistics.domain.entity.LogisticsOrderEntity; |
||||
|
import com.project.logistics.domain.enums.OrderStatusEnum; |
||||
|
import com.project.logistics.domain.enums.RetryActionEnum; |
||||
|
import com.project.logistics.domain.service.WebDavService; |
||||
|
import com.project.logistics.domain.service.base.LogisticsOrderService; |
||||
|
import com.project.logistics.domain.strategy.ApiTaskHandler; |
||||
|
import com.project.logistics.domain.utils.FilePathUtil; |
||||
|
import com.project.receive.domain.entity.PodPushLogEntity; |
||||
|
import com.project.receive.domain.service.base.PodPushLogService; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.crypto.Cipher; |
||||
|
import javax.crypto.spec.IvParameterSpec; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.security.spec.AlgorithmParameterSpec; |
||||
|
import java.util.Base64; |
||||
|
import java.util.Date; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class SfDownloadPodHandler implements ApiTaskHandler { |
||||
|
|
||||
|
@Autowired |
||||
|
private PodPushLogService podPushLogService; |
||||
|
@Autowired |
||||
|
private LogisticsOrderService logisticsOrderService; |
||||
|
@Autowired |
||||
|
private WebDavService webDavService; |
||||
|
@Autowired |
||||
|
private FixedRuleProperties fixedRuleProperties; |
||||
|
|
||||
|
@Autowired |
||||
|
private WebDavProperties webDavProperties; |
||||
|
|
||||
|
@Autowired |
||||
|
private SfApiProperties sfApiProperties; |
||||
|
|
||||
|
@Override |
||||
|
public String getActionCode() { |
||||
|
return RetryActionEnum.SF_DOWNLOAD_POD.getCode(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handle(ApiRetryTaskEntity task, LogisticsOrderEntity order) throws Exception { |
||||
|
// 1. 获取原始密文数据
|
||||
|
PodPushLogEntity podLog = podPushLogService.lambdaQuery() |
||||
|
.eq(PodPushLogEntity::getWaybillNo, order.getSfWaybillNo()) |
||||
|
.orderByDesc(PodPushLogEntity::getId) |
||||
|
.last("LIMIT 1").one(); |
||||
|
|
||||
|
if (podLog == null || StrUtil.isBlank(podLog.getRawPushData())) { |
||||
|
throw new RuntimeException("流水表中未发现加密的回单数据,等待顺丰推送..."); |
||||
|
} |
||||
|
|
||||
|
log.info(">>> 任务执行:开始解密单号 {} 的原始报文", order.getOrderNo()); |
||||
|
|
||||
|
// 2. 执行解密逻辑 (AES解密 + 二次解码)
|
||||
|
byte[] finalPdfBytes; |
||||
|
try { |
||||
|
// 获取并处理 Key (CheckWord 前16位)
|
||||
|
// String secret = "b283e485aad2a78f";
|
||||
|
|
||||
|
String secret = sfApiProperties.getPodSecret(); |
||||
|
|
||||
|
if (StrUtil.isBlank(secret)) { |
||||
|
secret = "axjGikuWgvYkI3JA"; // 沙箱
|
||||
|
} |
||||
|
if (secret.length() > 16) { |
||||
|
secret = secret.substring(0, 16); |
||||
|
} |
||||
|
|
||||
|
finalPdfBytes = decryptAndDecode2(podLog.getRawPushData(), secret); |
||||
|
} catch (Exception e) { |
||||
|
log.error(">>> 解密失败: {}", e.getMessage()); |
||||
|
throw new RuntimeException("DECRYPT_PROCESS_FAILED: " + e.getMessage()); |
||||
|
} |
||||
|
|
||||
|
// 3. 上传 WebDAV
|
||||
|
String datePath = FilePathUtil.getHierarchicalPath(new Date()); |
||||
|
String fileName = order.getOrderNo() + ".pdf"; |
||||
|
String fullSavePath = webDavProperties.getPodRoot() + "/" + datePath + "/" + fileName; |
||||
|
|
||||
|
webDavService.uploadFile(fullSavePath , finalPdfBytes); |
||||
|
|
||||
|
// 4. 更新订单和日志状态
|
||||
|
order.setPodPdfPath(fullSavePath); |
||||
|
order.setOrderStatus(OrderStatusEnum.FINISHED.getCode()); // 流程结束
|
||||
|
logisticsOrderService.updateById(order); |
||||
|
|
||||
|
podLog.setProcessStatus(1); // 成功
|
||||
|
podPushLogService.updateById(podLog); |
||||
|
|
||||
|
log.info(">>> 回单处理成功并已上传 WebDAV: {}", fullSavePath); |
||||
|
} |
||||
|
|
||||
|
/** |
||||
|
* 内部解密方法:AES -> Base64解码 |
||||
|
*/ |
||||
|
private byte[] decryptAndDecode(String rawEncryptedBase64, String secret) throws Exception { |
||||
|
// A. 第一次解码:密文Base64 -> 密文字节
|
||||
|
byte[] cipherText = Base64.getDecoder().decode(rawEncryptedBase64.trim().replaceAll("\\s+", "")); |
||||
|
|
||||
|
// B. AES 解密
|
||||
|
SecretKeySpec skeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "AES"); |
||||
|
IvParameterSpec iv = new IvParameterSpec(new byte[16]); // 16个0
|
||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); |
||||
|
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); |
||||
|
byte[] decryptedBytes = cipher.doFinal(cipherText); |
||||
|
|
||||
|
// C. 第二次解码:顺丰解密后的字节其实是 PDF 的 Base64 字符串
|
||||
|
String pdfBase64Str = new String(decryptedBytes, "UTF-8").trim(); |
||||
|
return Base64.getDecoder().decode(pdfBase64Str); |
||||
|
} |
||||
|
|
||||
|
public static byte[] ivBytes = { 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
||||
|
0x00, 0x00, 0x00 }; |
||||
|
|
||||
|
private byte[] decryptAndDecode2(String rawEncryptedBase64, String secret) throws Exception { |
||||
|
// 1. 密钥深度清理:去空格、确保16位
|
||||
|
String cleanedSecret = secret.trim(); |
||||
|
if (cleanedSecret.length() > 16) { |
||||
|
cleanedSecret = cleanedSecret.substring(0, 16); |
||||
|
} |
||||
|
byte[] keyBytes = cleanedSecret.getBytes(StandardCharsets.UTF_8); |
||||
|
|
||||
|
// 2. 密文深度清理:去除所有换行、回车、空格
|
||||
|
// 关键点:Standard Decoder 遇到换行会出问题,必须手动清理或使用 MimeDecoder
|
||||
|
String cleanedPayload = rawEncryptedBase64.trim().replaceAll("\\s", ""); |
||||
|
|
||||
|
// 3. 使用 MimeDecoder (更稳健,会自动忽略非法字符)
|
||||
|
byte[] encryptedBytes = Base64.getMimeDecoder().decode(cleanedPayload); |
||||
|
|
||||
|
// 4. AES 解密配置
|
||||
|
SecretKeySpec newKey = new SecretKeySpec(keyBytes, "AES"); |
||||
|
// 顺丰 POD 推送固定使用 16 个 0 字节作为 IV
|
||||
|
IvParameterSpec ivSpec = new IvParameterSpec(new byte[16]); |
||||
|
|
||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); |
||||
|
cipher.init(Cipher.DECRYPT_MODE, newKey, ivSpec); |
||||
|
|
||||
|
byte[] decryptedResult; |
||||
|
try { |
||||
|
log.info("DEBUG: 待解密字节数组长度: {}", encryptedBytes.length); |
||||
|
decryptedResult = cipher.doFinal(encryptedBytes); |
||||
|
} catch (Exception e) { |
||||
|
log.error("AES解密失败!密钥长度: {}, 密文字节长度: {}", keyBytes.length, encryptedBytes.length); |
||||
|
throw e; |
||||
|
} |
||||
|
|
||||
|
// 5. 顺丰解出的是 PDF 的 Base64 文本,需要进行二次解码
|
||||
|
String pdfBase64Str = new String(decryptedResult, StandardCharsets.UTF_8).trim().replaceAll("\\s", ""); |
||||
|
return Base64.getMimeDecoder().decode(pdfBase64Str); |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,75 @@ |
|||||
|
package com.project.receive.strategy.handler; |
||||
|
|
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.jayway.jsonpath.JsonPath; |
||||
|
import com.project.logistics.config.FixedRuleProperties; |
||||
|
import com.project.logistics.config.SfApiProperties; |
||||
|
import com.project.logistics.domain.entity.ApiRetryTaskEntity; |
||||
|
import com.project.logistics.domain.entity.LogisticsOrderEntity; |
||||
|
import com.project.logistics.domain.enums.OrderStatusEnum; |
||||
|
import com.project.logistics.domain.enums.RetryActionEnum; |
||||
|
import com.project.logistics.domain.service.SfApiService; |
||||
|
import com.project.logistics.domain.service.base.LogisticsOrderService; |
||||
|
import com.project.logistics.domain.strategy.ApiTaskHandler; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Autowired; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class SfRegisterPodHandler implements ApiTaskHandler { |
||||
|
|
||||
|
@Autowired |
||||
|
private SfApiService sfApiService; |
||||
|
@Autowired |
||||
|
private LogisticsOrderService logisticsOrderService; |
||||
|
@Autowired |
||||
|
private FixedRuleProperties fixedRuleProperties; |
||||
|
|
||||
|
@Autowired |
||||
|
private SfApiProperties sfApiProperties; |
||||
|
|
||||
|
@Override |
||||
|
public String getActionCode() { |
||||
|
return RetryActionEnum.SF_REGISTER_POD.getCode(); |
||||
|
} |
||||
|
|
||||
|
@Override |
||||
|
public void handle(ApiRetryTaskEntity task, LogisticsOrderEntity order) throws Exception { |
||||
|
log.info(">>> [常规出货单] 发起回单图片主动注册: {}", order.getOrderNo()); |
||||
|
|
||||
|
// 构造请求报文 (参考顺丰文档)
|
||||
|
JSONObject msgData = new JSONObject(); |
||||
|
msgData.put("clientCode", sfApiProperties.getPartnerId()); |
||||
|
msgData.put("waybillNo", order.getSfWaybillNo()); |
||||
|
// 图片类型:122代表电子回单(IN149)
|
||||
|
msgData.put("imgType", "122"); |
||||
|
// 校验电话:通常传寄件人电话
|
||||
|
msgData.put("customerAcctCode", fixedRuleProperties.getMonthlyCard()); |
||||
|
|
||||
|
msgData.put("phone", fixedRuleProperties.getSenderMobile()); |
||||
|
|
||||
|
|
||||
|
task.setRequestData(msgData.toJSONString()); |
||||
|
|
||||
|
String result = sfApiService.callSfApi("EXP_RECE_REGISTER_WAYBILL_PICTURE", msgData, null); |
||||
|
task.setResponseData(result); |
||||
|
|
||||
|
// 解析结果
|
||||
|
String apiCode = JsonPath.read(result, "$.apiResultCode"); |
||||
|
if ("A1000".equals(apiCode)) { |
||||
|
String innerJsonStr = JsonPath.read(result, "$.apiResultData"); |
||||
|
boolean success = JsonPath.read(innerJsonStr, "$.success"); |
||||
|
if (success) { |
||||
|
log.info(">>> 顺丰回单图片注册成功,等待回调推送..."); |
||||
|
order.setOrderStatus(OrderStatusEnum.POD_REGISTERED.getCode()); |
||||
|
logisticsOrderService.updateById(order); |
||||
|
} else { |
||||
|
String errorMsg = JsonPath.read(innerJsonStr, "$.errorMsg"); |
||||
|
throw new RuntimeException("业务注册失败: " + errorMsg); |
||||
|
} |
||||
|
} else { |
||||
|
throw new RuntimeException("网关调用失败: " + JsonPath.read(result, "$.apiErrorMsg")); |
||||
|
} |
||||
|
} |
||||
|
} |
||||
@ -0,0 +1,119 @@ |
|||||
|
server: |
||||
|
port: 9088 |
||||
|
spring: |
||||
|
main: |
||||
|
# 允许 Bean 覆盖,解决 dynamic-datasource 与 JPA 的初始化冲突 |
||||
|
allow-bean-definition-overriding: true |
||||
|
datasource: |
||||
|
url: jdbc:mysql://8.129.84.155:3306/auto_logistics_pre?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false |
||||
|
username: logistics_admin |
||||
|
password: Itc@123456 |
||||
|
driver-class-name: com.mysql.cj.jdbc.Driver |
||||
|
|
||||
|
# dynamic: |
||||
|
# primary: master |
||||
|
# datasource: |
||||
|
# master: |
||||
|
# driverClassName: com.mysql.cj.jdbc.Driver |
||||
|
# password: Itc@123456 |
||||
|
# url: jdbc:mysql://8.129.84.155:3306/auto_logistics?serverTimezone=GMT%2B8&useUnicode=true&characterEncoding=utf-8&allowMultiQueries=true&allowPublicKeyRetrieval=true&useSSL=false |
||||
|
# username: logistics_admin |
||||
|
|
||||
|
data: |
||||
|
redis: |
||||
|
host: 8.129.84.155 |
||||
|
port: 6379 |
||||
|
password: Itc@123456 |
||||
|
database: 5 |
||||
|
timeout: 5000ms |
||||
|
lettuce: |
||||
|
pool: |
||||
|
max-active: 8 |
||||
|
max-idle: 30 |
||||
|
max-wait: 10000 |
||||
|
min-idle: 10 |
||||
|
jpa: |
||||
|
hibernate: |
||||
|
# 确保是 update |
||||
|
ddl-auto: update |
||||
|
# 显式指定数据库平台 |
||||
|
database-platform: org.hibernate.dialect.MySQL8Dialect |
||||
|
show-sql: true |
||||
|
# 关键:告诉 Hibernate 自动扫描实体类 |
||||
|
open-in-view: true |
||||
|
properties: |
||||
|
hibernate: |
||||
|
dialect: org.hibernate.dialect.MySQL8Dialect |
||||
|
# 显式指定命名策略,防止大小写或下划线解析错误 |
||||
|
physical_strategy: org.hibernate.boot.model.naming.PhysicalNamingStrategyStandardImpl |
||||
|
sf: |
||||
|
api: |
||||
|
partnerId: Y847O1KA |
||||
|
secret: DgNS7ANgynXYcnhvu4moAe4rCAHkWvk9 |
||||
|
tokenUrl: https://bspgw.sf-express.com/oauth2/accessToken |
||||
|
baseUrl: https://bspgw.sf-express.com/std/service |
||||
|
channelCode: INC-VMOS-CORE |
||||
|
podSecret: b4fb275c23204056 |
||||
|
mybatis-plus: |
||||
|
configuration: |
||||
|
# log-impl: org.apache.ibatis.logging.stdout.StdOutImpl # 开发环境下打印SQL |
||||
|
log-impl: org.apache.ibatis.logging.slf4j.Slf4jImpl |
||||
|
map-underscore-to-camel-case: true # 开启驼峰命名 |
||||
|
global-config: |
||||
|
db-config: |
||||
|
id-type: assign_id # 使用雪花算法生成本地订单主键ID |
||||
|
webdav: |
||||
|
url: "http://8.129.84.155:8881" |
||||
|
username: "admin" |
||||
|
password: "123456" |
||||
|
logistics: |
||||
|
scanner: |
||||
|
enabled: false |
||||
|
# 每 10 分钟唤醒一次 (系统开销微乎其微) |
||||
|
cron: "0 0/10 * * * ?" |
||||
|
windows: |
||||
|
# 这个窗口设置10分钟有效期,配合10分钟一次的唤醒,只会跑一次 |
||||
|
- name: "上午11点班次" |
||||
|
startTime: "11:08" |
||||
|
endTime: "11:08" |
||||
|
intervalMinutes: 60 |
||||
|
# 这个窗口在15-20点之间,每隔20分钟会真正跑一次逻辑 |
||||
|
- name: "下午至傍晚波次" |
||||
|
startTime: "15:08" |
||||
|
endTime: "20:08" |
||||
|
intervalMinutes: 60 |
||||
|
sample-scanner: |
||||
|
enabled: false |
||||
|
# 每 10 分钟唤醒一次 (系统开销微乎其微) |
||||
|
cron: "0 0/10 * * * ?" |
||||
|
windows: |
||||
|
# 这个窗口设置10分钟有效期,配合10分钟一次的唤醒,只会跑一次 |
||||
|
- name: "上午11点班次" |
||||
|
startTime: "11:08" |
||||
|
endTime: "11:08" |
||||
|
intervalMinutes: 60 |
||||
|
# 这个窗口在15-20点之间,每隔20分钟会真正跑一次逻辑 |
||||
|
- name: "下午至傍晚波次" |
||||
|
startTime: "15:08" |
||||
|
endTime: "20:08" |
||||
|
intervalMinutes: 20 |
||||
|
logging: |
||||
|
level: |
||||
|
# 强制打印 Hibernate 初始化过程 |
||||
|
org.hibernate.SQL: debug |
||||
|
org.hibernate.orm.deprecation: error |
||||
|
org.hibernate.tool.schema: debug |
||||
|
# 看看 Spring 到底有没有加载 JPA |
||||
|
org.springframework.orm.jpa: debug |
||||
|
u9-source: |
||||
|
url: jdbc:sqlserver://192.168.4.202:1433;databaseName=20241030;encrypt=false;trustServerCertificate=true |
||||
|
username: sa |
||||
|
password: 'Liujun1928374650' |
||||
|
driver-class-name: com.microsoft.sqlserver.jdbc.SQLServerDriver |
||||
|
fixed-rule: |
||||
|
senderPayContact: '罗伟健' |
||||
|
senderMobile: '15014218570' |
||||
|
senderAddress: '广东省广州市番禺区石碁镇南荔东路56号' |
||||
|
monthlyCard: '0200467987' |
||||
|
scheduled-task: |
||||
|
owner: test |
||||
@ -0,0 +1,82 @@ |
|||||
|
package com.project.logistics.domain.scheduler; |
||||
|
|
||||
|
import javax.crypto.Cipher; |
||||
|
import javax.crypto.spec.IvParameterSpec; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
import java.nio.file.Files; |
||||
|
import java.nio.file.Paths; |
||||
|
import java.util.Base64; |
||||
|
|
||||
|
public class SfTest { |
||||
|
|
||||
|
public static void main(String[] args) throws Exception { |
||||
|
// 1. 文档提供的样例密文
|
||||
|
String rawContent = "njdEklz2DvgAnu6GNrtzrxZrta+jg6Z9DptuedRafk2aJ8Lbo6FH7c8Z4ZcS8I7MTV1Q02GaBacX\\nOrT" + |
||||
|
"IfNesEsrVE1POLzPUXfnRznmVbXcPz0mknRfU1qtMSJBtBJdT3loY6Fnd30oatPmNaMey7G2x\\nO4Sh3V4EbeCt3GrBwEMQk/qwBA" + |
||||
|
"IhS56B4rquN2lX3fvJENJt/boklc1pz0JybTHgyvp1yTRIyqkR\\nMjfcfrTks1OF/WJ5Iuy/dYGe8HFooHyYS+5y+agVJWjIXleOZ" + |
||||
|
"gXUwm/WcLGTxsjJgiVdGDQhPsJU\\nwU86HOqapeJbGSFDw0Nzxzqu53rXVeqYxJ+UdsUGSiTL7+0kStTWmtknxoj/1XpeueUkytbN" + |
||||
|
"jgP9\\nickUKA6V/k/+PkVKoWbyKO4K7CGtljrAvOIwnWPwTsygnFRTsiEG7w+QzAQuMqIFwkSBSjKesv7d\\nLkizTOENT18CkcNi" + |
||||
|
"tSZJ2fOv0liqdU0gfGpFK3hxlKf5fr4Q5YKl0UfZdr3YEQl0QqW+pViRjezI\\ndS7Fyk4984DHab4L+se6HTUf1v+OTzQ47cnKPc" + |
||||
|
"SNpYjRRB8HSeI+xLgAI7k+oUU/1xgWXZlMO2Jh\\nMG96iRI92cXHjH0W67CvFYYA5oSct/wClQZBj02h5VCtslx160swgOMZj" + |
||||
|
"eGqgK4SekhZdCs0qdSm\\nmNHQue4dfQt0EtnWJ3rDyEw6+66UAsYftEYdVAWKt92yl2ATctelJvN060wwQutxx2EPjOLx0whx\\n" + |
||||
|
"i8rfktmzh3bUyf/VLCUPjBl1jiHIFCreAa8XA+rOo/6HcyWHKMwSpY7P54nDY7Ds4U7c0NOqoMtF\\ns+R9u81jQnAKsylj4iDm" + |
||||
|
"eMoMi63cBJ6pfsh3ztU2qa260NWiEa0pzS9P0QEjCmjBpWAHAW70gx7L\\n/uGAu+ln/Uf27cbE2c61stnRBLDfMohQ9wvrLSF8xHx" + |
||||
|
"nV9h5noCXkAd4U/EtqhSaGRWdAIEZ6Dxk\\nucyMxtpz13v9M6eZ9PMgJKNIyHUVe38QF1XNw8SpvuBVUj41QMA0cJWS6NwSKUVBEf" + |
||||
|
"y7Zmiv7z/7\\nbEIwGv9Z/zUt1TFzhxn1VXJLCwYLAYDOaPYpj8qhHCaTs59dfFBIvj6k3rUeqS3c4dGzCUJQNbW0\\nCtqBWClPz" + |
||||
|
"AW29C4/2w+f9s0V1Mx3HaJKROEy1UyfdtiAUTL3XF6u5aki7ZKKf4iRvuC5tooNkA1M\\ng222fEqxpwAFfGQS98yHL0unes39XGo/" + |
||||
|
"17ke8CB1aI2f4m6+NW3fe+ciSZe5zEvz/84U4M8IDW/s\\nR3OImJNWoLF5sadfnf5xSYV8zzz2MRn+/4bN0tjJkIIBvBEiVf7cxFD" + |
||||
|
"F1tLR3rXE1SiMQDso110A\\nn1PsLjwcl6uMqYkcso8cLOuk5ysfim2MRs1CYsqMPqKu6XT2w5dwKYY+9hKtTW4lxBbBsqgvPp/t\\n" + |
||||
|
"qn50WZu7bYVPsxErXYTN1sHi17bp8bsl/qjAieZkiqw4TyxJF30El7Lb+BqD8opqSHeUlaxm68/s\\nQ8fh3xpfWxDLxa1j0Vt+a" + |
||||
|
"w18dadX3burYtjqPZo3L5Ny+Ph94dl0h4eeOceFPz1jqBwA8G/o3FPR\\n3zKCWnXgjy7Xqcspdu0v0lBSQHF5KVqwifnSDJZQDRZ" + |
||||
|
"A2qB9+Mgx1IgMMqaZwY+Kmfqyw5D7CVLk\\nilN/ecMPA3Ng5TLd8UvzUbr46Vxaun7JkwyfE3VgRC63DaLUeq08qVApggUSVgjK" + |
||||
|
"vz9IwpaNW3yL\\nb/01vjOVWRUVzUoj4of0A7VBERPOxHhDSFNjhj/GRbPInlR4mT7m5tUQoGQblhAByACKMvl0Omgr\\nASRACmav" + |
||||
|
"UcaxWrw05bn+8wdqtLvZuB8d+1wmhULgSUaQNH1jGz04UUsZhN3FWtEVFnO5TlHShFco\\nbhQK38J9Cblb+Lt4sgjSMcjX66VPpa" + |
||||
|
"PYxpQIVtiWTxfnWdkbJcFRKby5MBPVnL9qOL5npM/qmCcO\\nQUQkpK7iL9upbASLRZ9Oeu4GAiKg2H9AxJHC/gZ27LDQq7+REXnq" + |
||||
|
"zUAeZ/O3HDMXOA3amupgLCiC\\noZr6lUoAm3HbcOgP7KvduuqgOuzy5YfpHnG4IkQ/l0gVtT/5/DOVMTZAc3ORmeaVKoskdKzgri" + |
||||
|
"Zi\\nVFCBqUbH7DZdCTvMReGW3ykXFopMyYbclorXlVQICxcZEhdQ9XIfm3w2Qr48AYjuNReX7mP4IebB\\nudaM8vs3UZSpQd2gF" + |
||||
|
"V+Gl2GullPhCgXV+m0Ntpxu5pR3bwWCsEYpnN2nG7QXdtE+j7OgYoHK/geZ\\nAdfVCcrPCY265NmBzU8jH1haJCQdr7jlxrgKdtp" + |
||||
|
"DroKTJGh7CuuTq4oWe5fhyGlVBtF8dYByjGSD\\nNUhDzjrJLy0qhT4C6Q74TWe6Pr/LHDZyOhNsvXBl0BBamH96Ndn1QO0GMP8JO" + |
||||
|
"stzQFaRbXQ9uC1m\\nhsJkx0KqYQ1bC0dhLcfHCohcniP4OY/kF+dz1rGRrwkaiP96xpSpKzfbitUCrQ66ZdoBHwJYZuXn\\njpvqQ" + |
||||
|
"HDwWuFgARUvJ79ig+end1vv7k6DqqmsB1ZxC12hsv/oN/E8HtdT7zi9WcAeCAq6A5iKxdM4\\naKhrVDo7qgWNZYxVPn6q5XnZLDz" + |
||||
|
"OgnUu8TXGr8DD/Jk+HfnoOODMOIvHcozxsyReb/npTekxmYqF\\nC/NE5pXNmDdCsVFojO33l4PIrpfiK1TcGMwZeSF4upKX0id42" + |
||||
|
"SNnK2BPGKBFEmUsIXu7vWXHpxBd\\n8lIVmPNZ0vb/eXP2Fcom3v39ALVdMY93jgQI1BsuFh7U4N8YBKWbfAgrtFj/anujArNJcSg" + |
||||
|
"biWUr\\n1CFJjmAanPPuXaWHfU/3fIqQCFDt/8X8S0Xbzvt4dBe0LqRC7ObFIqLa2esaXcJ34bNgs3MgvdpV\\n6duebo7PNjX6ra0" + |
||||
|
"LHYbDUrSo3XO9NMBprWbToa+uKoDHgzsAVT0zffJTJSU6bG5zytta22mMKAOG\\nFmxwuU1z11/cltUMAxYd4nljt3tGBLhGRrWmvY" + |
||||
|
"dlQGRnqdMyhChvaFpkZB68rrvOTT9HQ08Pf2UT\\nz+A0dpXwZLpfwV7PzFHZRN4czA4LeUIqQVh3byfU0r/ReC26UEL4KUBU0hm6Y" + |
||||
|
"mDvuRM72QOWM7HD\\nNyMywtWUbOIdoslcKqa97FTu+bgXo4VS9c+gyQLY1SizSlPvxePewaoqMjzYT6TTsSZW0dAiOMdf\\nSUehT" + |
||||
|
"wNPvRs/NZBGPBXHKLTWZtiHE4WCCV9ghYXdzXxyL3N2BSYa2AM16wGQsDb201nmRsDWPFD8\\ngW9ZBuEMIB80XqdmYeWNHA+tWtH8" + |
||||
|
"Le59WyBNolNbjXxIZCL7HGT3qKH1McUxHLJrhL4UWFbg2u4H\\nYBX+QbI5li7ugK+mTlXJZ/eW81PmSUFkaTVLM+BrWseGsbjmZBD" + |
||||
|
"PT8aBk9QAb1j1Wyka9yX6NZ20\\nQEeijYAUWU3KlsOsrfSPqyrVoO9deyc8sTPdtxUzbJCtxGcHKoNwEG04/WF1TrNa5eZrPoYW1F" + |
||||
|
"0Q\\ne9+U4BnPSn8Fe6M9KysLT5Xu0XTGhbYhuRGXJFlU1JbfYxywlUs4r0nznodZFrrhmWV7MzavKUGQ\\n+/i2nuQDbjdz3Fxz8Z" + |
||||
|
"05lH22WnYmiGEiK+fwb8Z7JINT6a5uEYGchpXJYQB6t/ucjPRnE+Emcyco\\nt4oLVP3jaSrebQgg7TIXL84Ib9we/YbT63u0LDZp95" + |
||||
|
"jnZkrmjBPSz9YhVuD/oRgznuVjm8L5VTkS\\nWCQZ0opBwYfaW/NVaZhg5cDVcv+4wRCLp4S3zmMerM02FaUtoxShK9VwQ6K4nq4al"; |
||||
|
|
||||
|
|
||||
|
|
||||
|
// 2. 文档第6页标注的样例密钥
|
||||
|
String secret = "axjGikuWgvYkI3JA"; |
||||
|
|
||||
|
// 第一步:Base64解码获取密文字节
|
||||
|
byte[] cipherText = Base64.getDecoder().decode(rawContent); |
||||
|
|
||||
|
// 第二步:AES解密
|
||||
|
SecretKeySpec skeySpec = new SecretKeySpec(secret.getBytes("UTF-8"), "AES"); |
||||
|
// 文档第6页底部显示:ivBytes 16个0
|
||||
|
byte[] ivBytes = new byte[16]; |
||||
|
IvParameterSpec iv = new IvParameterSpec(ivBytes); |
||||
|
|
||||
|
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); |
||||
|
cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv); |
||||
|
|
||||
|
// 解密后得到的内容(其实是一串 Base64 文本)
|
||||
|
byte[] decryptedTextBytes = cipher.doFinal(cipherText); |
||||
|
String base64PdfText = new String(decryptedTextBytes, "UTF-8"); |
||||
|
|
||||
|
// 第三步:【关键】对解密出来的文本再次进行 Base64 解码,得到 PDF 二进制流
|
||||
|
byte[] pdfFileBytes = Base64.getDecoder().decode(base64PdfText); |
||||
|
|
||||
|
// 保存文件到本地
|
||||
|
Files.write(Paths.get("SF_POD_SUCCESS.pdf"), pdfFileBytes); |
||||
|
System.out.println(">>> 解密成功!文件已生成:SF_POD_SUCCESS.pdf"); |
||||
|
} |
||||
|
} |
||||
Loading…
Reference in new issue