Apache commons ftp API 的Spike

心已入冬 提交于 2020-03-01 05:54:12

由于公司给了我一个传FTP的任务,刚好可以学习一下,同时也进行了一些单元测试,spike了一把,哈哈。分享一下。

这里只对最简单常用的FtpClient的上传下载列表功能进行测试学习使用^-^

创建一个maven工程

在工作根目录创建目录

工程path/res/log

工程path/res/ftphome

配置pom文件。

<dependencies>
		<dependency>
			<groupId>org.apache.ftpserver</groupId>
			<artifactId>ftpserver-core</artifactId>
			<version>1.0.6</version>
		</dependency>


		<dependency>
			<groupId>commons-net</groupId>
			<artifactId>commons-net</artifactId>
			<version>3.3</version>
		</dependency>

		<dependency>
			<groupId>org.slf4j</groupId>
			<artifactId>slf4j-log4j12</artifactId>
			<version>1.5.2</version>
		</dependency>

		<dependency>
			<groupId>log4j</groupId>
			<artifactId>log4j</artifactId>
			<version>1.2.14</version>
		</dependency>

		<dependency>
			<groupId>junit</groupId>
			<artifactId>junit</artifactId>
			<version>4.11</version>
			<scope>test</scope>
		</dependency>
	</dependencies>
配置创建一个log4j.properties文件,放在src/test/resource下
log4j.rootLogger=INFO, stdout ,R 

log4j.appender.R=org.apache.log4j.RollingFileAppender 
log4j.appender.R.File=./res/log/ftpd.log
log4j.appender.R.MaxFileSize=10MB 
log4j.appender.R.MaxBackupIndex=10 
log4j.appender.R.layout=org.apache.log4j.PatternLayout 
log4j.appender.R.layout.ConversionPattern=[%5p] %d [%X{userName}] [%X{remoteIp}] %m%n

log4j.appender.stdout=org.apache.log4j.ConsoleAppender
log4j.appender.stdout.Target=System.out
log4j.appender.stdout.layout=org.apache.log4j.PatternLayout
log4j.appender.stdout.layout.ConversionPattern=%-d{yyyy-MM-dd HH:mm:ss}-%m%n
在src/test/resource下

创建一个目录upload/

存放两个文件 upload/rober.c.martin.jpg, upload/excel.xslx

还是在src/test/resource下加一个myusers.properties文件

ftpserver.user.test.userpassword=test
ftpserver.user.test.homedirectory=./res/ftphome
ftpserver.user.test.enableflag=true
ftpserver.user.test.writepermission=true
ftpserver.user.test.maxloginnumber=3
ftpserver.user.test.maxloginperip=3
ftpserver.user.test.idletime=0
ftpserver.user.test.uploadrate=0
ftpserver.user.test.downloadrate=0
一切准备好了,首先我们来测试FtpServer的启动

src/test/java/com/fengzidm/spike/ftp/TestFtpServer.java

package com.fengzidm.spike.ftp;

import static org.junit.Assert.*;

import java.io.File;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.junit.AfterClass;
import org.junit.BeforeClass;
import org.junit.Test;

public class TestFtpServer
{

	private static final String HOST = "127.0.0.1";
	private static final int PORT = 2222;
	
	 // 与myusers.properties配置相结合

        private static final String USERNAME = "test";
	private static final String PASSWORD = "test";

	private static FtpServer server;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception
	{
		try
		{
			FtpServerFactory serverFactory = new FtpServerFactory();

			ListenerFactory listenerFactory = new ListenerFactory();
			listenerFactory.setPort( PORT );
			// replace the default listener
			serverFactory.addListener( "default", listenerFactory.createListener() );

			PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
			userManagerFactory.setFile( new File( "myusers.properties" ) );
			
			 //设置为不加密码形式的密码
			userManagerFactory.setPasswordEncryptor( new ClearTextPasswordEncryptor() );
			serverFactory.setUserManager( userManagerFactory.createUserManager() );

			server = serverFactory.createServer();
			server.start();
		}
		catch ( Exception e )
		{
			e.printStackTrace();
			fail( e.getMessage() );
		}
	}

	@Test
	public void testConnect() throws Exception
	{

		FTPClient ftpClient = new FTPClient();
		ftpClient.connect( HOST, PORT );

		Integer replyCode = ftpClient.getReplyCode();
	
		//连接成功状态码为220
		assertEquals( new Integer( 220 ), replyCode );
	}

	@Test
	public void testLoginSuccess() throws Exception
	{
		FTPClient ftpClient = new FTPClient();
		ftpClient.connect( HOST, PORT );
		boolean result = ftpClient.login( USERNAME, PASSWORD );
		Integer replyCode = ftpClient.getReplyCode();
		assertTrue( result );
		assertEquals( new Integer( 230 ), replyCode );
	}

	@Test
	public void testLoginFailue() throws Exception
	{
		FTPClient ftpClient = new FTPClient();
		ftpClient.connect( HOST, PORT );

		String invalidPassword = "fuck";
		boolean result = ftpClient.login( USERNAME, invalidPassword );
		Integer replyCode = ftpClient.getReplyCode();
		assertFalse( result );

		 //登录失败返回状态码530
		assertEquals( new Integer( 530 ), replyCode );
	}

	@AfterClass
	public static void tearDown() throws Exception
	{
		if ( server != null )
		{
			try
			{
				server.stop();
				System.out.println( "testing server is stop" );
			}
			catch ( Exception igonred )
			{
			}
		}
	}
}
在测试FtpClient前

根据上面

创建一个FtpTestHelper类

src/test/java/com/fengzidm/spike/ftp/FtpTestHelper.java


package com.fengzidm.spike.ftp;

import java.io.File;

import org.apache.ftpserver.FtpServer;
import org.apache.ftpserver.FtpServerFactory;
import org.apache.ftpserver.ftplet.FtpException;
import org.apache.ftpserver.listener.ListenerFactory;
import org.apache.ftpserver.usermanager.ClearTextPasswordEncryptor;
import org.apache.ftpserver.usermanager.PropertiesUserManagerFactory;
import org.junit.Assert;

public class FtpTestHelper
{
	private FtpServer ftpServer;

	public void initialize( int port, String userManagerProperties )
	{
		try
		{
			FtpServerFactory serverFactory = new FtpServerFactory();

			ListenerFactory listenerFactory = new ListenerFactory();
			listenerFactory.setPort( port );
			// replace the default listener
			serverFactory.addListener( "default", listenerFactory.createListener() );

			PropertiesUserManagerFactory userManagerFactory = new PropertiesUserManagerFactory();
			userManagerFactory.setFile( new File( userManagerProperties ) );

			
			 // 设置为不加密码形式的密码
			userManagerFactory.setPasswordEncryptor( new ClearTextPasswordEncryptor() );
			serverFactory.setUserManager( userManagerFactory.createUserManager() );

			ftpServer = serverFactory.createServer();
		}
		catch ( Exception e )
		{
			e.printStackTrace();
			Assert.fail( "创建FtpServer失败,原因:" + e.getMessage() );
		}
	}

	public void startServer() throws FtpException
	{
		if ( ftpServer != null && ftpServer.isStopped() )
		{
			ftpServer.start();
		}
	}

	public void stopServer()
	{
		if ( ftpServer != null && !ftpServer.isStopped() )
		{
			ftpServer.stop();
		}
	}
}




创建一个TestCase类,帮组消除setUp 等这些代码

src/test/java/com/fengzidm/spike/ftp/FtpClientTestCase.java


package com.fengzidm.spike.ftp;

import static org.junit.Assert.assertTrue;

import java.io.IOException;
import java.net.SocketException;

import org.apache.commons.net.ftp.FTPClient;
import org.junit.After;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;

public abstract class FtpClientTestCase
{
	
	protected static final String HOST = "127.0.0.1";
	protected static final int PORT = 2222;

	//与myusers.properties配置相结合
        protected static final String USERNAME = "test";
	protected static final String PASSWORD = "test";

	protected static FtpTestHelper helper;

	@BeforeClass
	public static void setUpBeforeClass() throws Exception
	{
		helper = new FtpTestHelper();
		helper.initialize( PORT, "myusers.properties" );
		helper.startServer();
	}

	protected FTPClient ftpClient;

	@Before
	public void setUp() throws Exception
	{
		ftpClient = new FTPClient();
		ftpClient.connect( HOST, PORT );
		boolean result = ftpClient.login( USERNAME, PASSWORD );
		assertTrue( result );
	}

	@After
	public void tearDown() throws Exception
	{
		if ( ftpClient.isConnected() )
		{
			ftpClient.disconnect();
		}
	}

	@AfterClass
	public static void tearDownAfterClass() throws Exception
	{
		helper.stopServer();
	}
	
	protected FTPClient reConnectFtpClient() throws SocketException, IOException
	{
		if ( ftpClient.isConnected() )
		{
			ftpClient.disconnect();
		}

		ftpClient = new FTPClient();
		ftpClient.connect( HOST, PORT );
		ftpClient.login( USERNAME, PASSWORD );
		return ftpClient;
	}
}
我们进行FtpClient的API简单使用的测试


src/test/java/com/fengzidm/spike/ftp/TestFtpClient.java


package com.fengzidm.spike.ftp;

import static org.junit.Assert.*;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;

import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPListParseEngine;
import org.junit.Test;

public class TestFtpClient extends FtpClientTestCase {

	/**
	 * 上传文本文件
	 */
	@Test
	public void testStoreTextFile() throws Exception {
		ByteArrayInputStream bais = new ByteArrayInputStream(new String("中文").getBytes("GBK"));

		
		//通过改方法指定文件名,上传,通过指定dir目录 , 可以直接上传到目录里面 重复上传,文件里面是直接累加。 不会重新写文件!
		// store操作,会重新执行会覆盖原有文件
		boolean result = ftpClient.storeFile("upload-store.txt", bais);
		bais.close();
		assertTrue(result);

		 //方式二
		ftpClient.deleteFile("upload-store2.txt");
		OutputStream ops = ftpClient.storeFileStream("upload-store2.txt");
		ops.write(new String("中文").getBytes("GBK"));
		ops.flush();
		ops.close();

		assertEquals(new Integer(1), new Integer(reConnectFtpClient().listFiles("upload-store2.txt").length));

	}

	/**
	 * 上传excel,图片这类文件,要设置FileType为FTPClient. BINARY_FILE_TYPE 否则文件损坏
	 */
	@Test
	public void testStoreBinaryFile() throws Exception {
		ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);

		InputStream ips = this.getClass().getClassLoader().getResourceAsStream("upload/rober.c.martin.jpg");
		assertNotNull(ips);

		//上传图片
		boolean result = ftpClient.storeFile("rober.c.martin.jpg", ips);
		assertTrue(result);
		ips.close();

		ips = this.getClass().getClassLoader().getResourceAsStream("upload/excel.xlsx");
		assertNotNull(ips);

		 // 上传excel , 可以通过转换跟创建dir方法,直接指定上传的目录
		assertEquals("/", ftpClient.printWorkingDirectory());
		ftpClient.makeDirectory("excel");
		ftpClient.changeWorkingDirectory("excel");

