来一个复古的技术FTP

news2025/5/20 2:54:33

背景

       10年前的老代码,需要升级springboot框架,在升级过程中,测试业务流程里,有FTP的下载业务,不管测试环境如何测试,都没有成功,最后只能自己搭建一个FTP服务器,写一个ftp-demo来测试。记录一下过程,防止后续使用的时候在来一次。

CentOS7安装FTP

步骤1:安装 vsftpd

# 更新系统软件包
sudo yum update -y

# 安装 vsftpd
sudo yum install vsftpd -y

步骤2:启动服务并设置开机自启

# 启动 vsftpd 服务
sudo systemctl start vsftpd
     
# 设置开机自启
sudo systemctl enable vsftpd
     
# 检查服务状态
sudo systemctl status vsftpd

# 停止服务
sudo systemctl stop vsftpd

# 重启服务
sudo systemctl restart vsftpd

步骤3:配置防火墙

# 开放FTP端口(21和被动模式端口范围)
sudo firewall-cmd --zone=public --add-port=21/tcp --permanent
sudo firewall-cmd --zone=public --add-port=30000-31000/tcp --permanent
sudo firewall-cmd --zone=public --add-service=ftp --permanent

# 重新加载防火墙规则
sudo firewall-cmd --reload

步骤4:配置 vsftpd

cd /etc/vsftpd/

cp vsftpd.conf vsftpd.conf_default
修改下列参数的值
anonymous_enable=NO          #禁止匿名登录FTP服务器
local_enable=YES             #允许本地用户登录FTP服务器
listen=YES                   #监听IPv4 sockets
#listen_ipv6=YES             #关闭监听IPv6 sockets或者改为NO
chroot_local_user=YES        #全部用户被限制在主目录
chroot_list_enable=YES       #启用例外用户名单
chroot_list_file=/etc/vsftpd/chroot_list  #指定例外用户列表文件,列表中用户不被锁定在主目录
allow_writeable_chroot=YES
pasv_enable=YES
pasv_min_port=30000
pasv_max_port=31000
以上配置可以直接用下面命令进行替换修改(一句一句执行)
sed -i 's/anonymous_enable=YES/anonymous_enable=NO/' /etc/vsftpd/vsftpd.conf
sed -i 's/listen=NO/listen=YES/' /etc/vsftpd/vsftpd.conf
sed -i 's/listen_ipv6=YES/listen_ipv6=NO/' /etc/vsftpd/vsftpd.conf
sed -i 's/#chroot_local_user=YES/chroot_local_user=YES/' /etc/vsftpd/vsftpd.conf
sed -i 's/#chroot_list_enable=YES/chroot_list_enable=YES/' /etc/vsftpd/vsftpd.conf
sed -i 's/#chroot_list_file=/chroot_list_file=/' /etc/vsftpd/vsftpd.conf
echo "allow_writeable_chroot=YES" >> /etc/vsftpd/vsftpd.conf
# 被动模式
echo "pasv_enable=YES">> /etc/vsftpd/vsftpd.conf
echo "pasv_min_port=30001">> /etc/vsftpd/vsftpd.conf
echo "pasv_max_port=30010">> /etc/vsftpd/vsftpd.conf

 步骤5:创建FTP用户

# 创建用户(例如用户名为 ftpuser,目录为 /home/ftpuser)
sudo useradd -m -d /home/ftpuser -s /sbin/nologin ftpuser

# 设置用户密码
sudo passwd ftpuser

# 确保用户目录权限正确
sudo chmod -R 750 /home/ftpuser
sudo chown -R ftpuser: /home/ftpuser


例如:
sudo useradd -m -d /home/douzi -s /sbin/nologin douzi
sudo passwd 123456
sudo chmod -R 750 /home/douzi
sudo chown -R douzi: /home/douzi

步骤6:重启vsftpd服务

# 启动 vsftpd 服务
sudo systemctl restart vsftpd

步骤7:客户端登录测试

ftp localhost
问题1:ftp客户端未安装

解决办法:
sudo yum install ftp
问题2:登录失败

找的截图,不要纠结里边的命令,只看红框部分即可

 解决办法:
vi /etc/pam.d/vsftpd

# 注释以下一行
#auth       required    pam_shells.so

重启vsftpd服务,再进行登录提示:

再手动在/etc/vsftpd/目录下创建一下chroot_list文件即可

cd /etc/vsftpd/

touch /etc/vsftpd/chroot_list

 然后重启vsftpd服务,登录即可正常:

登陆后默认为二进制传输模式

扩展FTP客户端命令:

