用sftp推送zip包到目标服务器
- 任务类里面,主要功能是,定时采集三张表的数据,并把数据转换成csv,三份csv压缩成一个加密的zip包,通过sftp推送到指定的目录下
配置类
@Data
@Configuration
@ConfigurationProperties(prefix = "sftp.zip")
public class SftpConfig {
private String host;
private int port = 22;
private String username;
private String password;
private String remoteDir;
private String zipPassword;
private String cron;
}
任务类SftpTask
import net.lingala.zip4j.ZipFile;
import net.lingala.zip4j.model.ZipParameters;
import net.lingala.zip4j.model.enums.EncryptionMethod;
import net.schmizz.sshj.SSHClient;
import net.schmizz.sshj.sftp.SFTPClient;
import net.schmizz.sshj.sftp.SFTPException;
import net.schmizz.sshj.transport.verification.HostKeyVerifier;
import org.apache.commons.io.FileUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Service;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.text.SimpleDateFormat;
import java.util.*;
@Service
public class SftpTask {
Logger logger = LoggerFactory.getLogger(this.getClass());
@Autowired
private SftpConfig sftpConfig;
private static final SimpleDateFormat DATE_FORMAT = new SimpleDateFormat("yyyyMMdd");
// 定义表格头
private static final String V4_HEADER = "网段名称,是否是专业公司,专业公司名称,单位名称/具体业务信息,单位所属分类,单位性质,所属省份,所属地市,所属区县,使用方式,业务类型,使用状态,管理状态,机房,设备名称,Loopbak地址,接入端口信息";
private static final String RA_HEADER = "地址范围,是否是专业公司,专业公司名称,单位名称/具体业务信息,单位所属分类,单位性质,所属省份,所属地市,所属区县,使用方式,业务类型,使用状态,管理状态,机房,设备名称,Loopbak地址,接入端口信息";
@Scheduled(cron = "${sftp.zip.cron}")
public void executeScheduledTask() {
try {
logger.info("开始执行SFTP ZIP上传任务: " + new Date());
// 定义压缩包的名字,例如TEST_20250415.zip
String zipFileName = "TEST_" + DATE_FORMAT.format(new Date()) + ".zip";
createEncryptedZip(zipFileName);
uploadViaSftp(zipFileName);
new File(zipFileName).delete();
logger.info("SFTP ZIP上传任务完成: " + new Date());
} catch (Exception e) {
logger.error("SFTP ZIP上传任务失败:"+e.getMessage());
}
}
private void createEncryptedZip(String zipFileName) throws Exception {
ZipParameters parameters = new ZipParameters();
parameters.setEncryptFiles(true);
parameters.setEncryptionMethod(EncryptionMethod.ZIP_STANDARD);
String previousDayFormatted = getPreviousDayFormatted();
ZipFile zipFile = new ZipFile(zipFileName, sftpConfig.getZipPassword().toCharArray());
try {
String v4FileName = "V4_Subnet_"+previousDayFormatted+".csv";
// 1. IPV4地址规划数据CSV
addCsvToZip(zipFile, parameters, v4FileName,
V4_HEADER, v4Mapper.getAllDataByDate(),
new CsvRowMapper<SftpDto>() {
@Override
public String mapRow(SftpDto info) {
return String.format(Locale.US, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
// 如果字段可能为 null,则替换为空字符串 ""
info.getSubnet() != null ? info.getSubnet() : "",
"", // 直接传空字符串
"", // 直接传空字符串
info.getUnit_business() != null ? info.getUnit_business() : "",
"", // 直接传空字符串
"", // 直接传空字符串
450000, // 固定值
info.getIp_use_city() != null ? info.getIp_use_city() : "",
"", // 直接传空字符串
"", // 直接传空字符串
"", // 直接传空字符串
2, // 固定值
5, // 固定值
info.getRoom() != null ? info.getRoom() : "",
info.getDevice_name() != null ? info.getDevice_name() : "",
info.getLookback_address() != null ? info.getLookback_address() : "",
"" // 直接传空字符串
);
}
});
// // 2. IPV6地址规划数据CSV
String v6FileName = "V6_Subnet_"+previousDayFormatted+".csv";
addCsvToZip(zipFile, parameters, v6FileName,
V4_HEADER, v6Mapper.getAllDataByZero(),
new CsvRowMapper<SftpDto>() {
@Override
public String mapRow(SftpDto info) {
return String.format(Locale.US, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
// 如果字段可能为 null,则替换为空字符串 ""
info.getSubnet() != null ? info.getSubnet() : "",
"", // 直接传空字符串
"", // 直接传空字符串
info.getUnit_business() != null ? info.getUnit_business() : "",
"", // 直接传空字符串
"", // 直接传空字符串
450000, // 固定值
info.getIp_use_city() != null ? info.getIp_use_city() : "",
info.getIp_use_county() != null ? info.getIp_use_county() : "",
"", // 直接传空字符串
"", // 直接传空字符串
2, // 固定值
5, // 固定值
info.getRoom() != null ? info.getRoom() : "",
info.getDevice_name() != null ? info.getDevice_name() : "",
info.getLookback_address() != null ? info.getLookback_address() : "",
"" // 直接传空字符串
);
}
});
//
// // 3. IP地址备案数据CSV
String raFileName = "IP_address_"+previousDayFormatted+".csv";
addCsvToZip(zipFile, parameters, raFileName,
RA_HEADER, raMapper.getAllDataByZero(),
new CsvRowMapper<SftpDto>() {
@Override
public String mapRow(SftpDto info) {
return String.format(Locale.US, "%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s,%s",
// 如果字段可能为 null,则替换为空字符串 ""
info.getAddress() != null ? info.getAddress() : "",
info.getIs_profession() != null ? info.getIs_profession() : "",
info.getPro_co_name() != null ? info.getPro_co_name() : "",
"",
info.getUnit_type() != null ? info.getUnit_type() : "",
info.getUnit_nature() != null ? info.getUnit_nature() : "",
info.getIp_use_province() != null ? info.getIp_use_province() : "",
info.getIp_use_city() != null ? info.getIp_use_city() : "",
info.getIp_use_county() != null ? info.getIp_use_county() : "",
info.getUse_type() != null ? info.getUse_type() : "",
info.getIp_service_type() != null ? info.getIp_service_type() : "",
3, // 固定值
8, // 固定值
info.getRoom() != null ? info.getRoom() : "",
info.getDevice_name() != null ? info.getDevice_name() : "",
info.getLookback_address() != null ? info.getLookback_address() : "",
info.getInterface_data() !=null ? info.getInterface_data() : ""
);
}
});
} finally {
zipFile.close();
}
}
private interface CsvRowMapper<T> {
String mapRow(T item);
}
private <T> void addCsvToZip(ZipFile zipFile, ZipParameters parameters,
String fileName, String header, List<T> dataList, CsvRowMapper<T> rowMapper) throws IOException {
File tempFile = File.createTempFile(fileName.replace(".csv", ""), ".csv");
try {
// 写入CSV内容
PrintWriter writer = null;
try {
writer = new PrintWriter(new OutputStreamWriter(
new FileOutputStream(tempFile), StandardCharsets.UTF_8));
writer.println(header);
for (T data : dataList) {
writer.println(rowMapper.mapRow(data));
}
} finally {
if (writer != null) {
writer.close();
}
}
parameters.setFileNameInZip(fileName);
// 添加到ZIP
zipFile.addFile(tempFile, parameters);
} finally {
FileUtils.deleteQuietly(tempFile);
}
}
private void uploadViaSftp(String zipFileName) throws Exception {
try (SSHClient ssh = new SSHClient()) {
// 跳过主机密钥验证(仅用于测试)
ssh.addHostKeyVerifier(new HostKeyVerifier() {
@Override
public boolean verify(String hostname, int port, PublicKey key) {
return true; // 跳过主机密钥验证(仅用于测试)
}
@Override
public List<String> findExistingAlgorithms(String hostname, int port) {
return Collections.emptyList();
}
});
ssh.connect(sftpConfig.getHost(), sftpConfig.getPort());
// 认证
ssh.authPassword(sftpConfig.getUsername(), sftpConfig.getPassword());
// 打开 SFTP 客户端并上传文件
try (SFTPClient sftp = ssh.newSFTPClient()) {
// 确保远程目录存在(支持多层目录)
ensureDirectoryExists(sftp, sftpConfig.getRemoteDir());
CustomLocalSourceFile customFile = new CustomLocalSourceFile(new File(zipFileName));
// 指定远程路径
String remotePath = sftpConfig.getRemoteDir() + new File(zipFileName).getName(); // 目标路径
sftp.put(customFile, remotePath); // 上传到指定的远程路径
}
logger.info("文件上传成功:{}",zipFileName);
} catch (Exception e) {
logger.error("文件上传失败:{}",e.getMessage());
}
}
public static String getPreviousDayFormatted() {
// 定义日期格式
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyyMMdd");
// 获取当前时间
Date now = new Date();
// 格式化日期
return dateFormat.format(now);
}
/**
* 确保远程目录存在(支持多层目录)
*
* @param sftp SFTP 客户端
* @param remoteDir 远程目录路径
* @throws IOException 如果操作失败
*/
private void ensureDirectoryExists(SFTPClient sftp, String remoteDir) throws IOException {
// 分割路径为多个部分
String[] dirs = remoteDir.split("/");
StringBuilder currentPath = new StringBuilder("/");
for (String dir : dirs) {
if (!dir.isEmpty()) {
currentPath.append(dir).append("/");
try {
// 检查当前路径是否存在
sftp.stat(currentPath.toString());
} catch (SFTPException e) {
// 如果路径不存在,则创建目录
if (e.getMessage().contains("No such file")) {
sftp.mkdir(currentPath.toString());
} else {
// 如果是其他异常,重新抛出
throw e;
}
}
}
}
}
}
配置文件 application.properties
- sftp.zip.cron : 执行频率
- sftp.zip.host : 接收端服务器IP地址
- sftp.zip.port : 协议端口,默认22
- sftp.zip.username : 接收端服务器登录用户名
- sftp.zip.password : 接收端服务器登录密码
- sftp.zip.remote-dir : 接收端文件接收路径
- sftp.zip.zip-password : 压缩包密码
sftp.zip.cron=0 03 * * * ?
sftp.zip.host=192.168.195.98
sftp.zip.port=22
sftp.zip.username=root
sftp.zip.password=123456
sftp.zip.remote-dir=/mya/test/
sftp.zip.zip-password=666
pom.xml
<!-- Zip4j for encrypted ZIP -->
<dependency>
<groupId>net.lingala.zip4j</groupId>
<artifactId>zip4j</artifactId>
<version>2.11.5</version>
</dependency>
<!-- JSch for SFTP -->
<dependency>
<groupId>com.hierynomus</groupId>
<artifactId>sshj</artifactId>
<version>0.32.0</version>
</dependency>
<!-- Configuration Processor -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-configuration-processor</artifactId>
<optional>true</optional>
</dependency>