需求简介
因响应国家号召,需要对提现用户进行实名认证,最终采用了公司内部支付部门的接口(便宜~~)。
然后就出现了这个奇怪的对账逻辑。
首先调用支付的对账接口,返回一个加密的zip文件的字节流。 然后zip文件里面是昨天的excel文件,excel里面是昨日
的交易流水。
其次,我方生成一个excel 包含两个sheet,第一个sheet是当日的交易笔数以及交易金额(我方调用支付方,每次要收费).
第二个sheet里面是交易异常的流水。
最后,每日通过邮箱定时发送第二步生成的对账文件。
吐槽
- 不提供每日交易总账接口,最好的流程是先看总数是否一致,不一致再对流水。
- 流水不是以接口分页的形式进行,而是文件形式。 若是请求暴增,流水暴增靠文件流传输,是否稳妥。
记录的意义
这个需求涉及了以下功能
- 二进制流的加密解密。
- java读取zip文件,直接提取内部文件
- excel解析,excel生成。
- java发送邮件功能。
这些功能在日常的业务开发中都是很少用到的, 属于下次做还是从头查起的开发逻辑。
代码逻辑
- 获取二进制流并做加解密处理
/**
* 二进制流加密解密
*
*/
public String getFile(String date) {
File decryptFile;
Map parames = builderParames(date);
try {
HttpEntity httpEntity = HttpClientUtil.invokeGetRequestByte(url, parames);
InputStream input = httpEntity.getContent();
//密文文件名
Path balanceFilePath = Paths.get(System.getProperty("server_log_home")).getParent().resolve("trade");
File parent = balanceFilePath.toFile();
File encryptFile = balanceFilePath.resolve("encrypt-" + date).toFile();
if (!encryptFile.exists()) {
parent.mkdirs();
encryptFile.createNewFile();
}
//明文文件名
decryptFile = balanceFilePath.resolve("decrypt-" + date + ".zip").toFile();
if (!decryptFile.exists()) {
parent.mkdirs();
decryptFile.createNewFile();
}
//密文数据流
OutputStream encryptOuput = new FileOutputStream(encryptFile);
//明文数据流
OutputStream decryptOuput = new FileOutputStream(decryptFile);
if (input != null) {
IOUtils.copy(input, encryptOuput);
encryptOuput.close();
input.close();
InputStream encryptIS = new FileInputStream(encryptFile);
//解密-ras加密工具类,这个满大街都是
RSAUtilsNew.decrypt(encryptIS, decryptOuput, Base64.decodeBase64(AuthNameMapUtil.publicKey), RSAUtils.PUBLIC_KEY);
decryptOuput.close();
encryptIS.close();
} else {
ApiLogger.info("获取对账文件流为空");
return "";
}
} catch ( Exception e ) {
e.printStackTrace();
return "";
}
return decryptFile.getPath();
}
/**
* httpclient
*
* @param url
* @param parames
* @return
*/
public static HttpEntity invokeGetRequestByte(String url, Object parames) {
try {
URIBuilder uriBuilder = getUriBuilder(url, parames);
HttpGet httpGet = new HttpGet(uriBuilder.build());
httpGet.setConfig(requestConfig);
CloseableHttpClient closeableHttpClient = getCloseableHttpClient();
//注意这个头部配置
httpGet.setHeader("Content-type", "*/*");
CloseableHttpResponse response = closeableHttpClient.execute(httpGet);
return response.getEntity();
} catch ( URISyntaxException | IOException e1 ) {
e1.printStackTrace();
}
return null;
}
/**
* RSA解密
*
* @param inStream
* 密文输入流
* @param outStream
* 明文输出流
* @param keyByte
* 密钥
* @param keyType
* 密钥类型(公钥/私钥)
* @throws InvalidKeySpecException
* @throws InvalidKeyException
* @throws IOException
*/
public static void decrypt(InputStream inStream, OutputStream outStream, byte[] keyByte, String keyType) throws InvalidKeySpecException, IOException, InvalidKeyException {
try {
Key key = null;
if (keyType.equals(PUBLIC_KEY)) {
key = convertPublicKey(keyByte);
}
if (keyType.equals(PRIVATE_KEY)) {
key = convertPrivateKey(keyByte);
}
Cipher cipher = Cipher.getInstance(KEY_ALGORITHM);
cipher.init(Cipher.DECRYPT_MODE, key);
byte[] tmp = new byte[MAX_DECRYPT_BLOCK];
byte[] cache;
int len ;
// 对数据分段解密
while (-1 != (len = inStream.read(tmp))) {
cache = cipher.doFinal(tmp, 0, len);
outStream.write(cache, 0, cache.length);
}
outStream.close();
inStream.close();
} catch ( NoSuchAlgorithmException | NoSuchPaddingException | IllegalBlockSizeException | BadPaddingException e) {
log.error(e.getMessage());
}
}
2、解析zip,提取文件
/**
* 没有复用的可能直接沙盒处理
*
* @param decryptFilePath
* @throws IOException
*/
private void zipAnalysisBill(String decryptFilePath) throws IOException {
//解析
ZipFile zipFile = new ZipFile(decryptFilePath);
Enumeration enumeration = zipFile.entries();
if (enumeration == null) {
ApiLogger.info("获取的zip文件有误!!!");
return;
}
ZipEntry zipEntry = zipFile.entries().nextElement();
if (zipEntry != null) {
InputStream inputStream = zipFile.getInputStream(zipEntry);
//真正的对账逻辑触发器
ExcelReader excelReader = new ExcelReader(inputStream, ExcelTypeEnum.XLSX, SohuPayBillModel.class, analysisSohuPaySendExcelListener);
excelReader.read();
}
}
3、 easyExcel 解析excel
/**
* 对应的解析model
*/
@Data
public class SohuPayBillModel extends BaseRowModel {
@ExcelProperty(value = {"产品ID"}, index = 0)
private String productId;
@ExcelProperty(value = {"产品名称"}, index = 1)
private String produceName;
@ExcelProperty(value = {"用户名"}, index = 2)
private String userName;
@ExcelProperty(value = {"业务订单号"}, index = 3)
private String orederId;
@ExcelProperty(value = {"交易流水号"}, index = 4)
private String psTransId;
@ExcelProperty(value = {"钱包流水号"}, index = 5)
private String transId;
@ExcelProperty(value = {"认证渠道"}, index = 6)
private String channel;
@ExcelProperty(value = {"手续费"}, index = 7)
private double fee;
@ExcelProperty(value = {"处理状态"}, index = 8)
private String tradeStatus;
@ExcelProperty(value = {"认证状态"}, index = 9)
private String verifyStatus;
@ExcelProperty(value = {"提交时间"}, index = 10)
private String commitTime;
@ExcelProperty(value = {"更新时间"}, index = 11)
private String updateTime;
@ExcelProperty(value = {"备注"}, index = 12)
private String desrc;
}
/**
* Created with IntelliJ IDEA.
* Description:
* 解析响应器
*
* @author: rd
* @Date: 2019-06-27
*/
@Service
public class AnalysisSohuPaySendExcelListener extends AnalysisEventListener {
private List<SohuPayBillModel> sohuPayBillModelList = Lists.newArrayList();
@Autowired
AuthRealNameRedisOperatorService realNameRedisOperatorService;
@Autowired
GenerateCheckBillExcelService generateCheckBillExcelService;
@Autowired
SohuMailService sohuMailService;
/**
* @param object one row data
* @param context analysis context
*/
@Override
public void invoke(Object object, AnalysisContext context) {
SohuPayBillModel sohuPayBillModel = new SohuPayBillModel();
ArrayList<String> arrayList = (ArrayList<String>) object;
if (arrayList.size() <= 13) {
ApiLogger.info("[解析zip] 出现空行!!!");
return;
}
sohuPayBillModel.setProductId(arrayList.get(0));
sohuPayBillModel.setProduceName(arrayList.get(1));
sohuPayBillModel.setUserName(arrayList.get(2));
sohuPayBillModel.setOrederId(arrayList.get(3));
sohuPayBillModel.setPsTransId(arrayList.get(4));
sohuPayBillModel.setTransId(arrayList.get(5));
sohuPayBillModel.setChannel(arrayList.get(6));
String feeString = arrayList.get(7);
try {
double fee = Double.parseDouble(feeString);
sohuPayBillModel.setFee(fee);
} catch ( Exception e ) {
ApiLogger.info(feeString);
sohuPayBillModel.setFee(0);
}
sohuPayBillModel.setTradeStatus(arrayList.get(8));
sohuPayBillModel.setVerifyStatus(arrayList.get(9));
sohuPayBillModel.setCommitTime(arrayList.get(10));
sohuPayBillModel.setUpdateTime(arrayList.get(11));
sohuPayBillModel.setDesrc(arrayList.get(12));
sohuPayBillModelList.add(sohuPayBillModel);
}
/**
* if have something to do after all analysis
*
* @param context
*/
@Override
public void doAfterAllAnalysed(AnalysisContext context) {
ApiLogger.info("[解析zip] 解析完成,开始对账逻辑");
//此处是校验逻辑
CheckBillModel checkBillModel = checkResult(sohuPayBillModelList, DateUtil.getYesterday());
//生成excel逻辑
String path = generateCheckBillExcelService.writeExcel(sohuPayBillModelList, checkBillModel);
//此处是发送邮件逻辑
sohuMailService.sendMessage(path);
sohuPayBillModelList.clear();
}
}
***************************************************生成excel**********************
public String writeExcel(List<SohuPayBillModel> sohuPayBillModelList, CheckBillModel checkBillModel) {
File file = Paths.get(System.getProperty("server_log_home")).getParent().resolve("trade").resolve("3602-" + DateUtil.getYesterday() + ".xlsx").toFile();
try {
if (!file.exists()) {
file.createNewFile();
}
OutputStream out = new FileOutputStream(file);
ExcelWriter writer = new ExcelWriter(out, ExcelTypeEnum.XLSX);
Sheet checkBillSheet = new Sheet(1, 1, CheckBillModel.class);
checkBillSheet.setSheetName("对账汇总");
Sheet diffBillSheet = new Sheet(2, 1, DiffBillModel.class);
diffBillSheet.setSheetName("差异订单明细");
writer.write(Lists.newArrayList(checkBillModel), checkBillSheet);
//差异订单明细
List<DiffBillModel> diffBillModels = getCheckDiffModeList(sohuPayBillModelList, checkBillModel);
writer.write(diffBillModels, diffBillSheet);
writer.finish();
} catch ( Exception e ) {
e.printStackTrace();
return "";
}
return file.getPath();
}
- java发送邮件功能
public void sendMessage(String path) {
// 基础配置功能
Session session = Session.getInstance(properties);
Transport transport;
try {
transport = session.getTransport();
transport.connect("发件人邮箱账号", "发件人邮箱密码");
Message msg = getMimeMessage(session, path);
transport.sendMessage(msg, new Address[]{
new InternetAddress("收件人"),
});
transport.close();
} catch ( MessagingException | UnsupportedEncodingException e ) {
e.printStackTrace();
}
}
private static Message getMimeMessage(Session session, String path) throws MessagingException, UnsupportedEncodingException {
MimeMessage msg = new MimeMessage(session);
msg.setFrom(new InternetAddress("发件人"));
msg.setRecipients(MimeMessage.RecipientType.TO, new Address[]{"收件人"
});
msg.setSubject("日常对账");
MimeBodyPart textContent = new MimeBodyPart();
textContent.setContent("hi,all: <br/> 今日对账详情请看附件!", "text/html;charset=UTF-8");
MimeBodyPart attachment = new MimeBodyPart();
DataHandler dataHandler = new DataHandler(new FileDataSource(path));
attachment.setDataHandler(dataHandler);
attachment.setFileName(dataHandler.getName());
MimeMultipart mimeMultipart = new MimeMultipart();
mimeMultipart.addBodyPart(textContent);
mimeMultipart.addBodyPart(attachment);
msg.setContent(mimeMultipart);
msg.setSentDate(new Date());
return msg;
}