dir ls cd pwd lcd(切换工作目录)  mkdir rmdir get mget(下载多个文件) put mput(上传多个文件) rename delete mdelete(删除多个ftp文件) rmdir ascii,bin(切换传输模式) close(关闭链接) open(重连ftp) quit

扩展防火墙命令:

一、防火墙的开启、关闭、禁用命令
设置开机启用防火墙:systemctl enable firewalld
设置开机禁用防火墙:systemctl disable firewalld
启动防火墙:       systemctl start firewalld
关闭防火墙:       systemctl stop firewalld 或 systemctl stop firewalld.service
检查防火墙状态     systemctl status firewalld

二、使用firewall-cmd配置端口
查看防火墙状态: firewall-cmd --state
重新加载配置:   firewall-cmd --reload
查看开放的端口: firewall-cmd --list-ports
开启防火墙端口: firewall-cmd --zone=public --add-port=9200/tcp --permanent

扩展FTP配置项说明:

1. 基础访问控制
配置项默认值说明
anonymous_enableNO是否允许匿名登录(YES/NO
local_enableYES是否允许本地用户登录(YES/NO
write_enableYES是否允许写入操作(上传/删除/重命名)
2. 权限与安全
配置项默认值说明
local_umask022本地用户创建文件的权限掩码(022表示文件权限为644,目录为755
chroot_local_userNO是否将本地用户限制在其主目录(需配合allow_writeable_chroot=YES使用)
allow_writeable_chrootNO允许被chroot的用户目录可写(需chroot_local_user=YES
3. 连接与日志
配置项默认值说明
dirmessage_enableYES显示目录欢迎消息(消息文件默认为.message
xferlog_enableYES启用传输日志(记录上传/下载)
xferlog_file/var/log/vsftpd.log指定日志文件路径
xferlog_std_formatYES使用标准FTP日志格式(兼容wu-ftp格式)
connect_from_port_20YES主动模式时,强制数据连接从端口20发起
4. 超时设置
配置项默认值说明
idle_session_timeout600空闲会话超时时间(秒)
data_connection_timeout120数据连接超时时间(秒)
5. 被动模式(PASV)配置
配置项默认值说明
pasv_enableYES启用被动模式
pasv_min_port-被动模式端口范围下限(如30000
pasv_max_port-被动模式端口范围上限(如31000
pasv_address-服务器公网IP(NAT环境下需指定)
6. 高级选项
配置项默认值说明
listenNO以独立模式运行(YES=IPv4,NO=通过xinetd启动)
listen_ipv6NO启用IPv6监听
tcp_wrappersYES使用TCP Wrappers进行主机访问控制
userlist_enableNO启用用户列表控制(userlist_file指定文件)
userlist_denyYES用户列表中的用户是否被拒绝(YES=黑名单,NO=白名单)

FTP-DEMO Springboot3代码

maven需要引用的包:

    ......
    <parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.4.5</version>
		<relativePath /> <!-- lookup parent from repository -->
	</parent>    
    ......
    <properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>

		<dependency>
			<groupId>cn.hutool</groupId>
			<artifactId>hutool-all</artifactId>
			<version>5.8.35</version>
		</dependency>
		<dependency>
			<groupId>commons-net</groupId>
			<artifactId>commons-net</artifactId>
			<version>3.11.1</version>
		</dependency>
		<dependency>
			<groupId>com.jcraft</groupId>
			<artifactId>jsch</artifactId>
			<version>0.1.55</version>
		</dependency>
		<dependency>
			<groupId>org.projectlombok</groupId>
			<artifactId>lombok</artifactId>
		</dependency>
		<dependency>
			<groupId>ch.qos.logback</groupId>
			<artifactId>logback-core</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>
    ......

配置文件:

ftp:
  # 服务器地址
  host: 192.168.1.56
  # 端口号
  port: 21
  # 用户名
  userName: douzi
  # 密码
  password: 123456

代码部分:

package com.wd.ftp.config;

import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Configuration;
 
/**
 * ftp配置
 */
@Configuration
public class FtpConfig {
 
    /**
     * 服务器地址
     */
    private static String host;
 
    /**
     * 端口
     */
    private static Integer port;
 
    /**
     * 用户名
     */
    private static String userName;
 
    /**
     * 密码
     */
    private static String password;
 
    @Value("${ftp.host}")
    public void setHost(String host) {
        FtpConfig.host = host;
    }
 
    public static String getHost() {
        return host;
    }
 
    @Value("${ftp.port}")
    public void setPort(Integer port) {
        FtpConfig.port = port;
    }
 
    public static Integer getPort() {
        return port;
    }
 
    @Value("${ftp.userName}")
    public void setUserName(String userName) {
        FtpConfig.userName = userName;
    }
 
    public static String getUserName() {
        return userName;
    }
 
    @Value("${ftp.password}")
    public void setPassword(String password) {
        FtpConfig.password = password;
    }
 
    public static String getPassword() {
        return password;
    }
}
package com.wd.ftp.util;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.apache.commons.net.ftp.FTPFile;

import com.wd.ftp.config.FtpConfig;

import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.extra.ftp.Ftp;
import cn.hutool.extra.ftp.FtpMode;
import lombok.extern.slf4j.Slf4j;
 
/**
 * FTP服务工具类
 */
@Slf4j
public class FtpUtil {
    /**
     * 获取 FTPClient对象
     */
    private static Ftp getFTPClient() {
        try {
            if(StrUtil.isBlank(FtpConfig.getHost()) || FtpConfig.getPort() == null
                || StrUtil.isBlank(FtpConfig.getUserName()) || StrUtil.isBlank(FtpConfig.getPassword())) {
                throw new RuntimeException("ftp配置信息不能为空");
            }
 
            Ftp ftp = new Ftp(FtpConfig.getHost(),FtpConfig.getPort(),FtpConfig.getUserName(),FtpConfig.getPassword());
            //设置为被动模式,防止防火墙拦截
            ftp.setMode(FtpMode.Passive);
            return ftp;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("获取ftp客户端异常",e);
            throw new RuntimeException("获取ftp客户端异常:"+e.getMessage());
        }
    }
    
    /**
     * 下载ftp服务器上的文件到本地
     * @param remoteFile    ftp上的文件路径
     * @param localFile     输出的目录,使用服务端的文件名
     */
    public static void download(String remoteFile, String localPath) {
        if(StrUtil.isBlank(remoteFile) || StrUtil.isBlank(localPath)) {
            return;
        }
 
        Ftp ftp = getFTPClient();
        try {
            if(!FileUtil.exist(localPath)){
                FileUtil.mkdir(localPath);
            }    
 
            File lFile = FileUtil.file(localPath);
            ftp.download(remoteFile, lFile);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("FTP文件下载异常",e);
        } finally {
            //关闭连接
            try {
                if(ftp != null)  ftp.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    /**
     * 本地文件上传到ftp服务器上
     * @param remoteDir 上传的ftp目录
     * @param remoteFileName  保存到ftp服务器上的名称
     * @param localFile 本地文件全名称
     */
    public static boolean upload(String remoteDir, String remoteFileName, String localFile) {
        if(StrUtil.isBlank(remoteDir) || StrUtil.isBlank(remoteFileName) || StrUtil.isBlank(localFile)) {
            return false;
        }
        Ftp ftp = getFTPClient();
        try {
            File lFile = FileUtil.file(localFile);
            if(!lFile.exists()) {
                log.error("本地文件不存在");
                return false;
            }
 
            if(StrUtil.isBlank(remoteFileName)) {
                return ftp.upload(remoteDir, lFile);
            } else {
                return ftp.upload(remoteDir, remoteFileName, lFile);
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.error("文件上传FTP异常",e);
            return false;
        } finally {
            //关闭连接
            try {
                if(ftp != null)  ftp.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    /**
     * 删除FTP服务器中的文件
     * @param remoteFile    ftp上的文件路径
     */
    public static boolean delFile(String remoteFile) {
        if(StrUtil.isBlank(remoteFile)) {
            return false;
        }
 
        Ftp ftp = getFTPClient();
        try {
            return ftp.delFile(remoteFile);
        } catch (Exception e) {
            e.printStackTrace();
            log.error("删除FTP服务器中的文件异常",e);
            return false;
        } finally {
            //关闭连接
            try {
                if(ftp != null)  ftp.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
 
    /**
     * 遍历某个目录下所有文件,不会递归遍历
     * @param path    目录
     */
    public static List<String> listFile(String path) {
        List<String> listFile = new ArrayList<>();
        Ftp ftp = getFTPClient();
        try {
            FTPFile[] ftpFiles = ftp.lsFiles(path);
            for (int i = 0; i < ftpFiles.length; i++) {
                FTPFile ftpFile = ftpFiles[i];
                if(ftpFile.isFile()){
                    listFile.add(ftpFile.getName());
                }
            }
            return listFile;
        } catch (Exception e) {
            e.printStackTrace();
            log.error("遍历某个目录下所有文件异常",e);
            return null;
        } finally {
            //关闭连接
            try {
                if(ftp != null)  ftp.close();
            } catch (IOException e) {
                throw new RuntimeException(e);
            }
        }
    }
}
package com.wd.ftp.controller;

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

import com.wd.ftp.util.FtpUtil;
import com.wd.ftp.util.SftpUtil;

import jakarta.servlet.http.HttpServletRequest;

@RestController
public class FtpController {
	
	
	 /**
     * 下载ftp服务器上的文件到本地
     * @param rf    ftp上的文件路径
     * @param lp     输出的目录,使用服务端的文件名
     */
	@GetMapping("/download")
	public String download(
			@RequestParam(required = false, defaultValue = "1.xml") String rf, 
			@RequestParam(required = false, defaultValue = "/ftp/download/") String lp) {
		FtpUtil.download(rf, lp);
		return "success";
	}

}

可以根据FtpUtil扩展SftpUtil的代码,从而支持Sftp模式。

执行效果:

随便造一个1.xml上传ftp

cd /home/douzi 然后查看内容

 

浏览器执行

http://localhost:8080/download

windows下,代码在哪个盘执行,就会生成在哪个盘。

下载成功!其他上传,删除等功能自行试验。

打完收工。

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2378806.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

OpenWebUI新突破,MCPO框架解锁MCP工具新玩法

大家好&#xff0c;Open WebUI 迎来重要更新&#xff0c;现已正式支持 MCP 工具服务器&#xff0c;但 MCP 工具服务器需由兼容 OpenAPI 的代理作为前端。mcpo 是一款实用代理&#xff0c;经测试&#xff0c;它能让开发者使用 MCP 服务器命令和标准 OpenAPI 服务器工具&#xff…

TRTC实时对话式AI解决方案,助力人机语音交互极致体验

近年来&#xff0c;AI热度持续攀升&#xff0c;无论是融资规模还是用户热度都大幅增长。2023 年&#xff0c;中国 AI 行业融资规模达2631亿人民币&#xff0c;较2022年上升51%&#xff1b;2024年第二季度&#xff0c;全球 AI 初创企业融资规模为 240 亿美金&#xff0c;较第一季…

Linux安全篇 --firewalld

一、Firewalld 防火墙概述 1、Firewalld 简介 firewalld 的作用是为包过滤机制提供匹配规则(或称为策略)&#xff0c;通过各种不同的规则告诉netfilter 对来自指定源、前往指定目的或具有某些协议特征的数据包采取何种处理方式为了更加方便地组织和管理防火墙,firewalld 提供…

系分论文《论系统需求分析方法及应用》

系统分析师论文范文系列 【摘要】 2022年6月&#xff0c;我作为系统分析师参与了某金融机构“智能信贷风控系统”的建设项目。该系统旨在通过对业务流程的数字化重构&#xff0c;优化信贷审批效率并降低风险。项目涉及信贷申请、资质审核、风险评估、额度审批等核心流程&#x…

LIIGO ❤️ RUST: 12 YEARS

LIIGO &#x1f496; RUST: 12 YEARS 今天是RUST语言1.0发布十周年纪念日。十年前的今天&#xff0c;2015年的今天&#xff0c;Rust 1.0 正式发行。这是值得全球Rust支持者隆重纪念的日子。我借此机会衷心感谢Rust语言创始人Graydon Hoare&#xff0c;Mozilla公司&#xff0c;…

Trivy:让你时刻掌控的开源安全扫描器

深入了解 Trivy:全面的安全扫描工具 在如今互联网快速发展的时代,软件的安全性显得尤为重要。随着应用程序的复杂性增加,其可能带来的安全漏洞也在不断增多。如何快速、准确地发现这些潜在威胁是每个开发者和运维人员心中的课题。今天,我们将为大家介绍一个开源的安全扫描…

LlamaIndex 第八篇 MilvusVectorStore

本指南演示了如何使用 LlamaIndex 和 Milvus 构建一个检索增强生成&#xff08;RAG&#xff09;系统。 RAG 系统将检索系统与生成模型相结合&#xff0c;根据给定的提示生成新的文本。该系统首先使用 Milvus 等向量相似性搜索引擎从语料库中检索相关文档&#xff0c;然后使用生…

2022河南CCPC(前四题)

签到题目 #include <bits/stdc.h> using namespace std; #define int long long #define PII pair<int,int> #define fi first #define se second #define endl \n #define IOS ios::sync_with_stdio(0),cin.tie(0),cout.tie(0);void solve() {int n;cin>>…

谷歌浏览器(Google Chrome)136.0.7103.93便携增强版|Win中文|安装教程

软件下载 【名称】&#xff1a;谷歌浏览器&#xff08;Google Chrome&#xff09;136.0.7103.93 【大小】&#xff1a;170M 【语言】&#xff1a;简体中文 【安装环境】&#xff1a;Win10/Win11 【夸克网盘下载链接】&#xff08;务必手机注册&#xff09;&#xff1a; h…

高可用消息队列实战:AWS SQS 在分布式系统中的核心解决方案

引言&#xff1a;消息队列的“不可替代性” 在微服务架构和分布式系统盛行的今天&#xff0c;消息队列&#xff08;Message Queue&#xff09; 已成为解决系统解耦、流量削峰、异步处理等难题的核心组件。然而&#xff0c;传统的自建消息队列&#xff08;如RabbitMQ、Kafka&am…

「Mac畅玩AIGC与多模态41」开发篇36 - 用 ArkTS 构建聚合搜索前端页面

一、概述 本篇基于上一节 Python 实现的双通道搜索服务&#xff08;聚合 SearxNG 本地知识库&#xff09;&#xff0c;构建一个完整的 HarmonyOS ArkTS 前端页面。用户可在输入框中输入关键词&#xff0c;实时查询本地服务 http://localhost:5001/search?q...&#xff0c;返…

springCloud/Alibaba常用中间件之Seata分布式事务

文章目录 SpringCloud Alibaba:依赖版本补充Seata处理分布式事务(AT模式)AT模式介绍核心组件介绍AT的工作流程&#xff1a;两阶段提交&#xff08;**2PC**&#xff09; Seata-AT模式使用Seata(2.0.0)下载、配置和启动Seata案例实战前置代码添加全局注解 GlobalTransactional Sp…

Datawhale FastAPI Web框架5月第1次笔记

原课程地址&#xff1a; FastAPI Web框架https://www.datawhale.cn/learn/summary/164本次难点&#xff1a; 切换python的版本为3.10 作业过程 启动&#xff1a; jupyter notebook 首先我们要确保自己的python版本是3.10 import sys print(sys.version) 第一个fastapi…

操作系统:os概述

操作系统&#xff1a;OS概述 程序、进程与线程无极二级目录三级目录 程序、进程与线程 指令执行需要那些条件&#xff1f;CPU内存 需要数据和 无极 二级目录 三级目录

LLaMA-Factory:环境准备

一、硬件和系统 操作系统: Ubuntu 24.04.2 LTS&#xff08;64位&#xff09;GPU: NVIDIA RTX 4090 笔记本 GPU&#xff0c;16GB显存CPU: 建议高性能多核 CPU&#xff08;如 Intel i7/i9 或 AMD Ryzen 7/9&#xff09;以支持数据预处理&#xff0c;我的是32核。RAM: 至少 32GB&…

ArrayList-集合使用

自动扩容&#xff0c;集合的长度可以变化&#xff0c;而数组长度不变&#xff0c;集合更加灵活。 集合只能存引用数据类型&#xff0c;不能直接存基本数据类型&#xff0c;除非包装 ArrayList会拿[]展示数据

一分钟用 MCP 上线一个 贪吃蛇 小游戏(CodeBuddy版)

我正在参加CodeBuddy「首席试玩官」内容创作大赛&#xff0c;本文所使用的 CodeBuddy 免费下载链接&#xff1a;腾讯云代码助手 CodeBuddy - AI 时代的智能编程伙伴 你好&#xff0c;我是悟空。 背景 上篇我们用 MCP 上线了一个 2048 小游戏&#xff0c;这次我们继续做一个 …

TTS:F5-TTS 带有 ConvNeXt V2 的扩散变换器

1&#xff0c;项目简介 F5-TTS 于英文生成领域表现卓越&#xff0c;发音标准程度在本次评测软件中独占鳌头。再者&#xff0c;官方预设的多角色生成模式独具匠心&#xff0c;能够配置多个角色&#xff0c;一次性为多角色、多情绪生成对话式语音&#xff0c;别出心裁。 最低配置…

大型语言模型中的QKV与多头注意力机制解析

每周跟踪AI热点新闻动向和震撼发展 想要探索生成式人工智能的前沿进展吗&#xff1f;订阅我们的简报&#xff0c;深入解析最新的技术突破、实际应用案例和未来的趋势。与全球数同行一同&#xff0c;从行业内部的深度分析和实用指南中受益。不要错过这个机会&#xff0c;成为AI领…

基于地图的数据可视化:解锁地理数据的真正价值

目录 一、基于地图的数据可视化概述 &#xff08;一&#xff09;定义与内涵 &#xff08;二&#xff09;重要性与意义 二、基于地图的数据可视化的实现方式 &#xff08;一&#xff09;数据收集与整理 &#xff08;二&#xff09;选择合适的可视化工具 &#xff08;三&a…