Commit 3766188a authored by chenzhao's avatar chenzhao

工行代扣

parent 45cb1f33
......@@ -5,10 +5,7 @@ import org.apache.commons.lang3.StringUtils;
import java.text.ParseException;
import java.text.ParsePosition;
import java.text.SimpleDateFormat;
import java.time.Duration;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.*;
import java.time.format.DateTimeFormatter;
import java.util.*;
......@@ -853,6 +850,18 @@ public class DateUtils {
}
/**
* 获取现在日期字符串时间戳格式
*
* @return返回字符串格式 yyyy-MM-dd
*/
public static String getDateNowShortNumberN() {
Date currentTime = new Date();
SimpleDateFormat formatter = new SimpleDateFormat(DATE_PATTERN);
String dateString = formatter.format(currentTime);
return dateString;
}
/**
* 获取一年的第几周
*
* @param date
......@@ -1166,4 +1175,23 @@ public class DateUtils {
private static boolean shouldContinue(int i, int num, Boolean bool) {
return bool ? i < Math.abs(num) : i <=Math.abs(num) ;
}
/**
* 获取指定年度和季度包含的月份
* */
public static List<String> getMonthsInQuarterAsString(int year, int quarter) {
if (quarter < 1 || quarter > 4) {
throw new IllegalArgumentException("Quarter must be between 1 and 4.");
}
List<String> monthsInQuarter = new ArrayList<>();
for (int i = (quarter - 1) * 3 + 1; i <= quarter * 3; i++) {
Month month = Month.of(i);
// 格式化为 "yyyy-MM"
String formattedMonth = String.format("%d-%02d", year, month.getValue());
monthsInQuarter.add(formattedMonth);
}
return monthsInQuarter;
}
}
......@@ -17,6 +17,10 @@ import javax.xml.transform.stream.StreamResult;
import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import static org.springframework.util.FileCopyUtils.BUFFER_SIZE;
/**
......@@ -33,6 +37,12 @@ public class WordConverterUtils {
return multipartFile;
}
public static MultipartFile fileToMultipartFileZip(File file) throws IOException {
FileItem fileItem = createFileItemZip(file);
MultipartFile multipartFile = new CommonsMultipartFile(fileItem);
return multipartFile;
}
private static FileItem createFileItem(File file) {
FileItemFactory factory = new DiskFileItemFactory(16, null);
FileItem item = factory.createItem("textField", "text/plain", true, file.getName());
......@@ -49,6 +59,66 @@ public class WordConverterUtils {
return item;
}
private static boolean isZipFile(File file) {
try (InputStream is = new BufferedInputStream(new FileInputStream(file))) {
byte[] signature = new byte[4];
int length = is.read(signature);
return length == 4 && (signature[0] == 0x50 && signature[1] == 0x4B && signature[2] == 0x03 && signature[3] == 0x04);
} catch (IOException e) {
e.printStackTrace();
return false;
}
}
public static FileItem createFileItemZip(File file) throws IOException {
FileItemFactory factory = new DiskFileItemFactory(16, null);
String mimeType = getMimeType(file);
// 创建 FileItem
FileItem item = factory.createItem("textField", mimeType, true, file.getName());
try (FileInputStream fis = new FileInputStream(file);
OutputStream os = item.getOutputStream()) {
if (isZipFile(file)) {
handleZipFile(fis, os);
} else {
copyStream(fis, os);
}
}
return item;
}
private static void handleZipFile(InputStream fis, OutputStream os) throws IOException {
try (ZipInputStream zis = new ZipInputStream(fis)) {
ZipEntry entry;
while ((entry = zis.getNextEntry()) != null) {
if (!entry.isDirectory()) {
copyStream(zis, os);
}
zis.closeEntry();
}
}
}
private static void copyStream(InputStream input, OutputStream output) throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int bytesRead;
while ((bytesRead = input.read(buffer, 0, BUFFER_SIZE)) != -1) {
output.write(buffer, 0, bytesRead);
}
}
private static String getMimeType(File file) {
String fileName = file.getName().toLowerCase();
if (fileName.endsWith(".zip")) {
return "application/zip";
} else {
// 这里可以根据文件扩展名返回其他 MIME 类型,或者使用更复杂的 MIME 类型检测机制
return "application/octet-stream";
}
}
......
......@@ -24,5 +24,30 @@
<artifactId>taos-jdbcdriver</artifactId>
<version>3.2.4</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version> <!-- 请根据需要选择最新版本 -->
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.70</version>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
</dependency>
</dependencies>
</dependencyManagement>
</project>
package com.yeejoin.amos.boot.module.hygf.api.Enum;
import lombok.AllArgsConstructor;
import lombok.Getter;
@Getter
@AllArgsConstructor
public enum UploadStatusEnum {
未上传("未上传","未上传"),
已上传("上传中","上传中"),
未生成("未生成","未生成"),
成功("成功","成功"),
失败("失败","失败"),
代扣处理中("代扣处理中","代扣处理中");
private String name;
private String code;
}
......@@ -124,6 +124,17 @@ public class HygfIcbcRecordDTO {
@ApiModelProperty(value = "电站信息")
private List<IcbcPeasantHousehold> peasantHouseholds;
/*
* 区域公司code
* */
@ApiModelProperty(value = "区域公司code")
private String regionalCompaniesCode;
/*
* 区域公司名称
* */
@ApiModelProperty(value = "区域公司名称")
private String regionalCompaniesName;
@Data
@AllArgsConstructor
@NoArgsConstructor
......@@ -168,5 +179,6 @@ public class HygfIcbcRecordDTO {
@ApiModelProperty(value = "实际安装规模(kW)")
private String realScale;
}
@ApiModelProperty (value = "代扣金额")
private double paymentAmount;
}
\ No newline at end of file
package com.yeejoin.amos.boot.module.hygf.api.dto;
import com.alibaba.excel.annotation.ExcelIgnore;
import com.alibaba.excel.annotation.ExcelProperty;
import com.baomidou.mybatisplus.annotation.TableField;
import com.baomidou.mybatisplus.extension.handlers.FastjsonTypeHandler;
import com.yeejoin.amos.boot.module.hygf.api.entity.PeasantHousehold;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import javax.validation.constraints.NotBlank;
import java.util.Date;
import java.util.List;
/**
* (hygf_icbc_record)实体类
*
* @author yangyang
* @description
* @since 2024-07-18 11:40:46
*/
@Data
@NoArgsConstructor
@ApiModel (value = "HygfIcbcRecordExportDTO", description = "聚富通钱包开户DTO")
public class HygfIcbcRecordExportDTO {
@ExcelIgnore
private static final long serialVersionUID = 1L;
@ExcelIgnore
protected Long sequenceNbr;
@ExcelProperty(value = "户主姓名", index = 0)
@ApiModelProperty (value = "户主姓名")
private String custName;
/**
* 身份证号
*/
@ExcelProperty(value = "区域公司", index = 1)
@ApiModelProperty(value = "区域公司")
private String regionalCompaniesName;
/**
* 手机号码
*/
@ExcelProperty(value = "手机号", index = 2)
@ApiModelProperty (value = "手机号码")
private String phone;
@ExcelProperty(value = "身份证号", index = 3)
@ApiModelProperty (value = "身份证号")
private String idCard;
/**
* 聚富通电子账户账号
*/
@ExcelProperty(value = "银行卡号", index = 4)
@ApiModelProperty (value = "聚富通电子账户账号")
private String mediumId;
/**
* 开户状态, 00-初始,01-开户中,02-开户成功,03-开户失败
*/
@ExcelProperty(value = "开户状态", index = 5)
@ApiModelProperty (value = "开户状态, 00-初始,01-开户中,02-开户成功,03-开户失败")
private String openAccountStatus;
/**
* 协议状态, 0-未生效,1-已生效,2-过期,3-作废,4-待短信确认
*/
@ExcelProperty(value = "协议状态", index = 6)
@ApiModelProperty (value = "协议状态, 0-未生效,1-已生效,2-过期,3-作废,4-待短信确认")
private String protocolStatus;
}
\ No newline at end of file
package com.yeejoin.amos.boot.module.hygf.api.dto;
import lombok.Data;
@Data
public class ResultLinkField {
/**
* 地区编号 5
*
*/
private String zoneNo;
/**
* 批次号 18
*/
private String batchNo;
/**
* 序号 30
*/
private String billNo;
/**
* 状态 1 0成功 1失败
*/
private String status;
/**
* 错误信息 500
*/
private String errMsg;
/**
* 导入时间 20
*/
private String timestamp;
/**
* 实扣金额(单位:分) 17
*/
private String amt;
}
package com.yeejoin.amos.boot.module.hygf.api.dto;
import lombok.Data;
@Data
public class WithholdFirstLinkField {
/**
* 总金额(单位:分)
* 非负整数,无小数位
*/
private int totalAmt;
/**
* 总笔数
*/
private int totalNum;
/**
* 批次号 同文件名后18位
*/
private String batchNo;
/**
* 企业CIS号
*/
private String corpCis;
/**
* 缴费大类
*/
private String payClass;
/**
* 缴费小类
*/
private String paySubClass;
/**
* 缴费项目ID
*/
private String projectId;
/**
* 账单别名 最长10个中文
*/
private String billName;
/**
* 摘要
*/
private String summary;
/**
* 自定义字段 50位长度1000010格式,第一位表示
* 是否启用item50作为回单自定义字
* 段。0-否,1-是
*/
private String customField;
}
package com.yeejoin.amos.boot.module.hygf.api.dto;
import lombok.Data;
@Data
public class WithholdLoopField {
/**
* 序号,不能重复
*/
private int billNo;
/**
* 业务编号
*/
private String busiCode;
/**
* 付款人账号
* 付方账号,会与对应委托代扣协议做校验
*/
private String busiAcct;
/**
*付款人户
* 名
*/
private String busiName;
/**
* 付款人地
* 址
*/
private String busiAddr;
/**
* 付款人手
* 机
*/
private String busiTel;
/**
* 付款人邮
* 箱
*/
private String busiEmail;
/**
* 收款企业
* 入账账号
*/
private String actualAcctNo;
/**
* 出账日期
*/
private String billDate;
/**
* 明细失效
* 日期
*/
private String billCaeseDate;
/**
* 应缴金额
* (单位:
* 分)
*/
private int captAmount;
/**
* 查询业务
* 要素1
*/
private String query_item1;
/**
* 查询业务
* 要素2
*/
private String query_item2;
/**
* 查询业务
* 要素3
*/
private String query_item3;
/**
* 查询业务
* 要素4
*/
private String query_item4;
/**
* 查询业务
* 要素5
*/
private String query_item5;
/**
* 查询业务
* 要素6
*/
private String query_item6;
/**
* 查询业务
* 要素7
*/
private String query_item7;
/**
* 查询业务
* 要素8
*/
private String query_item8;
/**
* 查询业务
* 要素9
*/
private String query_item9;
/**
* 查询业务
* 要素10
*/
private String query_item10;
/**
* 查询业务
* 要素11
*/
private String query_item11;
/**
* 查询业务
* 要素12
*/
private String query_item12;
/**
* 查询业务
* 要素13
*/
private String query_item13;
/**
* 查询业务
* 要素14
*/
private String query_item14;
/**
* 查询业务
* 要素15
*/
private String query_item15;
/**
* 缴费业务
* 要素1
*/
private String item1;
/**
* 缴费业务
* 要素2
*/
private String item2;
/**
* 缴费业务
* 要素3
*/
private String item3;
/**
* 缴费业务
* 要素4
*/
private String item4;
/**
* 缴费业务
* 要素5
*/
private String item5;
/**
* 缴费业务
* 要素6
*/
private String item6;
/**
* 缴费业务
* 要素7
*/
private String item7;
/**
* 缴费业务
* 要素8
*/
private String item8;
/**
* 缴费业务
* 要素9
*/
private String item9;
/**
* 缴费业务
* 要素10
*/
private String item10;
/**
* 缴费业务
* 要素11
*/
private String item11;
/**
* 缴费业务
* 要素12
*/
private String item12;
/**
* 缴费业务
* 要素13
*/
private String item13;
/**
* 缴费业务
* 要素14
*/
private String item14;
/**
* 缴费业务
* 要素15
*/
private String item15;
/**
* 缴费业务
* 要素16
*/
private String item16;
/**
* 缴费业务
* 要素17
*/
private String item17;
/**
* 缴费业务
* 要素18
*/
private String item18;
/**
* 缴费业务
* 要素19
*/
private String item19;
/**
* 缴费业务
* 要素20
*/
private String item20;
/**
* 缴费业务
* 要素21
*/
private String item21;
/**
* 缴费业务
* 要素22
*/
private String item22;
/**
* 缴费业务
* 要素23
*/
private String item23;
/**
* 缴费业务
* 要素24
*/
private String item24;
/**
* 缴费业务
* 要素25
*/
private String item25;
/**
* 缴费业务
* 要素26
*/
private String item26;
/**
* 缴费业务
* 要素27
*/
private String item27;
/**
* 缴费业务
* 要素28
*/
private String item28;
/**
* 缴费业务
* 要素29
*/
private String item29;
/**
* 缴费业务
* 要素30
*/
private String item30;
/**
* 缴费业务
* 要素31
*/
private String item31;
/**
* 缴费业务
* 要素32
*/
private String item32;
/**
* 缴费业务
* 要素33
*/
private String item33;
/**
* 缴费业务
* 要素34
*/
private String item34;
/**
* 缴费业务
* 要素35
*/
private String item35;
/**
* 缴费业务
* 要素36
*/
private String item36;
/**
* 缴费业务
* 要素37
*/
private String item37;
/**
* 缴费业务
* 要素38
*/
private String item38;
/**
* 缴费业务
* 要素39
*/
private String item39;
/**
* 缴费业务
* 要素40
*/
private String item40;
/**
* 缴费业务
* 要素41
*/
private String item41;
/**
* 缴费业务
* 要素42
*/
private String item42;
/**
* 缴费业务
* 要素43
*/
private String item43;
/**
* 缴费业务
* 要素44
*/
private String item44;
/**
* 缴费业务
* 要素45
*/
private String item45;
/**
* 缴费业务
* 要素46
*/
private String item46;
/**
* 缴费业务
* 要素47
*/
private String item47;
/**
* 缴费业务
* 要素48
*/
private String item48;
/**
* 缴费业务
* 要素49
*/
private String item49;
/**
* 缴费业务
* 要素1
*/
private String item50;
/**
* 地区
*/
private String depzone;
/**
* 网点
*/
private String depbrno;
/**
* 代理业务
* 编号
*/
private String agentno;
/**
* 缴费客户
* 注册号
*/
private String CUSTOMER_NO;
/**
* 客户类型
*/
private String CUSTOMER_TYPE;
}
......@@ -4,6 +4,7 @@ import com.baomidou.mybatisplus.core.mapper.BaseMapper;
import com.yeejoin.amos.boot.module.hygf.api.config.UserEmpower;
import com.yeejoin.amos.boot.module.hygf.api.dto.AcceptanceDto;
import com.yeejoin.amos.boot.module.hygf.api.dto.HygfIcbcRecordDTO;
import com.yeejoin.amos.boot.module.hygf.api.dto.HygfIcbcRecordExportDTO;
import com.yeejoin.amos.boot.module.hygf.api.dto.HygfIcbcRecordQueryDTO;
import com.yeejoin.amos.boot.module.hygf.api.entity.HygfIcbcRecord;
import org.apache.ibatis.annotations.Mapper;
......@@ -28,4 +29,7 @@ public interface HygfIcbcRecordMapper extends BaseMapper<HygfIcbcRecord> {
@UserEmpower (field = {"ph.regional_companies_code"}, dealerField = {"ph.developer_code", "ph.regional_companies_code", "ph.developer_user_id"}, fieldConditions = {"in", "in", "in"}, relationship = "and")
List<HygfIcbcRecordDTO> pageList(@Param ("param") HygfIcbcRecordQueryDTO hygfIcbcRecordQueryDTO);
@UserEmpower (field = {"ph.regional_companies_code"}, dealerField = {"ph.developer_code", "ph.regional_companies_code", "ph.developer_user_id"}, fieldConditions = {"in", "in", "in"}, relationship = "and")
List<HygfIcbcRecordExportDTO> exportTotal(String developerCode, String regionalCompaniesCode, String province, String city, String district);
}
......@@ -4,6 +4,7 @@ package com.yeejoin.amos.boot.module.hygf.api.service;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.util.Map;
/**
......@@ -13,4 +14,5 @@ import javax.servlet.http.HttpServletResponse;
* @date 2024-01-12
*/
public interface IHygfIcbcService {
Map<String,Long> exportTotal(String developerCode, String regionalCompaniesCode, String province, String city, String district);
}
......@@ -6,6 +6,14 @@ import java.nio.ByteBuffer;
import java.nio.MappedByteBuffer;
import java.nio.channels.FileChannel;
import java.nio.channels.FileChannel.MapMode;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Comparator;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import java.util.zip.ZipEntry;
import java.util.zip.ZipOutputStream;
public class FileUtils {
public byte[] getContent(String filePath) throws IOException {
......@@ -32,6 +40,74 @@ public class FileUtils {
return buffer;
}
/**
* 创建ZIP文件
*
* @param filesToZip 要打包的文件列表
* @param zipPath ZIP文件保存路径
* @throws IOException 如果I/O操作失败
*/
public static void createZipFile(List<Path> filesToZip, String zipPath) throws IOException {
try (FileOutputStream fos = new FileOutputStream(zipPath);
ZipOutputStream zos = new ZipOutputStream(fos)) {
for (Path file : filesToZip) {
FileInputStream fis = new FileInputStream(file.toFile());
ZipEntry zipEntry = new ZipEntry(file.getFileName().toString());
zos.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length);
}
zos.closeEntry();
fis.close();
}
}
}
/**
*
* 创建压缩包文件
**/
public static void addFileToZip(String filePath, String zipFileName, ZipOutputStream zos) throws IOException {
File file = new File(filePath);
FileInputStream fis = new FileInputStream(file);
ZipEntry zipEntry = new ZipEntry(zipFileName);
zos.putNextEntry(zipEntry);
byte[] bytes = new byte[1024];
int length;
while ((length = fis.read(bytes)) >= 0) {
zos.write(bytes, 0, length);
}
zos.closeEntry();
fis.close();
}
public static void cleanup(Path tempDir) {
try (Stream<Path> paths = Files.walk(tempDir)) {
List<Path> filesToDelete = paths.sorted(Comparator.reverseOrder())
.collect(Collectors.toList());
// 分批删除文件
int batchSize = 100; // 根据实际情况调整批次大小
for (int i = 0; i < filesToDelete.size(); i += batchSize) {
List<Path> batch = filesToDelete.subList(i, Math.min(i + batchSize, filesToDelete.size()));
batch.forEach(file -> {
try {
Files.delete(file);
} catch (IOException e) {
e.printStackTrace();
}
});
}
} catch (IOException e) {
e.printStackTrace();
}
}
/**
* the traditional io way
*
......@@ -174,4 +250,5 @@ public class FileUtils {
}
}
}
\ No newline at end of file
......@@ -3,6 +3,7 @@ package com.yeejoin.amos.boot.module.hygf.api.util;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.security.KeyFactory;
......@@ -16,7 +17,15 @@ package com.yeejoin.amos.boot.module.hygf.api.util;
import java.security.spec.X509EncodedKeySpec;
import org.apache.commons.codec.binary.Base64;
import org.bouncycastle.asn1.DERNull;
import org.bouncycastle.asn1.pkcs.PKCSObjectIdentifiers;
import org.bouncycastle.asn1.pkcs.PrivateKeyInfo;
import org.bouncycastle.asn1.pkcs.RSAPrivateKey;
import org.bouncycastle.asn1.x509.AlgorithmIdentifier;
import org.bouncycastle.util.io.pem.PemObject;
import org.bouncycastle.util.io.pem.PemReader;
import org.springframework.core.io.ClassPathResource;
/**
* SHA256WithRSA签名、验签工具
*
......@@ -64,6 +73,25 @@ public class RSASignUtils {
return signature.sign();
}
private static byte[] convertPKCS1ToPKCS8(byte[] pkcs1Bytes) throws Exception {
// 使用 BouncyCastle 库解析 PKCS#1 格式的私钥
RSAPrivateKey pkcs1PrivKey = RSAPrivateKey.getInstance(pkcs1Bytes);
// 构建 AlgorithmIdentifier,指定了 rsaEncryption OID 和空参数
AlgorithmIdentifier algId = new AlgorithmIdentifier(
PKCSObjectIdentifiers.rsaEncryption,
DERNull.INSTANCE
);
// 构建 PKCS#8 格式的私钥信息
PrivateKeyInfo pkcs8PrivKeyInfo = new PrivateKeyInfo(algId, pkcs1PrivKey.toASN1Primitive());
// 返回 PKCS#8 格式的编码字节数组
return pkcs8PrivKeyInfo.getEncoded();
}
/**
* 验签
*
......@@ -121,7 +149,9 @@ public class RSASignUtils {
* @throws Exception
*/
public static PrivateKey loadPrivateKey(String path) throws Exception {
PemReader pemReader = new PemReader(new InputStreamReader(new FileInputStream(path)));
ClassPathResource classPathResource = new ClassPathResource(path);
PemReader pemReader = new PemReader(new InputStreamReader(classPathResource.getInputStream()));
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(pemReader.readPemObject().getContent());
pemReader.close();
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
......@@ -143,6 +173,44 @@ public class RSASignUtils {
}
/**
* 从指定路径加载并转换 PKCS#1 格式的 RSA 私钥为 PKCS#8 格式。
*
* @param path 私钥文件的路径。该文件应为 PEM 格式,并包含 PKCS#1 格式的 RSA 私钥信息。
* @return 解析并转换后的 PrivateKey 对象,用于后续的加密或签名操作。
* @throws Exception 如果读取文件、解析或转换私钥时发生错误。
*/
public static PrivateKey loadPrivateKeyNew(String path) throws Exception {
try (FileInputStream fis = new FileInputStream(path);
InputStreamReader isr = new InputStreamReader(fis);
PemReader pemReader = new PemReader(isr)) {
// 读取 PEM 文件中的对象
PemObject pemObject = pemReader.readPemObject();
if (pemObject == null) {
throw new IOException("PEM file is empty or not in the correct format: " + path);
}
// 获取编码后的私钥数据
byte[] encoded = pemObject.getContent();
// 将 PKCS#1 转换为 PKCS#8
byte[] pkcs8Key = convertPKCS1ToPKCS8(encoded);
// 创建 PKCS#8 编码规范
PKCS8EncodedKeySpec pkcs8EncodedKeySpec = new PKCS8EncodedKeySpec(pkcs8Key);
// 获取 RSA 算法的 KeyFactory 实例,用于生成私钥对象
KeyFactory keyFactory = KeyFactory.getInstance("RSA", "BC");
// 使用 KeyFactory 和 PKCS#8 编码规范来生成并返回 PrivateKey 对象
return keyFactory.generatePrivate(pkcs8EncodedKeySpec);
} catch (IOException e) {
throw new IOException("Error reading private key from file: " + path, e);
} catch (Exception e) {
throw new Exception("Unexpected error while loading private key from file: " + path, e);
}
}
/**
* 从文件加载 PKCS8 格式的 RSA 公钥
*/
public static RSAPublicKey readPublicKeyFromFile(String path) throws Exception {
......
package com.yeejoin.amos.boot.module.hygf.api.util;
import java.io.*;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.*;
import java.util.zip.ZipEntry;
import java.util.zip.ZipInputStream;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelSftp;
import com.jcraft.jsch.ChannelSftp.LsEntry;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.JSchException;
import com.jcraft.jsch.Session;
import com.jcraft.jsch.SftpException;
import com.yeejoin.amos.boot.module.hygf.api.dto.ResultLinkField;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
/**
* 提供SFTP处理文件服务
*
* @author
* @date 2016-02-29
*/
public class SFTPUtil {
private static final Logger logger = LoggerFactory.getLogger(SFTPUtil.class);
private JSch jSch = null;
private ChannelSftp sftp = null;// sftp主服务
private Channel channel = null;
private Session session = null;
private String hostName ;// 远程服务器地址
private int port = 8001;// 端口
private String userName ;// 用户名
private String password ;// 密码
private String priKeyFile; // 密钥文件
private String passphrase; // 秘钥字符串
@Value("${icbc.Withhold.sftpIp:gw.open.icbc.com.cn}")
public String sftpIp ="gw.open.icbc.com.cn" ;
@Value("${icbc.Withhold.sftpPort:8001}")
public int sftpPort=8001;
@Value("${icbc.Withhold.sftpUserName:jrgf}")
public String sftpUserName="jrgf";
public SFTPUtil(String hostName, int port, String userName, String password) {
this.hostName = hostName;
this.port = port;
this.userName = userName;
this.password = password;
}
/**
* 密钥构造文件
*
* @param hostName
* @param port
* @param userName
* @param priKeyFile
* @param passphrase
*/
public SFTPUtil(String hostName, int port, String userName,
String priKeyFile, String passphrase) {
this.hostName = hostName;
this.port = port;
this.userName = userName;
this.password = null;
this.priKeyFile = priKeyFile;
this.passphrase = passphrase;
}
/**
* 连接登陆远程服务器
*
* @return
* @throws Exception
*/
public boolean connect() throws Exception {
try {
jSch = new JSch();
session = jSch.getSession(userName, hostName, port);
session.setPassword(password);
session.setConfig(this.getSshConfig());
session.connect();
channel = session.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
logger.info("登陆成功:" + sftp.getServerVersion());
} catch (JSchException e) {
logger.error("SSH方式连接FTP服务器时有JSchException异常!");
logger.error(e.getMessage());
throw e;
}
return true;
}
/**
* 密钥文件连接
*
* @return
*/
public boolean priKeyConnect() throws Exception {
JSch jsch = new JSch();
try {
if (priKeyFile != null && !"".equals(priKeyFile)) {
// 通过类加载器获取私钥文件输入流
InputStream inputStream = getClass().getClassLoader().getResourceAsStream(priKeyFile);
if (inputStream == null) {
throw new IOException("Private key file not found in classpath: " + priKeyFile);
}
// 创建临时文件并写入私钥内容
File tempPrivateKeyFile = File.createTempFile("temp_private_key", ".pem");
try (FileOutputStream fos = new FileOutputStream(tempPrivateKeyFile)) {
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
fos.write(buffer, 0, bytesRead);
}
} finally {
inputStream.close();
}
// 设置私钥文件权限(仅适用于 Unix/Linux 系统)
tempPrivateKeyFile.setReadable(true, false);
if (passphrase != null && !"".equals(passphrase)) {
jsch.addIdentity(tempPrivateKeyFile.getAbsolutePath(), passphrase);
} else {
jsch.addIdentity(tempPrivateKeyFile.getAbsolutePath());
}
}
if (port > 0) {
session = jsch.getSession(userName, hostName, port);
} else {
session = jsch.getSession(userName, hostName);
}
Properties config = new Properties();
config.put("StrictHostKeyChecking", "no");
session.setConfig(config);
// session.setTimeout(20000);
session.connect(3000);
channel = session.openChannel("sftp");
channel.connect();
sftp = (ChannelSftp) channel;
} catch (JSchException e) {
logger.error("SSH方式连接FTP服务器时有JSchException异常!");
logger.error(e.getMessage());
throw e;
}
return true;
}
/**
* 关闭连接
*
* @throws Exception
*/
private void disconnect() throws Exception {
try {
if (sftp.isConnected()) {
sftp.disconnect();
}
if (channel.isConnected()) {
channel.disconnect();
}
if (session.isConnected()) {
session.disconnect();
}
} catch (Exception e) {
throw e;
}
}
/**
* 获取服务配置
*
* @return
*/
private Properties getSshConfig() throws Exception {
Properties sshConfig = null;
try {
sshConfig = new Properties();
sshConfig.put("StrictHostKeyChecking", "no");
} catch (Exception e) {
throw e;
}
return sshConfig;
}
/**
* 下载远程sftp服务器文件
*
* @param remotePath
* @param remoteFilename
* @param localFilename
* @return
*/
public boolean downloadFile(String remotePath, String remoteFilename,
String localFilename) throws SftpException, IOException, Exception {
FileOutputStream output = null;
boolean success = false;
try {
if (null != remotePath && remotePath.trim() != "") {
sftp.cd(remotePath);
}
File localFile = new File(localFilename);
// 有文件和下载文件重名
if (localFile.exists()) {
logger.error("文件: " + localFilename + " 已经存在!");
return success;
}
output = new FileOutputStream(localFile);
sftp.get(remoteFilename, output);
success = true;
logger.info("成功接收文件,本地路径:" + localFilename);
} catch (SftpException e) {
logger.error("接收文件时有SftpException异常!");
logger.error(e.getMessage());
return success;
} catch (IOException e) {
logger.error("接收文件时有I/O异常!");
logger.error(e.getMessage());
return success;
} finally {
try {
if (null != output) {
output.close();
}
// 关闭连接
disconnect();
} catch (IOException e) {
logger.error("关闭文件时出错!");
logger.error(e.getMessage());
}
}
return success;
}
/**
* 下载远程SFTP服务器中包含特定名称的所有文件并打包到一个ZIP压缩包中
*
* @param remotePath 远程目录路径
* @param filenamePattern 文件名模式(例如,"xxx")
* @param localZipPath 本地ZIP文件保存路径
* @return 是否成功
*/
public boolean downloadAndZipFiles(String remotePath, String filenamePattern, String localZipPath) {
boolean success = false;
Path tempDir = null;
try {
// 创建临时目录
tempDir = Files.createTempDirectory("sftp_download_temp");
// 列出远程目录中的所有文件`
Vector<?> filesList = sftp.ls(remotePath);
List<Path> downloadedFiles = new ArrayList<>();
for (Object obj : filesList) {
ChannelSftp.LsEntry entry = (ChannelSftp.LsEntry) obj;
String filename = entry.getFilename();
if (filename.contains(filenamePattern)) {
Path localFilePath = tempDir.resolve(filename);
downloadFile(remotePath, filename, localFilePath.toString());
downloadedFiles.add(localFilePath);
}
}
// 打包下载的文件到ZIP文件
FileUtils.createZipFile(downloadedFiles, localZipPath);
success = true;
logger.info("成功接收文件并打包到ZIP文件: " + localZipPath);
} catch (Exception e) {
logger.error("下载或打包文件时发生异常!", e);
} finally {
try {
// 删除临时目录及其内容
if (tempDir != null) {
FileUtils.cleanup(tempDir);
}
disconnect();
} catch (IOException e) {
logger.error("删除临时目录时出错!", e);
} catch (Exception e) {
e.printStackTrace();
}
}
return success;
}
/**
* 上传文件至远程sftp服务器
*
* @param remotePath
* @param remoteFilename
* @param localFileName
* @return
*/
public boolean uploadFile(String remotePath, String remoteFilename,
String localFileName) throws SftpException, Exception {
boolean success = false;
FileInputStream fis = null;
try {
// 更改服务器目录
if (null != remotePath && remotePath.trim() != "") {
sftp.cd(remotePath);
}
File localFile = new File(localFileName);
fis = new FileInputStream(localFile);
// 发送文件
sftp.put(fis, remoteFilename);
success = true;
logger.info("成功发送文件,本地路径:" + localFileName);
} catch (SftpException e) {
logger.error("发送文件时有SftpException异常!");
e.printStackTrace();
logger.error(e.getMessage());
throw e;
} catch (Exception e) {
logger.error("发送文件时有异常!");
logger.error(e.getMessage());
throw e;
} finally {
try {
if (null != fis) {
fis.close();
}
// // 关闭连接
// disconnect();
} catch (IOException e) {
logger.error("关闭文件时出错!");
logger.error(e.getMessage());
}
}
return success;
}
/**
* 上传文件至远程sftp服务器
*
* @param remotePath
* @param remoteFilename
* @param input
* @return
*/
public boolean uploadFile(String remotePath, String remoteFilename,
InputStream input) throws SftpException, Exception {
boolean success = false;
try {
// 更改服务器目录
if (null != remotePath && remotePath.trim() != "") {
sftp.cd(remotePath);
}
// 发送文件
sftp.put(input, remoteFilename);
success = true;
} catch (SftpException e) {
logger.error("发送文件时有SftpException异常!");
e.printStackTrace();
logger.error(e.getMessage());
throw e;
} catch (Exception e) {
logger.error("发送文件时有异常!");
logger.error(e.getMessage());
throw e;
} finally {
try {
if (null != input) {
input.close();
}
// 关闭连接
disconnect();
} catch (IOException e) {
logger.error("关闭文件时出错!");
logger.error(e.getMessage());
}
}
return success;
}
/**
* 删除远程文件
*
* @param remotePath
* @param remoteFilename
* @return
* @throws Exception
*/
public boolean deleteFile(String remotePath, String remoteFilename)
throws Exception {
boolean success = false;
try {
// 更改服务器目录
if (null != remotePath && remotePath.trim() != "") {
sftp.cd(remotePath);
}
// 删除文件
sftp.rm(remoteFilename);
logger.error("删除远程文件" + remoteFilename + "成功!");
success = true;
} catch (SftpException e) {
logger.error("删除文件时有SftpException异常!");
e.printStackTrace();
logger.error(e.getMessage());
return success;
} catch (Exception e) {
logger.error("删除文件时有异常!");
logger.error(e.getMessage());
return success;
} finally {
// 关闭连接
disconnect();
}
return success;
}
/**
* 遍历远程文件
*
* @param remotePath
* @return
* @throws Exception
*/
public List<String> listFiles(String remotePath) throws SftpException {
List<String> ftpFileNameList = new ArrayList<String>();
Vector<LsEntry> sftpFile = sftp.ls(remotePath);
LsEntry isEntity = null;
String fileName = null;
Iterator<LsEntry> sftpFileNames = sftpFile.iterator();
while (sftpFileNames.hasNext()) {
isEntity = (LsEntry) sftpFileNames.next();
fileName = isEntity.getFilename();
logger.info(fileName);
ftpFileNameList.add(fileName);
}
return ftpFileNameList;
}
/**
* 判断路径是否存在
*
* @param remotePath
* @return
* @throws SftpException
*/
public boolean isExist(String remotePath) throws SftpException {
boolean flag = false;
try {
sftp.cd(remotePath);
logger.info("存在路径:" + remotePath);
flag = true;
} catch (SftpException sException) {
} catch (Exception Exception) {
}
return flag;
}
public String getHostName() {
return hostName;
}
public void setHostName(String hostName) {
this.hostName = hostName;
}
public int getPort() {
return port;
}
public void setPort(int port) {
this.port = port;
}
public String getUserName() {
return userName;
}
public void setUserName(String userName) {
this.userName = userName;
}
public String getPassword() {
return password;
}
public void setPassword(String password) {
this.password = password;
}
public String getPriKeyFile() {
return priKeyFile;
}
public void setPriKeyFile(String priKeyFile) {
this.priKeyFile = priKeyFile;
}
public String getPassphrase() {
return passphrase;
}
public void setPassphrase(String passphrase) {
this.passphrase = passphrase;
}
public boolean uploadAndUnzip(String remotePath, String remoteZipFilename, String localZipFilePath) throws Exception {
boolean success = false;
FileInputStream fis = null;
try {
// 上传 ZIP 文件
if (uploadFile(remotePath, remoteZipFilename, localZipFilePath)) {
// 解压 ZIP 文件
unzipFile(remotePath, remoteZipFilename);
success = true;
}
}catch (Exception e) {
logger.error("异常信息:" + e.getMessage());
}finally {
IOUtils.closeQuietly(fis);
disconnect();
}
return success;
}
private void unzipFile(String remotePath, String remoteZipFilename) throws Exception {
// 打印当前 SFTP 工作目录
try {
System.out.println("Current SFTP working directory: " + sftp.pwd());
} catch (SftpException e) {
System.err.println("Failed to get current SFTP working directory.");
throw e;
}
// 切换到指定的远程路径(如果提供了有效路径)
if (StringUtils.isNotBlank(remotePath) && !sftp.pwd().equals(remotePath)) {
try {
sftp.cd(remotePath);
System.out.println("Changed to remote directory: " + remotePath);
} catch (SftpException e) {
System.err.println("Failed to change to remote directory: " + remotePath);
throw e;
}
}
if (!sftp.isConnected()) {
reconnect();
}
// 获取 ZIP 文件的输入流
try (InputStream zipIn = sftp.get(remoteZipFilename);ZipInputStream zin = new ZipInputStream(zipIn)) {
ZipEntry entry;
while ((entry = zin.getNextEntry()) != null) {
// 假设 ZIP 中只包含文件条目,直接处理文件
System.out.println(sftp.pwd());
String remoteFilePath = remotePath + "/" + entry.getName();
System.out.println(sftp.pwd());
logger.info("Processing file: {}", remoteFilePath);
// 将 ZIP 条目内容写入远程文件
try (OutputStream out = sftp.put(remoteFilePath)) {
IOUtils.copy(zin, out);
} catch (SftpException e) {
logger.error("Failed to upload file: {}. Error: {}", remoteFilePath, e.getMessage(), e);
throw e;
}
// 关闭当前 ZIP 条目
zin.closeEntry();
}
} catch (Exception e){
logger.error("异常信息:" + e.getMessage());
}
// 删除已解压的 ZIP 文件
try {
sftp.rm(remoteZipFilename);
disconnect();
System.out.println("Removed ZIP file: " + remoteZipFilename);
} catch (SftpException e) {
System.err.println("Failed to remove ZIP file: " + remoteZipFilename);
throw e;
}
}
// private void unzipFile(String remotePath, String remoteZipFilename) throws SftpException, IOException {
//// if (StringUtils.isNotBlank(remotePath)) {
//// sftp.cd(remotePath);
//// }
//
// // 获取 ZIP 文件
// System.out.println(sftp.pwd());
// InputStream zipIn = sftp.get(remoteZipFilename);
// ZipInputStream zin = new ZipInputStream(zipIn);
// ZipEntry entry;
// while ((entry = zin.getNextEntry()) != null) {
// if (entry.isDirectory()) {
// createDirectory(remotePath, entry.getName());
// } else {
// // 创建文件
// File file = new File(entry.getName());
// String filePath = remotePath + "/" + file.getParent();
// if (file.getParent() != null && !filePath.equals(remotePath)) {
// createDirectory(filePath, "");
// }
// OutputStream out = sftp.put(entry.getName());
// IOUtils.copy(zin, out);
// out.close();
// }
// zin.closeEntry();
// }
// zin.close();
// zipIn.close();
//
// // 删除 ZIP 文件
// sftp.rm(remoteZipFilename);
// }
//
private void createDirectory(String remotePath, String dirName) throws SftpException {
if (StringUtils.isNotBlank(dirName)) {
String[] dirs = dirName.split("/");
for (String dir : dirs) {
if (dir.trim().length() > 0) {
remotePath += "/" + dir;
try {
sftp.lstat(remotePath);
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
sftp.mkdir(remotePath);
} else {
throw e;
}
}
}
}
}
}
public static void testSFTP1() {
try {
SFTPUtil sftp = new SFTPUtil("106.120.68.125", 8001,
"huidan",
"C:\\Users\\Administrator\\Desktop\\爱养牛\\id_rsa", null);
logger.info(String.valueOf(new StringBuffer().append("服务器地址: ")
.append(sftp.getHostName()).append(" 端口:")
.append(sftp.getPort()).append("用户名:")
.append(sftp.getUserName()).append("密钥文件:")
.append(sftp.getPriKeyFile())));
sftp.priKeyConnect();
// 服务器文件存放路径
String path = "/";
if (sftp.isExist(path)) {
sftp.uploadAndUnzip(path, "testupload.zip", "C:\\Users\\Administrator\\Desktop\\爱养牛\\testFile.zip");
logger.info("上传并解压成功");
}
} catch (Exception e) {
logger.error("异常信息:" + e.getMessage());
}
}
/**
* 尝试重新连接到 SFTP 服务器。
*/
private void reconnect() throws Exception {
SFTPUtil sftp1 = new SFTPUtil(sftpIp, sftpPort,
sftpUserName,
"secretKey/登录-gxjrid_rsa", null);
logger.info(String.valueOf(new StringBuffer().append("服务器地址: ")
.append(sftp1.getHostName()).append(" 端口:")
.append(sftp1.getPort()).append("用户名:")
.append(sftp1.getUserName()).append("密钥文件:")
.append(sftp1.getPriKeyFile())));
sftp1.priKeyConnect();
// if (session != null && !session.isConnected()) {
// session.connect();
// }
// if (sftp != null && !sftp.isConnected()) {
// ChannelSftp channel = (ChannelSftp) session.openChannel("sftp");
// channel.connect();
// this.sftp = channel;
// }
}
/**
* 先解压本地 ZIP 文件,然后上传解压后的文件到远程服务器。
*
* @param remotePath 远程服务器上的目标目录路径。
* @param localZipFilePath 本地 ZIP 文件的完整路径。
* @return 操作是否成功。
* @throws Exception 如果操作失败。
*/
public boolean unzipAndUpload(String remotePath, String localZipFilePath) throws Exception {
boolean success = false;
File tempDir = null;
try {
// 创建临时目录用于解压
tempDir = Files.createTempDirectory("unzip_temp_").toFile();
tempDir.deleteOnExit(); // 确保 JVM 退出时删除临时目录
// 解压本地 ZIP 文件到临时目录
unzipLocalFile(localZipFilePath, tempDir.getAbsolutePath());
// 获取解压后的文件列表
File[] files = tempDir.listFiles();
if (files == null || files.length == 0) {
throw new IOException("No files found after unzipping.");
}
// 上传解压后的文件到远程服务器
for (File file : files) {
uploadFile(remotePath, file.getName(), file.getAbsolutePath());
}
success = true;
} catch (Exception e) {
logger.error("异常信息:" + e.getMessage(), e);
throw e;
} finally {
// 清理临时文件和断开连接
if (tempDir != null) {
FileUtils.cleanup(tempDir.toPath());
}
disconnect();
}
return success;
}
/**
* 解压本地 ZIP 文件到指定目录。
*
* @param zipFilePath 本地 ZIP 文件的完整路径。
* @param destDir 目标解压目录。
* @throws IOException 如果解压过程中发生错误。
*/
private void unzipLocalFile(String zipFilePath, String destDir) throws IOException {
byte[] buffer = new byte[1024];
Path zipFilePathObj = Paths.get(zipFilePath);
Path destDirObj = Paths.get(destDir);
try (ZipInputStream zis = new ZipInputStream(new FileInputStream(zipFilePathObj.toFile()))) {
ZipEntry zipEntry = zis.getNextEntry();
while (zipEntry != null) {
Path newFilePath = destDirObj.resolve(zipEntry.getName());
if (!zipEntry.isDirectory()) {
// 如果是文件,则创建新文件并写入内容
Files.createDirectories(newFilePath.getParent());
try (FileOutputStream fos = new FileOutputStream(newFilePath.toFile())) {
int len;
while ((len = zis.read(buffer)) > 0) {
fos.write(buffer, 0, len);
}
}
} else {
// 如果是目录,则创建目录
Files.createDirectories(newFilePath);
}
zipEntry = zis.getNextEntry();
}
zis.closeEntry();
}
}
/**
* 获取指定目录下所有 .failure 文件的内容并解析为 JSON 对象。
*
* @param remotePath 远程服务器上的目标目录路径。
* @return 解析后的 JSON 对象列表。
* @throws Exception 如果操作失败。
*/
public List<Map<String, Object>> getAllFailureFilesContentAsJson(String remotePath) throws Exception {
List<Map<String, Object>> allJsonData = new ArrayList<>();
try {
// 列出远程目录下的所有文件
@SuppressWarnings("unchecked")
List<ChannelSftp.LsEntry> fileList = (List<ChannelSftp.LsEntry>) sftp.ls(remotePath + "/*.failure");
for (ChannelSftp.LsEntry entry : fileList) {
String fileName = entry.getFilename();
if (!fileName.equals(".") && !fileName.equals("..")) { // 排除当前和上级目录条目
Map<String, Object> jsonData = getFailureFileContentAsJson(remotePath, fileName);
allJsonData.add(jsonData);
}
}
} catch (SftpException e) {
logger.error("Failed to list files in directory: {}. Error: {}", remotePath, e.getMessage(), e);
throw e;
}
return allJsonData;
}
/**
* 获取指定目录下所有 结果文件的内容并解析为 JSON 对象。
*
* @param remotePath 远程服务器上的目标目录路径。
* @return 解析后的 JSON 对象列表。
* @throws Exception 如果操作失败。
*/
public List<ResultLinkField> getAllResultFilesContentAsJson(String remotePath) throws Exception {
List<ResultLinkField> allJsonData = new ArrayList<>();
try {
// 列出远程目录下的所有文件
@SuppressWarnings("unchecked")
List<ChannelSftp.LsEntry> fileList = (List<ChannelSftp.LsEntry>) sftp.ls(remotePath + "/ENTRUSTRESULT-*.bin");
for (ChannelSftp.LsEntry entry : fileList) {
String fileName = entry.getFilename();
if (!fileName.equals(".") && !fileName.equals("..")) { // 排除当前和上级目录条目
String fileContentAsString = getFileContentAsString(remotePath, fileName);
List<String> contents = Arrays.asList(fileContentAsString.split("\r\n"));
for (String content : contents) {
ResultLinkField resultLinkField = contentHandle(content);
allJsonData.add(resultLinkField);
}
}
}
} catch (SftpException e) {
logger.error("Failed to list files in directory: {}. Error: {}", remotePath, e.getMessage(), e);
throw e;
}
return allJsonData;
}
ResultLinkField contentHandle(String content){
ResultLinkField resultLinkField = new ResultLinkField();
resultLinkField.setZoneNo(content.substring(0,5));
resultLinkField.setBatchNo(content.substring(5,23));
resultLinkField.setBillNo(content.substring(23,53).replace(" ",""));
resultLinkField.setStatus(content.substring(53,54));
resultLinkField.setErrMsg(content.substring(54,content.length()-37).replace(" ",""));
resultLinkField.setTimestamp(content.substring(content.length()-37,content.length()-17));
resultLinkField.setAmt(content.substring(content.length()-17,content.length()).replace(" ",""));
return resultLinkField;
}
/**
* 读取指定文件的内容并转换为字符串。
*
* @param remotePath 远程服务器上的目标目录路径。
* @param fileName 文件名。
* @return 文件内容的字符串表示。
* @throws Exception 如果操作失败。
*/
private String getFileContentAsString(String remotePath, String fileName) throws Exception {
StringBuilder contentBuilder = new StringBuilder();
InputStream inputStream = null;
try {
inputStream = sftp.get(remotePath + "/" + fileName);
byte[] buffer = new byte[1024];
int bytesRead;
while ((bytesRead = inputStream.read(buffer)) != -1) {
contentBuilder.append(new String(buffer, 0, bytesRead, Charset.forName("GB2312")));
}
} finally {
if (inputStream != null) {
inputStream.close();
}
}
return contentBuilder.toString();
}
/**
* 获取指定目录下所有 .failure 文件的内容并解析为 JSON 对象。
*
* @param remotePath 远程服务器上的目标目录路径。
* @return 解析后的 JSON 对象列表。
* @throws Exception 如果操作失败。
*/
public List<String> getAllSuccessFilesContentAsJson(String remotePath) throws Exception {
List<String> batchNos = new ArrayList<>();
try {
// 列出远程目录下的所有文件
@SuppressWarnings("unchecked")
List<ChannelSftp.LsEntry> fileList = (List<ChannelSftp.LsEntry>) sftp.ls(remotePath + "/*.success");
for (ChannelSftp.LsEntry entry : fileList) {
String fileName = entry.getFilename();
if (!fileName.equals(".") && !fileName.equals("..")) { // 排除当前和上级目录条目
batchNos.add(fileName.substring(fileName.length()-18-8).replace(".success",""));
}
}
} catch (SftpException e) {
logger.error("Failed to list files in directory: {}. Error: {}", remotePath, e.getMessage(), e);
throw e;
}
return batchNos;
}
/**
* 从 SFTP 服务器获取 .failure 文件内容并解析为 JSON 对象。
*
* @param remotePath 远程服务器上的目标目录路径。
* @param remoteFilename 远程文件名(包括扩展名)。
* @return 解析后的 JSON 对象。
* @throws Exception 如果操作失败。
*/
private Map<String, Object> getFailureFileContentAsJson(String remotePath, String remoteFilename) throws Exception {
try (InputStream inputStream = getRemoteFileInputStream(remotePath, remoteFilename)) {
// 使用 Jackson 解析 JSON 内容
ObjectMapper objectMapper = new ObjectMapper();
return objectMapper.readValue(inputStream, Map.class);
} catch (SftpException | IOException e) {
logger.error("Failed to retrieve and parse file content: {}. Error: {}", remoteFilename, e.getMessage(), e);
throw e;
}
}
/**
* 获取远程文件的输入流。
*
* @param remotePath 远程服务器上的目标目录路径。
* @param remoteFilename 远程文件名(包括扩展名)。
* @return 文件的输入流。
* @throws Exception 如果操作失败。
*/
private InputStream getRemoteFileInputStream(String remotePath, String remoteFilename) throws Exception {
if (!sftp.isConnected()) {
reconnect();
}
// 打印尝试获取的文件路径
logger.info("Attempting to retrieve file: {}/{}", remotePath, remoteFilename);
try {
// 检查文件是否存在
if (!fileExists(remotePath + "/" + remoteFilename)) {
throw new FileNotFoundException("File not found: " + remoteFilename);
}
// 获取文件的输入流
return sftp.get(remotePath + "/" + remoteFilename);
} catch (SftpException e) {
logger.error("Failed to access file at path: {}/{}. Error: {}", remotePath, remoteFilename, e.getMessage(), e);
throw e;
}
}
/**
* 检查远程文件是否存在。
*/
private boolean fileExists(String remoteFilePath) throws SftpException {
try {
sftp.stat(remoteFilePath);
return true;
} catch (SftpException e) {
if (e.id == ChannelSftp.SSH_FX_NO_SUCH_FILE) {
return false;
}
throw e; // 对于其他错误,重新抛出异常
}
}
}
......@@ -100,9 +100,12 @@
ifnull( icbc.id_card, ph.id_card ) AS idCard,
ph.amos_user_id,
icbc.open_account_status,
icbc.out_user_id as outUserId,
icbc.protocol_status,
icbc.medium_id,
ph.rec_date
ph.rec_date,
ph.regional_companies_name as regionalCompaniesName,
ph.regional_companies_code as regionalCompaniesCode
FROM
(
SELECT
......@@ -154,4 +157,49 @@
ORDER BY
ph.rec_date DESC
</select>
<select id="exportTotal" resultType="com.yeejoin.amos.boot.module.hygf.api.dto.HygfIcbcRecordExportDTO">
SELECT
MAX(ph.owners_name) AS custName,
MAX(ph.telephone) AS idCard,
MAX(ph.id_card) AS idCard,
MAX(ph.regional_companies_name) AS regionalCompaniesName,
MAX(re.medium_id) AS mediumId,
MAX(
CASE
re.protocol_status
WHEN 0 THEN
'未生效'
WHEN 1 THEN
'已生效'
WHEN 2 THEN
'过期'
WHEN 3 THEN
'作废'
WHEN 4 THEN
'待短信确认' ELSE''
END
) AS protocolStatus,
MAX( CASE re.open_account_status WHEN 02 THEN '开户成功' WHEN 03 THEN '开户失败' ELSE'未开户' END ) AS openAccountStatus
FROM
`hygf_peasant_household` ph
LEFT JOIN hygf_icbc_record re ON re.amos_user_id = ph.amos_user_id
<if test="developerCode != null and developerCode != ''">
AND ph.developer_code = #{developerCode}
</if>
<if test="regionalCompaniesCode != null and regionalCompaniesCode != ''">
AND ph.regional_companies_code = #{regionalCompaniesCode}
</if>
<if test="province != null and province != ''">
AND ph.project_address LIKE CONCAT ('%',#{province},'%')
</if>
<if test="city != null and city != ''">
AND ph.project_address LIKE CONCAT ('%',#{city},'%')
</if>
<if test="district != null and district != ''">
AND ph.project_address LIKE CONCAT ('%',#{district},'%')
</if>
GROUP BY
ph.amos_user_id
</select>
</mapper>
\ No newline at end of file
......@@ -30,8 +30,6 @@
<artifactId>org.apache.commons.codec</artifactId>
<version>1.8</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk15on -->
<dependency>
<groupId>org.bouncycastle</groupId>
......
......@@ -122,4 +122,32 @@ public class HygfIcbcController extends BaseController {
public ResponseModel<Object> repair(@RequestParam("userId") String userId) {
return ResponseHelper.buildResponse(hygfIcbcService.repair(userId));
}
@TycloudOperation (ApiLevel = UserType.AGENCY, needAuth = false)
@GetMapping (value = "/exportTotal")
@ApiOperation (httpMethod = "GET", value = "导出聚富通开卡统计", notes = "导出聚富通开卡统计")
public ResponseModel<Map<String,Long>> exportTotal(@ApiParam(value = "经销商公司code",example = "87*253*775")@RequestParam(required = false) String developerCode,
@ApiParam(value = "区域公司code",example = "87*253*652") @RequestParam(required = false) String regionalCompaniesCode,
@ApiParam(value = "省份",example = "610000") @RequestParam(required = false) String province,
@ApiParam(value = "市",example = "610700") @RequestParam(required = false) String city,
@ApiParam(value = "区",example = "610116") @RequestParam(required = false) String district) {
return ResponseHelper.buildResponse(hygfIcbcService.exportTotal(developerCode,regionalCompaniesCode,province,city,district));
}
/**
* 列表全部数据查询
*
* @return
*/
@TycloudOperation(ApiLevel = UserType.AGENCY)
@ApiOperation(httpMethod = "GET",value = "存量合同电站下载", notes = "存量合同电站下载")
@GetMapping(value = "/export")
public void exportData(HttpServletResponse response,@ApiParam(value = "经销商公司code",example = "87*253*775")@RequestParam(required = false) String developerCode,
@ApiParam(value = "区域公司code",example = "87*253*652") @RequestParam(required = false) String regionalCompaniesCode,
@ApiParam(value = "省份",example = "610000") @RequestParam(required = false) String province,
@ApiParam(value = "市",example = "610700") @RequestParam(required = false) String city,
@ApiParam(value = "区",example = "610116") @RequestParam(required = false) String district) {
hygfIcbcService.exportData(response,developerCode,regionalCompaniesCode,province,city,district);
}
}
......@@ -22,8 +22,12 @@ import javax.annotation.Resource;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import cn.hutool.core.collection.CollectionUtil;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.yeejoin.amos.boot.biz.common.excel.ExcelUtil;
import com.yeejoin.amos.boot.module.hygf.api.dto.HistoryPeasantHouseholdDto;
import com.yeejoin.amos.boot.module.hygf.api.dto.HygfIcbcRecordExportDTO;
import com.yeejoin.amos.boot.module.hygf.api.entity.PowerStationEngineeringInfo;
import com.yeejoin.amos.boot.module.hygf.api.mapper.HouseholdContractMapper;
import com.yeejoin.amos.boot.module.hygf.api.mapper.PowerStationEngineeringInfoMapper;
......@@ -638,4 +642,35 @@ public class HygfIcbcServiceImpl extends BaseService<HygfIcbcRecordDTO, HygfIcbc
return "success";
}
public Map<String,Long> exportTotal(String developerCode, String regionalCompaniesCode, String province, String city, String district){
List<HygfIcbcRecordExportDTO> dtos = hygfIcbcRecordMapper.exportTotal(developerCode, regionalCompaniesCode, province, city, district);
Map<String, Long> collect = dtos.stream()
.collect(Collectors.groupingBy(HygfIcbcRecordExportDTO::getOpenAccountStatus, Collectors.counting()));
String[] strings = {"开户失败","开户成功","未开户"};
List<String> names = Arrays.asList(strings);
for (String name : names) {
if (!collect.keySet().contains(name)) {
collect.put(name,0L);
}
}
// 根据 openAccountStatus 分组统计
return collect;
}
public void exportData(HttpServletResponse response,String developerCode, String regionalCompaniesCode, String province, String city, String district){
List<HygfIcbcRecordExportDTO> dtos = hygfIcbcRecordMapper.exportTotal(developerCode, regionalCompaniesCode, province, city, district);
if (CollectionUtil.isNotEmpty(dtos)){
ExcelUtil.createTemplate(response,"开卡统计","开卡统计",dtos, HygfIcbcRecordExportDTO.class,null,false);
}
}
}
\ No newline at end of file
......@@ -278,7 +278,7 @@ public class StatisticsHomepageServiceImpl {
Map<String, Object> map = iterator2.next();
financingNum += (long) map.get("total");
if ("放款完成".equals(map.get("statusText"))) {
disbursementMoney += new BigDecimal(String.valueOf(map.get("realScale"))).doubleValue();
disbursementMoney += new BigDecimal(String.valueOf(map.get("disbursementMoney"))).doubleValue();
}
if ("未通过".equals(map.get("statusText"))) {
trtotal += (long) map.get("total");
......
exception.debug=true
## DB properties:
spring.datasource.dynamic.primary= mysql-service
spring.datasource.mysql-service.driver-class-name=com.mysql.cj.jdbc.Driver
spring.datasource.mysql-service.jdbc-url=jdbc:mysql://47.92.234.253:13306/amos_project?allowMultiQueries=true&serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.mysql-service.driver-class-name=com.kingbase8.Driver
spring.datasource.mysql-service.jdbc-url=jdbc:kingbase8://39.98.224.23:54321/amos_project_47?allowMultiQueries=true&serverTimezone=GMT%2B8&characterEncoding=utf8
spring.datasource.mysql-service.username=root
spring.datasource.mysql-service.password=Yeejoin_1234
spring.datasource.mysql-service.type=com.zaxxer.hikari.HikariDataSource
spring.datasource.mysql-service.password=Yeejoin@2020
spring.datasource.mysql-service.type=com.alibaba.druid.pool.DruidDataSource
#最小连接
spring.datasource.mysql-service.minimum-idle: 5
#最大连接
......@@ -261,7 +261,15 @@ hygf.icbc.camsPublicKey=7E095625BE87DF744C916E40BD6B1C1EA486125CA566006062D8FFBB
hygf.icbc.apigwPublicKey=MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCwFgHD4kzEVPdOj03ctKM7KV+16bWZ5BMNgvEeuEQwfQYkRVwI9HFOGkwNTMn5hiJXHnlXYCX+zp5r6R52MY0O7BsTCLT7aHaxsANsvI9ABGx3OaTVlPB59M6GPbJh0uXvio0m1r/lTW3Z60RU6Q3oid/rNhP3CiNgg0W6O3AGqwIDAQAB
hygf.icbc.outVendorId=071301
hygf.icbc.projectId=PJ14001401B000160171
## 聚富通生产代扣环境
icbc.Withhold.projectId=PJ140014023565102203
icbc.Withhold.corpCis=211590000183323
icbc.Withhold.partner.identification=JO004
icbc.Withhold.outVendorId=gxjr
icbc.Withhold.sftpUserName=jrgf
icbc.Withhold.sftpPort=8001
icbc.Withhold.sftpIp=gw.open.icbc.com.cn
withholdStatusCron=0,30 8-23 * * * ?
## 聚富通工行 生产环境配置
#hygf.icbc.appId=11000000000000028870
#hygf.icbc.corpNo=020240710000001169
......
......@@ -86,6 +86,10 @@ admin.delerKaId.roleId=1702551022574006274
exception.debug=true
feign.okhttp.enabled= true
workflow.feign.name=AMOS-API-WORKFLOW-CZ
workflow.feign.name=AMOS-API-WORKFLOW
repaymentCron=0 0 1 * * ?
urlHttp=http://47.92.234.253:9000
hygf.sms.tempCode=SMS_HYGF_0001
hygf.sms.maintenanceCode=SMS_HYGF_0005
hygf.sms.repaymentCode=SMS_HYGF_0006
\ No newline at end of file
-----BEGIN PRIVATE KEY-----
MIIEvQIBADANBgkqhkiG9w0BAQEFAASCBKcwggSjAgEAAoIBAQCsfCY3SBX8EMHQ
rUgcDZ5WReUq8cDjpx3g1HcNiZlKMoQyQoxEE7NBEFPPAl9X/jZd9IkxZ1i7DlBA
IP4ORzrEIXIua9ssX9SFmhqdF3IWZNsgORdLbU3DL8Johi1SlJw6ucXokBjcxmkb
06BABLa+JHhiCqswioYNjwNpJy/biAedOrB51DkVerOHmwKAtXKN56U0dpWvYHFP
ic613VYaeDXFGLwMKU9gfr6qTSRAH37cdaYu1SgnynbW96lrhWxnCbOgrCTInuaB
AkVJMp0vF4QjBfjj71f4Q1vcdpWUaxEhidlgmsH9BafmDZXx7jo5W3lOpnGF5LNz
PXcTbBVdAgMBAAECggEATSCCkiuosPrpP5+MZAbu61L7w3UwtFtQ7+zxg1so8VWf
aT36rKJGgGyFnUZJTfZ9ZwvmoPG+an5fAh3+nHHbZEI0ZW+TJMeBJ3CeP+pw+HSG
y2Tb9r1cjU/41XZSI+AR7+yMA48Tnv0VmPzLZnT1Jhb2wZhVrjHy+XMeqlF8g+QW
tzmC4+IWGgB5P86mFsuE8b1UwxleJIAVsAE+ujUr4cFObqjQsjMDq9ZNKVGYUEWa
2yciq4NzrhYB+FgtV/oJ7KQbrjqxBCsq4cP8m/e6pfYwoDIjJKKhUlUl0ai42ZFc
rji8msVfQxe23sQ+cfsU/GC+nsRu3sFxSJt4pip+4QKBgQDaycClcEqwAiRDTOSL
zXTvupukRnDEkQfl/pLOgr38fxAqt4fsyDWJcJZR+F+Fk+WqDFQB4Q/AbUy9SxKa
iZ/VqfGVujGCGKtJQz9h28NxR+wJ/mz3bqC1wBfJZWG9HANA0dW4VdwQgXZ3BjHt
ZechDbgsHF4Gipwmoe+/NIefeQKBgQDJ0lAaGlhPUq8aic6IDd72LZztR4cV90Bn
4ilkBMoE0aLVNxEd3EnATcywUBBtFM9/xeJuh6KpXSlYmC9DEXWdh3KrGfSQ/6wP
QGjLMex0JCw26xV13RJo+YE97m7GeOSTMPNFtcXY+QWWGYNCh4Y0BR/fb7z21HWM
IIVqNp+4BQKBgQCxzzG/wokQjk0GXSGI9QtKIXCIi67uMagnpUOOjfb1lBaBgPL4
3qWT82pXZ/HJz67hCm/jrxNsIegyFychbSP3M8xhERdXUdEzE3IZh89I61jLrnjE
hqYZz+oFBV3voSqLrX1x+GWHDJBIn0JIW38B//Jj9xH7Yv8Q7p632Gs4kQKBgFQG
OI01jLCxtu8wkoj4ZWxOvzfWgLo3b1wQv7TBfa4V7VohjAHHlfj3KxH4s24GrQEA
TO5BkAo9HBqjeYHNwg7stmPf8X9wwpyy7y4xwcT0zmeq5gtlF+YWgqVbIEmHSXqy
dYX/ULitrJK6WniXXdtIiuWGasDE4Y+Odp0q0sJVAoGAJRKaslGLFAx7fhk1ET3/
RKLgzlRJS3u19+a8JjYAaNbO1KeOihDcrjNi63Zkp4S4mwY52yHc17B6gLXukFwB
j8MvkWqpqxHjxf8csv4d/nm7oXau3m38gjZb605uRgmlJwYgA0ziwN822TKt2HTt
1ta2t6N0S5gtyAmK9amSwDY=
-----END PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIIEowIBAAKCAQEArHwmN0gV/BDB0K1IHA2eVkXlKvHA46cd4NR3DYmZSjKEMkKM
RBOzQRBTzwJfV/42XfSJMWdYuw5QQCD+Dkc6xCFyLmvbLF/UhZoanRdyFmTbIDkX
S21Nwy/CaIYtUpScOrnF6JAY3MZpG9OgQAS2viR4YgqrMIqGDY8DaScv24gHnTqw
edQ5FXqzh5sCgLVyjeelNHaVr2BxT4nOtd1WGng1xRi8DClPYH6+qk0kQB9+3HWm
LtUoJ8p21vepa4VsZwmzoKwkyJ7mgQJFSTKdLxeEIwX44+9X+ENb3HaVlGsRIYnZ
YJrB/QWn5g2V8e46OVt5TqZxheSzcz13E2wVXQIDAQABAoIBAE0ggpIrqLD66T+f
jGQG7utS+8N1MLRbUO/s8YNbKPFVn2k9+qyiRoBshZ1GSU32fWcL5qDxvmp+XwId
/pxx22RCNGVvkyTHgSdwnj/qcPh0hstk2/a9XI1P+NV2UiPgEe/sjAOPE579FZj8
y2Z09SYW9sGYVa4x8vlzHqpRfIPkFrc5guPiFhoAeT/OphbLhPG9VMMZXiSAFbAB
Pro1K+HBTm6o0LIzA6vWTSlRmFBFmtsnIquDc64WAfhYLVf6CeykG646sQQrKuHD
/Jv3uqX2MKAyIySioVJVJdGouNmRXK44vJrFX0MXtt7EPnH7FPxgvp7Ebt7BcUib
eKYqfuECgYEA2snApXBKsAIkQ0zki81077qbpEZwxJEH5f6SzoK9/H8QKreH7Mg1
iXCWUfhfhZPlqgxUAeEPwG1MvUsSmomf1anxlboxghirSUM/YdvDcUfsCf5s926g
tcAXyWVhvRwDQNHVuFXcEIF2dwYx7WXnIQ24LBxeBoqcJqHvvzSHn3kCgYEAydJQ
GhpYT1KvGonOiA3e9i2c7UeHFfdAZ+IpZATKBNGi1TcRHdxJwE3MsFAQbRTPf8Xi
boeiqV0pWJgvQxF1nYdyqxn0kP+sD0BoyzHsdCQsNusVdd0SaPmBPe5uxnjkkzDz
RbXF2PkFlhmDQoeGNAUf32+89tR1jCCFajafuAUCgYEAsc8xv8KJEI5NBl0hiPUL
SiFwiIuu7jGoJ6VDjo329ZQWgYDy+N6lk/NqV2fxyc+u4Qpv468TbCHoMhcnIW0j
9zPMYREXV1HRMxNyGYfPSOtYy654xIamGc/qBQVd76Eqi619cfhlhwyQSJ9CSFt/
Af/yY/cR+2L/EO6et9hrOJECgYBUBjiNNYywsbbvMJKI+GVsTr831oC6N29cEL+0
wX2uFe1aIYwBx5X49ysR+LNuBq0BAEzuQZAKPRwao3mBzcIO7LZj3/F/cMKcsu8u
McHE9M5nquYLZRfmFoKlWyBJh0l6snWF/1C4raySulp4l13bSIrlhmrAxOGPjnad
KtLCVQKBgCUSmrJRixQMe34ZNRE9/0Si4M5USUt7tffmvCY2AGjWztSnjooQ3K4z
Yut2ZKeEuJsGOdsh3NeweoC17pBcAY/DL5FqqasR48X/HLL+Hf55u6F2rt5t/II2
W+tObkYJpScGIANM4sDfNtkyrdh07dbWtrejdEuYLcgJivWpksA2
-----END RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
-----BEGIN RSA PRIVATE KEY-----
MIIEogIBAAKCAQEA19UCbQcV1uiyr+U5sxznTxKJ4r8lSgvCfi/2cOqs0MrQt0nM
V4nzqa9BJoiBNIK1RrxMALX1zsG4c+KxBomAbm/x8XeBQXVPJoWR7mYYCX8Huy4Y
/lB5kRfCZ/+xINe+PbTk2tH1q3V7Jb76eVDKAOG/6nWbQi4jAvLXVehrxLrXQPI+
zYIvRG5sszo8aL/PAMK+CQ03vuM6Mrdf3CNsAioMH4zLoPSzJQg0LknbHHrTwuGF
TcaP9LpfzmrtzlZUTG6mK6UurMo2/D9MlhDIecoBCB7LkKJcxCohJ2mET5rvSUqQ
tx/t6BuKFlplwQcaRdUKtLngH1EVidg62pujVQIDAQABAoIBAFnllhIVbmpKGAsd
fe/1rP6JaTcxiHWT+lmW3p3fkUWSBGcjbOJVSlE19vv2xWI6wwiIa1usborEs3BJ
TpS7duwI4oxBy2uZUfNolQToL0DdUToMlEpw+IGPlOC+ZmeYlNuc2emupBXbZASv
zCH1nX3KiaY9gAKLtveGcFyO5zxUFdbH0hoSpQ5/Yu4n3Yd9a0yh+5lergcMFP6k
Jn4lIktPpe/hoYbtvJlOlMEmUX7MsSVOJ69MfUkFOMOOsIg9Cf00BRi/rWPAK/FZ
w8mURmiiM48ZyU1ZUbDdwIeIXc55oLjUP5+KcetiEK62ANBPX/yUXyqnAolUGBuK
GQxxamECgYEA/w1sJUQIgWiX2N53nE+cJa3WOPzQdI0Zk16x5Vuxhw39gTc2L/ur
l3PySWWHCT4QQcWIPvMO91PnnF8KuYqrei5sG/aYuURZ5o4Ay3f5nubB8hZKdL5a
YtlKJUS8qYY8qynaNYIRN/UlXtM2BjgVO5FHdQT/+trU2UtvyXz2AS0CgYEA2KJI
9Ecnehk0rO6Ipszk4CPEXnPkr9vkAR2rAS5XWX53nR2EdtI0i8MjHwiK0z8O4QLq
qFqCXACBavsW+jaUS4boBkjZMge0mcE8U8n0/OgAsdCqGtO4NzGVVCuXtnTbKisw
sSbzqzGZe4FZ9yHue0lAmbgIn/L9vIiDdzQT88kCgYB8JXBqgz8QnnSrHz5hW27J
+F+5xXVCBhxY21MELSmwb2Lhrpo1qO7Q/aMFjuG34fnPyfKTkYPLZ4/pUWnK/nCR
PF9rDRExvM91pdFFONTMP85PpAIB6VXdn56znU2nxqtpNSn9uZ3f+veBvJUjWdEb
+Y8qqpBe7n2Ed2+mR6kG0QKBgAE58fXIDVYonF66PvXKxSTrrunl+A5yzTigZV5t
V7s+9whhqWVOzVNZOHKthrOpcLkfXhqz4HcK3bCwWTHzayV+TwPyF6Cr4H7aVDAZ
PZTM72wSRVQ/jJRraHAAiyxSPwdfFTh7gveeIUNMuSin/YSfJol4PDxDkOInV68u
EUrZAoGAQ9e9fRp6ZyzIaRuJzLK6SXHgRVSPkfTdVLsZVXHOTdgFomW+oPQYSCmt
yejCiYjyvJOXm+7GvvX9X+WtdAk4EISWiYC9RiOhliXiR63lFZSz78x1RVkZmNfX
d3kd6MHNzGZ+enFn5heJlc868s/Ss9ecvCl3dx+kVFA6AYgAw18=
-----END RSA PRIVATE KEY-----
......@@ -55,6 +55,12 @@
</exclusions>
</dependency>
<dependency>
<groupId>com.jcraft</groupId>
<artifactId>jsch</artifactId>
<version>0.1.55</version>
</dependency>
<dependency>
<groupId>org.apache.commons</groupId>
<artifactId>commons-text</artifactId>
<version>1.9</version>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment