记录一下最近碰到的需求问题,文件同步,将oss文件同步到ftp服务器上,首先到手的ftp服务器是这样的,ssl证书过期,加密方式为隐式传输,默认端口990。FileZilla测试没有问题,直接上代码测试,结果第一步就卡住了,卡在了hostname的连接上,通过FileZilla连接过程初步判断是证书和加密方式的问题,然后就自己建了一个不含ssl证书,明文传输的,默认端口21的ftp,连接并操作果然完全没问题。
下面是对无证书无加密的ftp测试:
public static void main(String[] args) throws Exception {
FTPClient ftpClient = new FTPClient();
//连接ftp
ftpClient.connect("hostname", "port");
//登陆ftp
ftpClient.login("username", "password");
//需要把文件上传到FTP哪个目录
ftpClient.changeWorkingDirectory("/home/ftptest/test");
ftpClient.deleteFile("/home/ftptest/test");
//需要上传的文件
File file = new File("E:\\a.txt");
//存储文件,成功返回true,失败false
System.out.println(ftpClient.storeFile(file.getName(), new FileInputStream(file)));
}
连接到目标服务器,首先是需要设置隐式传输,就要先在创建对象时就先设置好,这时FTPClient他没有设置为隐式的构造方式,但他的子类FTPSClient,在他的构造函数中有
/**
* Constructor for FTPSClient, using {@link #DEFAULT_PROTOCOL} - i.e. TLS
* The default TrustManager is set from {@link TrustManagerUtils#getValidateServerCertificateTrustManager()}
* @param isImplicit The security mode(Implicit/Explicit).
* @param context A pre-configured SSL Context
*/
public FTPSClient(boolean isImplicit, SSLContext context) {
this(DEFAULT_PROTOCOL, isImplicit);
this.context = context;
}
这个构造方法提供了证书和加密方式的设置,设为隐式传输只要isImplicit为true,而证书我是自定义一个证书,下面是证书初始化代码
//创建SSL上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
//自定义证书,忽略已过期证书
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;
//初始化
sslContext.init(null, trustAllCerts, null);
static class miTM implements TrustManager, X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
public void checkClientTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
}
这次的上传文件失败,是因为使用的还是父类的ftpClient.storeFile()的方法,需要将FTPClient ftpClient = new FTPClient();改为FTPSClient ftpClient = new FTPSClient();通过测试会发现,使用俩种不同的初始化方式,虽然使用上传文件方法名都相同,但在方法中实现的方式不同,这时会在发现第三个问题。
选用了子类上传文件的方法,会发现进程会卡死,通过测试发现卡在socket = server.accept();中,百度后也没解决,然后再看上传的方法问题,在测试中会发现在程序运行时会比对一些参数,所以这里我们还需要进行其他的一些配置,像主动连接参数配置问题,不加也会发生进程卡死,因为默认为主动,需要设置为被动模式,传输失败跟传输端口有关,具体需要添加的配置如下
//设为被动模式
ftpClient.enterLocalPassiveMode();
//设为CMD_PBSZ,是跟SSL证书的验证有关系
ftpClient.execPBSZ(0);
//设置PROT_COMMAND_VALUE(会进行参数匹配,"C","E","S","P"在不同范围内匹配不同方式)
ftpClient.execPROT("P");
历经千辛万苦,图片确实通过这种方式上传成功了,但是打开一看会发现图片失真了,这是因为和文本传输不同,传输图片需要对传输方式进行设置,设为二进制传输方式
//以二进制形式传输
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
以上就是这次遇到的一些问题,下面附完整代码
private OSS ossClient;
private FTPSClient ftpClient;
@Override
public void FtpSyncFile(Long id) {
List<MeetingTaskWechatPhoto> meetingTaskWechatPhotos = meetingTaskWechatPhotoRepository.findByMeetingTaskIdAndDeletedFalse(id);
String meetingNo = meetingTaskRepository.findById(id).get().getMeetingNo();
try {
// 创建OSSClient实例。
ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);
// ossObject包含文件所在的存储空间名称、文件名称、文件元信息以及一个输入流。
OSSObject ossObject = ossClient.getObject(bucketName, objectName);
// 读取文件内容。
InputStream is = ossObject.getObjectContent();
//创建SSL上下文
SSLContext sslContext = SSLContext.getInstance("TLS");
//自定义证书,忽略已过期证书
TrustManager[] trustAllCerts = new TrustManager[1];
TrustManager tm = new miTM();
trustAllCerts[0] = tm;
//初始化
sslContext.init(null, trustAllCerts, null);
//创建客户端,加密选择Implicit
ftpClient = new FTPSClient(true, sslContext);
//连接ftp
ftpClient.connect(hostname, Integer.parseInt(port));
//登陆ftp
ftpClient.login(username, password);
//创建文件夹
ftpClient.makeDirectory("/" + meetingNo);
//需要把文件上传到FTP哪个目录
ftpClient.changeWorkingDirectory("/" + meetingNo);
ftpClient.enterLocalPassiveMode();
ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
ftpClient.execPBSZ(0);
ftpClient.execPROT("P");
//上传图片
for (MeetingTaskWechatPhoto meetingTaskWechatPhoto : meetingTaskWechatPhotos) {
String photoUrl = meetingTaskWechatPhoto.getPhoto_url();
String[] split = StringUtils.split(photoUrl, "/");
String pictureName = split[split.length - 1];
//存储文件,成功返回true,失败false
//以二进制形式传输
if (ftpClient.storeFile(pictureName, is)) {
log.info(pictureName + "上传成功");
} else {
log.info(pictureName + "上传失败");
}
}
//断开连接
ftpClient.disconnect();
// 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
is.close();
// 关闭OSSClient。
ossClient.shutdown();
} catch (Exception e) {
e.printStackTrace();
log.error("文件同步失败!");
}
}
static class miTM implements TrustManager, X509TrustManager {
public X509Certificate[] getAcceptedIssuers() {
return null;
}
public boolean isServerTrusted(X509Certificate[] certs) {
return true;
}
public boolean isClientTrusted(X509Certificate[] certs) {
return true;
}
public void checkServerTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
public void checkClientTrusted(X509Certificate[] certs, String authType)
throws CertificateException {
return;
}
}
——————————————————————————
参考文章:https://blog.csdn.net/slan2069586311/article/details/89633741