		result = ftpClient.storeFile("excel.xlsx", ips);
		assertTrue(result);
		ips.close();
	}

	@Test
	public void testUploadZhNameFile() throws Exception {

		ftpClient.setCharset(Charset.forName("utf-8"));

		ByteArrayInputStream bais = new ByteArrayInputStream(new String("中文").getBytes());

		boolean result = ftpClient.storeFile(new String("测试中文名.txt".getBytes(), "iso8859-1"), bais);
		assertTrue(result);

		FTPFile[] files = ftpClient.listFiles(new String("测试中文名.txt".getBytes(), "iso8859-1"));
		assertEquals(new Integer(1), new Integer(files.length));

		ftpClient.deleteFile(new String("测试中文名.txt".getBytes(), "iso8859-1"));
	}

	/**
	 * 下载文件
	 */
	@Test
	public void testRetrieveFile() throws Exception {
		String text = "text";

		boolean result = ftpClient.storeFile("upload-store.txt", new ByteArrayInputStream(new String(text).getBytes()));
		assertTrue(result);

		ByteArrayOutputStream baos = new ByteArrayOutputStream();
		result = ftpClient.retrieveFile("upload-store.txt", baos);
		assertTrue(result);

		assertEquals(text, new String(baos.toByteArray()));
		baos.close();

		//方式二
		OutputStream ops = ftpClient.storeFileStream("upload-store.txt");
		byte[] bytes = new byte[text.length()];
		ops.write(bytes);
		ops.flush();
		ops.close();
		assertEquals(text, new String(text));
	}

	/**
	 * 删除服务器文件
	 */
	@Test
	public void testDeleteFile() throws Exception {

		boolean result = ftpClient.storeFile("upload-store.txt", new ByteArrayInputStream(new String("text").getBytes()));

		 //上传成功
		assertTrue(result);

		result = ftpClient.deleteFile("upload-store.txt");

		// 判断删除成功
		assertTrue(result);
	}

	/**
	 * 目录间跳转
	 */
	@Test
	public void testDirApi() throws Exception {
		boolean result;

		assertEquals("/", ftpClient.printWorkingDirectory());

		 //返回boolean 值,如果已经存在 ,getReplyCode为550码,并返回false值
		ftpClient.makeDirectory("test-dir-1");
		ftpClient.makeDirectory("test-dir-1/test-dir-11");

		 // 如果test-dir-2这一层目录没有,不能一次创建几层的目录
		result = ftpClient.makeDirectory("test-dir-2/test-dir-2");
		assertFalse(result);

		 // changeWorkingDirectory 有相应目录返回true,无相应目录返回false
		result = ftpClient.changeWorkingDirectory("test-dir-1");
		assertTrue(result);
		assertEquals("/test-dir-1", ftpClient.printWorkingDirectory());

		result = ftpClient.changeWorkingDirectory("invalid-dir");
		assertFalse(result);

		 // 返回上一级
		ftpClient.changeToParentDirectory();
		assertEquals("/", ftpClient.printWorkingDirectory());

		 // 删除一个目录 ,如果有子目录或有子文件,则删除不成功,需要递归删除
		result = ftpClient.makeDirectory("test-remove-dir");
		assertTrue(result);
		result = ftpClient.removeDirectory("test-remove-dir");
		assertTrue(result);
	}

	@Test
	public void testListFiles() throws Exception {
		ftpClient.storeFile("test-list-files.txt", new ByteArrayInputStream("恒拓开源".getBytes()));

		FTPFile[] ftpFiles = ftpClient.listFiles();
		assertTrue(ftpFiles.length > 0);

		for (FTPFile each : ftpFiles) {
			System.out.println("######################################");
			System.out.println(each.getUser());
			System.out.println(each.getGroup());
			System.out.println(each.getLink());
			System.out.println(each.getRawListing());
			System.out.println("isFile : " + each.isFile());
			System.out.println("type : " + each.getType());
			System.out.println("######################################");
		}

		ftpClient.makeDirectory("test-list-file-dir");
		ftpClient.storeFile("test-list-file-dir/list.txt", new ByteArrayInputStream("fuck".getBytes()));

		//list 传入一个参数,指定 list改目录
		ftpFiles = ftpClient.listFiles("test-list-file-dir");
		assertEquals(new Integer(1), new Integer(ftpFiles.length));

		 // 清场工作
		ftpClient.deleteFile("test-list-files.txt");
		ftpClient.deleteFile("test-list-file-dir/list.txt");
		ftpClient.removeDirectory("test-list-file-dir");
	}

	/**
	 * 测试limit一个大小
	 */
	@Test
	public void testListFilesLimitSize() throws Exception {

		ftpClient.makeDirectory("test-list-files-limit");
		ftpClient.storeFile("test-list-files-limit/test-list-files-limit-size-1.txt", new ByteArrayInputStream("恒拓开源".getBytes()));
		ftpClient.storeFile("test-list-files-limit/test-list-files-limit-size-2.txt", new ByteArrayInputStream("恒拓开源".getBytes()));
		ftpClient.storeFile("test-list-files-limit/test-list-files-limit-size-3.txt", new ByteArrayInputStream("恒拓开源".getBytes()));

		FTPListParseEngine engine = ftpClient.initiateListParsing("test-list-files-limit");
		FTPFile[] files = engine.getNext(2);
		assertEquals(new Integer(2), new Integer(files.length));

		files = engine.getNext(1);
		assertEquals(new Integer(1), new Integer(files.length));

		ftpClient.deleteFile("test-list-files-limit/test-list-files-limit-size-1.txt");
		ftpClient.deleteFile("test-list-files-limit/test-list-files-limit-size-2.txt");
		ftpClient.deleteFile("test-list-files-limit/test-list-files-limit-size-3.txt");
		ftpClient.removeDirectory("test-list-files-limit");
	}

}
通过阅读API,有个Util类跟CopyStreamListener类结合可以在上传下载流传输的时候对字节流进行回调监控。同样做一个测试


src/test/java/com/fengzidm/spike/ftp/TestCopyStreamListener.java


package com.fengzidm.spike.ftp;

import static org.junit.Assert.*;

import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import org.apache.commons.net.io.CopyStreamEvent;
import org.apache.commons.net.io.CopyStreamListener;
import org.apache.commons.net.io.Util;
import org.junit.After;
import org.junit.Test;

/**
 * 通过CopyStreamListener,可以有事件监听目标流读书的大小
 */
public class TestCopyStreamListener extends FtpClientTestCase
{

	private CopyStreamListener listener;

	private String FILE_PATH = "test-copy-stream-listener-upload.txt";

	@Override
	public void setUp() throws Exception
	{
		super.setUp();

		listener = new CopyStreamListener()
		{
			@Override
			public void bytesTransferred( long totalBytesTransferred, int bytesTransferred, long streamSize )
			{
				System.out.println( "#############################" );
				System.out.println( "totalBytesTransferred:" + totalBytesTransferred );
				System.out.println( "bytesTransferred:" + bytesTransferred );
				System.out.println( "streamSize:" + streamSize );
				System.out.println( "#############################" );
			}

			@Override
			public void bytesTransferred( CopyStreamEvent event )
			{
				System.out.println( "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" );
				System.out.println( "getBytesTransferred:" + event.getBytesTransferred() );
				System.out.println( "getStreamSize:" + event.getStreamSize() );
				System.out.println( "getTotalBytesTransferred:" + event.getTotalBytesTransferred() );
				System.out.println( "getSource:" + event.getSource() );
				System.out.println( "$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$$" );
			}
		};

	}

