当前位置: 首页 > news >正文

springboot项目添加定时任务,用sftp推送zip包到目标服务器

用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());@Autowiredprivate 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.zipString 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地址规划数据CSVaddCsvToZip(zipFile, parameters, v4FileName,V4_HEADER, v4Mapper.getAllDataByDate(),new CsvRowMapper<SftpDto>() {@Overridepublic 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地址规划数据CSVString v6FileName = "V6_Subnet_"+previousDayFormatted+".csv";addCsvToZip(zipFile, parameters, v6FileName,V4_HEADER, v6Mapper.getAllDataByZero(),new CsvRowMapper<SftpDto>() {@Overridepublic 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地址备案数据CSVString raFileName = "IP_address_"+previousDayFormatted+".csv";addCsvToZip(zipFile, parameters, raFileName,RA_HEADER, raMapper.getAllDataByZero(),new CsvRowMapper<SftpDto>() {@Overridepublic 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);// 添加到ZIPzipFile.addFile(tempFile, parameters);} finally {FileUtils.deleteQuietly(tempFile);}}private void uploadViaSftp(String zipFileName) throws Exception {try (SSHClient ssh = new SSHClient()) {// 跳过主机密钥验证(仅用于测试)ssh.addHostKeyVerifier(new HostKeyVerifier() {@Overridepublic boolean verify(String hostname, int port, PublicKey key) {return true; // 跳过主机密钥验证(仅用于测试)}@Overridepublic 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>

相关文章:

  • 车载信息安全 --- 密钥管理
  • Anaconda笔记
  • C语言-习题整理(1)
  • 第 2 篇:快速上手 Framer Motion(实操入门)
  • 烽火ai场控接入deepseek自动回复话术软件
  • 【Python】列表的创建:[[] for _ in range(2)] 与 [[]] * 2有什么区别?
  • STM32F407实现内部FLASH的读写功能
  • 【MySQL】MySQL数据库 —— 简单认识
  • 第3篇:深入 Framer Motion Variants:掌握组件动画编排的艺术
  • django项目之添加资产信息功能
  • YOLOv3模型架构与原理详解
  • Python 冷门魔术方法
  • Redis 高可用集群搭建与优化实践
  • 波束形成(BF)从算法仿真到工程源码实现-第十节-非线性波束形成
  • vue中使用swiper坑记录
  • 二、The Power of LLM Function Calling
  • 京东商品详情API接口调用讲解(实战案例)
  • Day56 | 99. 恢复二叉搜索树、103. 二叉树的锯齿形层序遍历、109. 有序链表转换二叉搜索树、113. 路径总和 II
  • 使用openpyxl时的一些注意点
  • 1.2 腾讯校招通关指南-面试官评分标准:技术岗/产品岗核心考核点揭秘
  • 拒绝“假期刺客”,澎湃启动“五一”消费维权线索征集
  • 祥源文旅:2024年营业收入约8.64亿元,今年旅游经济总体预期更为乐观
  • 景临已任四川省工商联党组书记
  • 美国佛罗里达州立大学发生枪击事件
  • 圆桌|艺术院校校长怎么看AI时代的艺术教育
  • 天津一季度GDP为4188.09亿元,同比增长5.8%