	/**
	 * 上传文件
	 */
	@Test
	public void testStoreFile() throws Exception
	{
		String text = "恒拓开源";
		ByteArrayInputStream bais = new ByteArrayInputStream( new String( text ).getBytes() );
		OutputStream ops = ftpClient.storeFileStream( FILE_PATH );
		Long len = Util.copyStream( bais, ops, 2, text.getBytes().length, listener, true );
		
		// 一定要close
		ops.close();
		bais.close();

		assertEquals( new Long( text.getBytes().length ), len );

	}

	@Test
	public void testRetriveFile() throws Exception
	{
		String text = "恒拓开源";
		ftpClient.storeFile( FILE_PATH, new ByteArrayInputStream( text.getBytes() ) );

		InputStream ips = ftpClient.retrieveFileStream( FILE_PATH );
		BufferedOutputStream bops = new BufferedOutputStream( new ByteArrayOutputStream() );
		Long len = Util.copyStream( ips, bops, 5, text.getBytes().length, listener );
		
		bops.close();
		ips.close();
		
		assertEquals( new Long( text.getBytes().length ), len );
	}

	@After
	public void tearDown() throws Exception
	{
		ftpClient.deleteFile( FILE_PATH );

		super.tearDown();
	}

}
然后个人总结简单列举一下API



FTPClient

连接

void connect(String hostname,int port)  

关闭连接

void disconnect()

进行登录

boolean login(String username, String password)

退出登录

boolean logout()

设置超时

void setConnectTimeout()

设置命令编码

void setControlEncoding(String encoding); 

上传文件,如果文件已经存在,追加文件内容

boolean appendFile(String remote, InputStream local)

OutputStream appendFileStream(String remote)

上传文件,如果文件已经存在,重新覆盖其内容

boolean storeFile(String remote, InputStream local)

OutputStream storeFileStream(String remote)

删除文件

boolean deleteFile(String pathname)

下载文件

boolean retrieveFile(String remote, OutputStream local)

InputStream   retrieveFileStream(String remote)

重命名(移动)文件

boolean rename(String from, String to)

文件列表

FTPFile[] listFiles()

FTPFile[] listFiles(String pathname)

FTPFile[] listFiles(String pathname, FTPFileFilter filter)

目录列表

FTPFile[] listDirectories()

FTPFile[] listDirectories(String parent)

创建目录

boolean makeDirectory(String pathname)

删除目录(弱有子目录或文件,执行返回false,需递归删除)

boolean removeDirectory(String pathname)

获取当前工作目录

String printWorkingDirectory()

改变工作目录

boolean changeToParentDirectory()

boolean changeWorkingDirectory(String pathname)

设置文件类型 默认为ASCII 上传图片excel等要设置Binary类型

boolean setFileType(int fileType)

设置为主动工作模式

void enterLocalActiveMode()

设置为被动工作模式

void enterLocalPassiveMode()

获得响应码

int   getReplyCode()

获得响应信息

String     getReplyString()

String[] getReplyStrings()

 

 

FTPClientConfig

//设置服务端语言(“zh,da,fr这些”)

void  setServerLanguageCode(String serverLanguageCode)

//设置日期转换格式

void  setRecentDateFormatStr(String recentDateFormatStr)

void  setDefaultDateFormatStr(String defaultDateFormatStr)

FTPClient f=FTPClient();

   FTPClientConfig conf = new FTPClientConfig(FTPClientConfig.SYST_UNIX);

   conf.setServerLanguageCode("fr");

   f.configure(conf);

 

FTPListParseEngine 用于list**操作时可以指定如分页大小之类信息

FTPClient f=FTPClient();

    f.connect(server);

    f.login(username, password);

    FTPListParseEngine engine = f.initiateListParsing(directory);

 

    while (engine.hasNext()) {

       FTPFile[] files = engine.getNext(25);  // "page size" you want

       //do whatever you want with these files, display them, etc.

       //expensive FTPFile objects not created until needed.

    }


最后:

这里使用到只是简单FTPClient , 在commons.net.ftp包中,还有像FTPHTTPClient , FTPSClient , 这些类大家应该琢磨一下都知道是干麻用的啦^-^。




易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!