ftp操作类
FTPUtil.java
package com;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.commons.pool.ObjectPool;
import org.apache.commons.pool.PoolableObjectFactory;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.TimeUnit;
/**
* Author: lzy
* Date: 16/9/22
* <p/>
* 依赖以下Jar包:
* <dependency>
* <groupId>commons-net</groupId>
* <artifactId>commons-net</artifactId>
* <version>3.5</version>
* </dependency>
*/
public class FTPUtil {
private static Logger logger = LoggerFactory.getLogger(FTPUtil.class);
private static final int FTP_KEEP_ALIVE_TIMEOUT = 600;
private static final int FTP_BUFFER_SIZE = 1024;
private static final int FTP_DEFAULT_PORT = 21;
private static final String FTP_ANONYMOUS_NAME = "anonymous";
private static final String FTP_ANONYMOUS_PASSWORD = "[email protected]";
private static final String FTP_PREFIX = "ftp://";
private static final String FTP_HOST_SEPARATOR = "@";
private static final String FTP_PASSWORD_SEPARATOR = ":";
private static final String FTP_PORT_SEPARATOR = ":";
private static final String ENCODING_GBK = "GBK";
private static final String ENCODING_UTF8 = "UTF-8";
private static final String SERVER_CHARSET = "ISO-8859-1";
//公开变量
public static final String FTP_PATH_SEPARATOR = "/";
//本地使用的默认编码
private static String localCharset = ENCODING_UTF8;
//ftp连接池
private FTPClientPool ftpClientPool;
/**
* 初始化ftp连接池
*/
public FTPUtil(String ftpUrl) throws Exception {
FTPClientFactory ftpClientFactory = new FTPClientFactory(ftpUrl);
ftpClientPool = new FTPClientPool(ftpClientFactory);
}
/**
* 初始化ftp连接池,并制定ftp连接池大小
*/
public FTPUtil(String ftpUrl, int poolSize) throws Exception {
FTPClientFactory ftpClientFactory = new FTPClientFactory(ftpUrl);
ftpClientPool = new FTPClientPool(poolSize, ftpClientFactory);
}
/**
* 从连接池里取一个ftp实例
*
* @return ftp实例
* @throws Exception
*/
public FTPClient borrowFTPClient() throws Exception {
if (null == ftpClientPool) {
throw new NullPointerException("ftp连接池为null,未初始化");
}
return ftpClientPool.borrowObject();
}
/**
* 关闭ftp连接池
*/
public void closeFTPPool() {
if (null != ftpClientPool) {
ftpClientPool.close();
}
ftpClientPool = null;
}
/**
* 归还一个ftp实例到连接池中
*
* @param ftpClient ftp实例
* @throws Exception
*/
public void returnFTPClient(FTPClient ftpClient) throws Exception {
if (null == ftpClientPool) {
throw new NullPointerException("ftp连接池为null,未初始化");
}
ftpClientPool.returnObject(ftpClient);
}
/**
* 对远端的ftp路径进行编码,以支持中文
*
* @param input 原始的远端的ftp路径
* @return 编码后的ftp路径
*/
public static String encoding(String input) {
if (null == input) return null;
//转换路径分隔符
if (!FTP_PATH_SEPARATOR.equals(File.separator)) {
input = input.replaceAll(File.separatorChar == '\\' ? "\\\\" : File.separator, FTP_PATH_SEPARATOR);
}
try {
//已经是ISO-8859-1编码的不再改变
if (input.equals(new String(input.getBytes(SERVER_CHARSET), SERVER_CHARSET))) return input;
//进行转码
return new String(input.getBytes(localCharset), SERVER_CHARSET);
} catch (UnsupportedEncodingException e) {
return input;
}
}
/**
* 对编码后的远端路径进行解码,以用于本地存储
*
* @param input 编码后的ftp路径
* @return 解码后的ftp路径
*/
public static String decoding(String input) {
if (null == input) return null;
//转换路径分隔符
if (!FTP_PATH_SEPARATOR.equals(File.separator)) {
input = input.replaceAll(FTP_PATH_SEPARATOR, File.separatorChar == '\\' ? "\\\\" : File.separator);
}
try {
//不是ISO-8859-1编码的不再改变
if (!input.equals(new String(input.getBytes(SERVER_CHARSET), SERVER_CHARSET))) return input;
//进行转码
return new String(input.getBytes(SERVER_CHARSET), localCharset);
} catch (UnsupportedEncodingException e) {
return input;
}
}
/**
* 传入ftpUrl获取FTPClient
*
* @param ftpUrl ftp的Url,格式:ftp://user:pass@xx.xx.xx.xx:port
* 其中,ftp://前缀,用户名密码,端口号都不是必须的
* 若没有用户名密码则匿名登录
* 没有端口号则使用FTP默认端口号
* @return FTPClient
*/
@Deprecated
public static FTPClient getFTPClient(String ftpUrl) {
String username;
String password;
int port;
if (StringUtils.isBlank(ftpUrl)) {
logger.error("ftp的URL为空.");
return null;
}
//去掉ftp前缀
if (StringUtils.startsWithIgnoreCase(ftpUrl, FTP_PREFIX)) {
ftpUrl = ftpUrl.substring(FTP_PREFIX.length());
}
//去掉path
if (ftpUrl.contains(FTP_PATH_SEPARATOR)) {
ftpUrl = ftpUrl.substring(0, ftpUrl.indexOf(FTP_PATH_SEPARATOR));
}
//获取用户名密码
int hostIndex = ftpUrl.indexOf(FTP_HOST_SEPARATOR);
if (hostIndex >= 0) {
//ftpUrl中包含用户名密码,需要提取
int passIndex = ftpUrl.indexOf(FTP_PASSWORD_SEPARATOR);
if (passIndex > 0 && passIndex < hostIndex) {
String account = ftpUrl.substring(0, hostIndex);
ftpUrl = ftpUrl.substring(hostIndex + 1);
username = account.substring(0, passIndex);
password = account.substring(passIndex + 1);
} else {
logger.error("ftp的URL格式错误,未提取到登录的用户名和密码.");
return null;
}
} else {
//ftpUrl不包含用户名密码,使用匿名登录
username = FTP_ANONYMOUS_NAME;
password = FTP_ANONYMOUS_PASSWORD;
}
//获取端口
int portIndex = ftpUrl.indexOf(FTP_PORT_SEPARATOR);
if (portIndex >= 0) {
//ftpUrl中指定了端口号
port = Integer.parseInt(ftpUrl.substring(portIndex + 1));
} else {
//ftpUrl中未指定端口号,使用默认端口
port = FTP_DEFAULT_PORT;
}
boolean flag;
FTPClient ftpClient = new FTPClient();
try {
ftpClient.connect(ftpUrl, port);
flag = ftpClient.login(username, password);
} catch (IOException e) {
logger.error("建立FTP连接或者登录出现错误.", e);
return null;
}
if (flag) {
ftpClient.setControlKeepAliveTimeout(FTP_KEEP_ALIVE_TIMEOUT);
//尝试进入被动模式
ftpClient.enterLocalPassiveMode();
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用GBK.
try {
if (!FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
localCharset = ENCODING_GBK;
}
} catch (IOException e) {
localCharset = ENCODING_GBK;
}
return ftpClient;
} else {
logger.error("登录FTP失败.");
return null;
}
}
/**
* 判断ftp上指定路径是否存在
*
* @param ftpClient FTPClient
* @param remote 远端的ftp路径
* @return ftp上指定路径是否存在
*/
public static boolean exists(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) return false;
remote = encoding(remote);
try {
FTPFile[] ftpFiles = ftpClient.listFiles(remote);
if (ftpFiles.length > 0) {
return true;
} else {
//需要排除空目录的情况
try {
String curPath = ftpClient.printWorkingDirectory();
boolean rst = ftpClient.changeWorkingDirectory(remote); //不是目录时,将抛异常,或者结果为false
if (null != curPath) {
// 回到原来的目录
ftpClient.changeWorkingDirectory(curPath);
}
return rst;
} catch (Exception e) {
return false;
}
}
} catch (IOException e) {
return false;
}
}
/**
* 判断ftp上的指定路径是否是目录
*
* @param ftpClient FTPClient
* @param remote 远端的ftp路径
* @return ftp上的指定路径是否是目录
*/
public static boolean isDirecotory(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) return false;
remote = encoding(remote);
if (!exists(ftpClient, remote)) return false;
//尝试使用MLST命令,该命令在老的ftp上不支持
FTPFile ftpFile;
try {
ftpFile = ftpClient.mlistFile(remote);
} catch (IOException e) {
ftpFile = null;
}
if (null != ftpFile) {
//该FTP支持MLST命令
return ftpFile.isDirectory();
} else {
//该FTP不支持MLST命令,换用另一种兼容方式
try {
String curPath = ftpClient.printWorkingDirectory();
boolean rst = ftpClient.changeWorkingDirectory(remote); //不是目录时,将抛异常,或者结果为false
if (null != curPath) {
// 回到原来的目录
ftpClient.changeWorkingDirectory(curPath);
}
return rst;
} catch (Exception e) {
return false;
}
}
}
/**
* 判断ftp上的指定路径是否是文件
*
* @param ftpClient FTPClient
* @param remote 远端的ftp路径
* @return ftp上的指定路径是否是文件
*/
public static boolean isFile(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) return false;
remote = encoding(remote);
if (!exists(ftpClient, remote)) return false;
//尝试使用MLST命令,该命令在老的ftp上不支持
FTPFile ftpFile;
try {
ftpFile = ftpClient.mlistFile(remote);
} catch (IOException e) {
ftpFile = null;
}
if (null != ftpFile) {
//该FTP支持MLST命令
return ftpFile.isFile();
} else {
//该FTP不支持MLST命令,换用另一种兼容方式
try {
String curPath = ftpClient.printWorkingDirectory();
boolean rst = ftpClient.changeWorkingDirectory(remote); //不是目录时,将抛异常,或者结果为false
if (null != curPath) {
// 回到原来的目录
ftpClient.changeWorkingDirectory(curPath);
}
return !rst;
} catch (Exception e) {
return true;
}
}
}
/**
* 从FTP下载文件或目录,本地已经存在的同名文件会被覆盖;
* 实现功能和以下的本地复制命令一致(但local不存在时会自动创建):
* cp -rf remote local
*
* @param ftpClient FTPClient
* @param remote 远端的ftp文件或ftp目录
* @param local 本地的目录
* @return 是否成功下载
*/
public static boolean downloadFtpFiles(FTPClient ftpClient, String remote, String local) {
if (null == ftpClient || StringUtils.isBlank(remote) || StringUtils.isBlank(local)) {
logger.error("下载ftp文件函数的入参:FTPClient,远端的ftp文件路径,或者本地文件路径为空.");
return false;
}
//去除末尾的/符号
remote = trimEndSeparator(remote);
local = trimEndSeparator(local);
return _downloadFtpFiles(ftpClient, remote, local);
}
private static boolean _downloadFtpFiles(FTPClient ftpClient, String remote, String local) {
try {
String remoteEncoding = encoding(remote);
String remoteName = remoteEncoding.substring(remoteEncoding.lastIndexOf(FTP_PATH_SEPARATOR) + 1);
String localDecoding = local + File.separator + decoding(remoteName);
File localFile = new File(localDecoding);
boolean success = true;
if (isFile(ftpClient, remoteEncoding)) {
//待下载的为文件:直接下载
if (localFile.exists()) {
success = localFile.delete();
}
if (!localFile.getParentFile().exists()) {
boolean oneTry = localFile.getParentFile().mkdirs();
//多线程时尝试建立目录可能会失败,直接检查结果即可
success &= oneTry || localFile.getParentFile().exists();
}
success &= localFile.createNewFile();
if (!success) {
logger.error("尝试删除本地同名文件并建立新文件时出错.");
return false;
}
// 输出到文件
OutputStream fos = new FileOutputStream(localFile);
// 下载文件
ftpClient.retrieveFile(remoteEncoding, fos);
fos.close();
} else if (isDirecotory(ftpClient, remoteEncoding)) {
//待下载的为文件夹:递归下载目录内的子文件/目录
ftpClient.changeWorkingDirectory(remoteEncoding);
if (!localFile.exists()) {
success = localFile.mkdirs();
//多线程下可能会失败,所以要检查结果
if (!success && !localFile.exists()) {
logger.error("尝试建立新文件夹时出错.");
return false;
}
}
FTPFile[] files = ftpClient.listFiles();
for (FTPFile file : files) {
if (".".equals(file.getName()) || "..".equals(file.getName())) {
continue;
}
String remoteSonEncoding = remoteEncoding + FTP_PATH_SEPARATOR + encoding(file.getName());
boolean rst = _downloadFtpFiles(ftpClient, remoteSonEncoding, localDecoding);
if (!rst) return false;
}
} else {
logger.error("待下载的ftp文件的类型识别错误.");
return false;
}
return true;
} catch (Exception e) {
logger.error("执行下载ftp文件的过程中,出现异常.", e);
return false;
}
}
/**
* 上传本地的文件或目录到FTP,如果ftp已经存在同名文件则进行覆盖;
* 实现功能和以下的本地复制命令一致(但remote不存在时会自动创建):
* cp -rf local remote
*
* @param ftpClient FTPClient
* @param local 本地的文件或目录
* @param remote 远端的ftp目录
* @return 是否成功上传
*/
public static boolean uploadFtpFiles(FTPClient ftpClient, String local, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote) || StringUtils.isBlank(local)) {
logger.error("上传ftp文件函数的入参:FTPClient,远端的ftp文件路径,或者本地文件路径为空.");
return false;
}
//去除末尾的/符号
remote = trimEndSeparator(remote);
return _uploadFtpFiles(ftpClient, local, remote);
}
private static boolean _uploadFtpFiles(FTPClient ftpClient, String local, String remote) {
try {
File localFile = new File(local);
String remoteEncoding = encoding(remote);
String fileNameEncoding = encoding(localFile.getName());
String remoteSonEncoding = remoteEncoding + FTP_PATH_SEPARATOR + fileNameEncoding;
boolean success;
if (localFile.isFile()) {
//待上传的为文件:直接上传
success = FTPUtil.mkdirs(ftpClient, remoteEncoding);
//多线程下可能创建目录失败,故需要直接检查结果
if(!success && !FTPUtil.exists(ftpClient, remoteEncoding)){
logger.error("创建ftp上所需父目录:{}时失败.",remoteEncoding);
return false;
}
FileInputStream fis = new FileInputStream(localFile);
ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
ftpClient.changeWorkingDirectory(remoteEncoding);
ftpClient.storeFile(fileNameEncoding, fis);
fis.close();
} else if (localFile.isDirectory()) {
//待上传的为文件夹:先创建目录,然后递归上传目录内的子文件/目录
success = FTPUtil.mkdirs(ftpClient, remoteSonEncoding);
//多线程下可能创建目录失败,故需要直接检查结果
if(!success && !FTPUtil.exists(ftpClient, remoteSonEncoding)){
logger.error("创建ftp上所需目录:{}时失败.", remoteSonEncoding);
return false;
}
File[] files = localFile.listFiles();
if (null == files) {
logger.error("解析本地文件时出错.");
return false;
}
for (File f : files) {
boolean rst = _uploadFtpFiles(ftpClient, f.getCanonicalPath(), remoteSonEncoding);
if (!rst) return false;
}
} else {
logger.error("待上传的文件的类型识别错误.");
return false;
}
return true;
} catch (Exception e) {
logger.error("执行上传ftp文件的过程中,出现异常.", e);
return false;
}
}
/**
* 递归创建ftp上的目录,如果已经存在则不创建
*
* @param ftpClient FTPClient
* @param remote 远端的ftp目录
* @return 是否创建目录成功
*/
public static boolean mkdirs(FTPClient ftpClient, String remote) {
try {
if (null == ftpClient || StringUtils.isBlank(remote)) {
logger.error("创建ftp文件函数的入参:FTPClient或者远端的ftp文件路径为空.");
return false;
}
String remoteEncoding = encoding(remote);
if (exists(ftpClient, remoteEncoding)) {
return true;
}
boolean success;
StringBuilder sb = new StringBuilder();
String[] nodes = remoteEncoding.split(FTP_PATH_SEPARATOR);
for (String node : nodes) {
if (null == node || "".equals(node)) continue;
sb.append(FTP_PATH_SEPARATOR).append(node);
String remoteParentEncoding = encoding(sb.toString());
if (!exists(ftpClient, remoteParentEncoding)) {
success = ftpClient.makeDirectory(remoteParentEncoding);
//多线程情况下可能会失败,所以需要直接检查结果
if (!success && !exists(ftpClient, remoteParentEncoding)) {
logger.error("创建目录:{}时失败.", sb.toString());
return false;
}
}
}
return true;
} catch (Exception e) {
logger.error("递归创建ftp目录的过程中,出现异常.", e);
return false;
}
}
/**
* 递归删除ftp上的文件或目录
*
* @param ftpClient FTPClient
* @param remote 远端的ftp文件或ftp目录
* @return 是否删除成功
*/
public static boolean removeFiles(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) {
logger.error("删除ftp文件函数的入参:FTPClient或者远端的ftp文件路径为空.");
return false;
}
//去除末尾的/符号
remote = trimEndSeparator(remote);
if (!exists(ftpClient, remote)) {
logger.error("待删除的文件不存在,无法删除.");
return false;
}
return _removeFiles(ftpClient, remote);
}
private static boolean _removeFiles(FTPClient ftpClient, String remote) {
try {
String remoteEncoding = encoding(remote);
ftpClient.changeWorkingDirectory(remoteEncoding);
boolean success;
if (isFile(ftpClient, remoteEncoding)) {
//待删除的为文件:直接删除
success = ftpClient.deleteFile(remoteEncoding);
} else if (isDirecotory(ftpClient, remoteEncoding)) {
//待删除的为文件夹:先递归删除目录内的子文件/目录,然后删除本目录
FTPFile[] files = ftpClient.listFiles();
for (FTPFile file : files) {
if (".".equals(file.getName()) || "..".equals(file.getName())) {
continue;
}
String remoteSonEncoding = remoteEncoding + FTP_PATH_SEPARATOR + encoding(file.getName());
boolean rst = _removeFiles(ftpClient, remoteSonEncoding);
if (!rst) return false;
}
success = ftpClient.removeDirectory(remoteEncoding);
} else {
logger.error("待删除的ftp文件的类型识别错误.");
return false;
}
return success;
} catch (Exception e) {
logger.error("执行删除ftp文件的过程中,出现异常.", e);
return false;
}
}
/**
* 获取给出的远端的ftp路径的最底层的文件夹集合的路径,以方便按照最底层文件夹级别进行多线程下载
*
* @param ftpClient FTPClient
* @param remote 远端的ftp路径
* @return 最底层的文件夹路径的集合
*/
public static List<String> getBottomDirs(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) {
logger.error("获取ftp底层文件夹路径的函数的入参:FTPClient或者远端的ftp文件路径为空.");
return new ArrayList<String>();
}
//去除末尾的/符号
remote = trimEndSeparator(remote);
return _getBottomDirs(ftpClient, remote);
}
private static List<String> _getBottomDirs(FTPClient ftpClient, String remote) {
List<String> result = new ArrayList<String>();
try {
String remoteEncoding = encoding(remote);
if (!FTPUtil.isDirecotory(ftpClient, remoteEncoding)) return result;
ftpClient.changeWorkingDirectory(remoteEncoding);
FTPFile[] files = ftpClient.listFiles();
for (FTPFile file : files) {
if (".".equals(file.getName()) || "..".equals(file.getName())) {
continue;
}
String remoteSonEncoding = remoteEncoding + FTP_PATH_SEPARATOR + encoding(file.getName());
List<String> sonRst = _getBottomDirs(ftpClient, remoteSonEncoding);
if (!sonRst.isEmpty()) {
//合并子文件夹结果
for (String rst : sonRst) {
result.add(rst);
}
}
}
if (result.isEmpty()) {
//本身就是最底层文件夹
result.add(remoteEncoding);
}
return result;
} catch (Exception e) {
logger.error("获取ftp底层文件夹路径的集合的过程中,出现异常.");
return new ArrayList<String>();
}
}
/**
* 获取给出的远端的ftp路径的最底层的文件和文件夹集合的路径,以方便按照最底层文件和文件夹级别进行多线程下载
*
* @param ftpClient FTPClient
* @param remote 远端的ftp路径
* @return 最底层的文件和文件夹路径的集合
*/
public static List<String> getBottomFiles(FTPClient ftpClient, String remote) {
if (null == ftpClient || StringUtils.isBlank(remote)) {
logger.error("获取ftp底层文件和文件夹路径的函数的入参:FTPClient或者远端的ftp文件路径为空.");
return new ArrayList<String>();
}
if (!FTPUtil.exists(ftpClient, remote)) {
logger.error("ftp文件路径不存在.");
return new ArrayList<String>();
}
//去除末尾的/符号
remote = trimEndSeparator(remote);
return _getBottomFiles(ftpClient, remote);
}
private static List<String> _getBottomFiles(FTPClient ftpClient, String remote) {
List<String> result = new ArrayList<String>();
try {
String remoteEncoding = encoding(remote);
if (FTPUtil.isFile(ftpClient, remoteEncoding)) {
//已经是最底层的文件
result.add(remoteEncoding);
return result;
}
ftpClient.changeWorkingDirectory(remoteEncoding);
FTPFile[] files = ftpClient.listFiles();
for (FTPFile file : files) {
if (".".equals(file.getName()) || "..".equals(file.getName())) {
continue;
}
String remoteSonEncoding = remoteEncoding + FTP_PATH_SEPARATOR + encoding(file.getName());
List<String> sonRst = _getBottomFiles(ftpClient, remoteSonEncoding);
if (!sonRst.isEmpty()) {
//合并子文件夹结果
for (String rst : sonRst) {
result.add(rst);
}
}
}
if (result.isEmpty()) {
//本身就是最底层文件夹(该文件夹下无子文件或子文件夹)
result.add(remoteEncoding);
}
return result;
} catch (Exception e) {
logger.error("获取ftp底层文件和文件夹路径的集合的过程中,出现异常.");
return new ArrayList<String>();
}
}
private static String trimEndSeparator(String oriPath) {
if (null == oriPath) return null;
while ((oriPath.endsWith(File.separator) || oriPath.endsWith(FTP_PATH_SEPARATOR)) && oriPath.length() > 1) {
oriPath = oriPath.substring(0, oriPath.length() - 1);
}
return oriPath;
}
public static void main(String[] args) {
}
/**
* 自定义实现ftp连接池
*/
private class FTPClientPool implements ObjectPool<FTPClient> {
private static final int DEFAULT_POOL_SIZE = 10;
private static final int WAIT_TIME_OUT = 1;
private String error;
private BlockingQueue<FTPClient> pool;
private FTPClientFactory factory;
public FTPClientPool(FTPClientFactory factory) throws Exception {
this(DEFAULT_POOL_SIZE, factory);
}
/**
* 创建并初始化ftp连接池
*
* @param poolSize 连接池大小
* @param factory factory实例
* @throws Exception 因为创建并添加ftp实例失败,导致初始化失败
*/
public FTPClientPool(int poolSize, FTPClientFactory factory) throws Exception {
this.factory = factory;
this.pool = new ArrayBlockingQueue<FTPClient>(poolSize);
//初始化时预分配所有的ftp实例
for (int i = 0; i < poolSize; i++) {
addOneFtp(); //可能导致异常
}
}
/**
* 为ftp连接池创建并添加一个新的ftp实例
*
* @throws Exception 创建ftp实例失败
*/
private void addOneFtp() throws Exception {
//使用阻塞等待的方式添加,是因为BlockingQueue的读和写共用一个锁;写有可能被读所阻塞
pool.offer(factory.makeObject(), WAIT_TIME_OUT, TimeUnit.SECONDS);
}
/**
* 获取(占用)ftp连接池的一个实例
*
* @return ftp实例
* @throws Exception 获取的ftp实例无效,尝试重新创建时失败
*/
@Override
public FTPClient borrowObject() throws Exception {
FTPClient client;
client = pool.take();
if (client == null) {
client = factory.makeObject();
addOneFtp(); //创建失败时抛出异常
} else if (!factory.validateObject(client)) {
invalidateObject(client);
client = factory.makeObject();
addOneFtp(); //创建失败时抛出异常
}
return client;
}
/**
* 归还(释放)ftp连接池的一个实例
*
* @param client ftp实例
* @throws Exception 归还失败时,尝试重新创建一个ftp实例补充到连接池里却失败
*/
@Override
public void returnObject(FTPClient client) throws Exception {
if (null == client) {
throw new NullPointerException("FTPClient为null.");
}
try {
if (!pool.offer(client, WAIT_TIME_OUT, TimeUnit.SECONDS)) {
//因为等待超时,导致将ftp实例放回连接池不成功
error = "因为等待超时,导致将ftp实例放回连接池不成功";
logger.error(error);
factory.destroyObject(client);
addOneFtp(); //添加不成功会抛出异常
}
} catch (InterruptedException e) {
//因为被人为中断,导致将ftp实例放回连接池不成功
error = "因为被人为中断,导致将ftp实例放回连接池不成功";
logger.error(error, e);
factory.destroyObject(client);
addOneFtp(); //添加不成功会抛出异常
}
}
/**
* 移除无效的对象(FTP客户端)
*
* @param client ftp实例
*/
@Override
public void invalidateObject(FTPClient client) {
if (null != client) {
pool.remove(client);
}
}
/**
* 关闭连接池并释放资源
*/
@Override
public void close() {
while (pool.iterator().hasNext()) {
FTPClient client;
try {
client = pool.take();
} catch (InterruptedException e) {
logger.error("从线程池中获取ftp实例时被中断.", e);
continue;
}
factory.destroyObject(client);
}
}
/**
* 添加一个ftp实例(不支持)
*
* @throws UnsupportedOperationException
*/
@Override
public void addObject() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* 获取空闲链接数(不支持)
*
* @return 空闲链接数
* @throws UnsupportedOperationException
*/
@Override
public int getNumIdle() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* 获取正在被使用的链接数(不支持)
*
* @return 正在被使用的链接数
* @throws UnsupportedOperationException
*/
@Override
public int getNumActive() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* 清除空闲ftp实例并释放资源.(不支持该方法)
*
* @throws UnsupportedOperationException
*/
@Override
public void clear() throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
/**
* 替换factory实例(不支持该方法)
*
* @param poolableObjectFactory factory实例
* @throws UnsupportedOperationException
*/
@Override
public void setFactory(PoolableObjectFactory<FTPClient> poolableObjectFactory) throws UnsupportedOperationException {
throw new UnsupportedOperationException();
}
}
/**
* 连接池工厂类
*/
private class FTPClientFactory implements PoolableObjectFactory<FTPClient> {
private String _ftpUrl;
/**
* 使用ftp的url,初始化factory类
*
* @param ftpUrl ftp的Url,格式:ftp://user:pass@xx.xx.xx.xx:port
* 其中,ftp://前缀,用户名密码,端口号都不是必须的
* 若没有用户名密码则匿名登录
* 没有端口号则使用FTP默认端口号
*/
public FTPClientFactory(String ftpUrl) {
this._ftpUrl = ftpUrl;
}
/**
* 获取一个有效的ftp实例
*
* @return 一个有效的ftp实例
* @throws Exception 无法创建ftp实例
*/
@Override
public FTPClient makeObject() throws Exception {
String username;
String password;
int port;
String error;
//对内部的ftpUrl复制后再操作
String ftpUrl = this._ftpUrl;
if (StringUtils.isBlank(ftpUrl)) {
error = "ftp的URL为空.";
logger.error(error);
throw new NullPointerException(error);
}
//去掉ftp前缀
if (StringUtils.startsWithIgnoreCase(ftpUrl, FTP_PREFIX)) {
ftpUrl = ftpUrl.substring(FTP_PREFIX.length());
}
//去掉path
if (ftpUrl.contains(FTP_PATH_SEPARATOR)) {
ftpUrl = ftpUrl.substring(0, ftpUrl.indexOf(FTP_PATH_SEPARATOR));
}
//获取用户名密码
int hostIndex = ftpUrl.indexOf(FTP_HOST_SEPARATOR);
if (hostIndex >= 0) {
//ftpUrl中包含用户名密码,需要提取
int passIndex = ftpUrl.indexOf(FTP_PASSWORD_SEPARATOR);
if (passIndex > 0 && passIndex < hostIndex) {
String account = ftpUrl.substring(0, hostIndex);
ftpUrl = ftpUrl.substring(hostIndex + 1);
username = account.substring(0, passIndex);
password = account.substring(passIndex + 1);
} else {
error = "ftp的URL格式错误,未提取到登录的用户名和密码.";
logger.error(error);
throw new IllegalArgumentException(error);
}
} else {
//ftpUrl不包含用户名密码,使用匿名登录
username = FTP_ANONYMOUS_NAME;
password = FTP_ANONYMOUS_PASSWORD;
}
//获取端口
int portIndex = ftpUrl.indexOf(FTP_PORT_SEPARATOR);
if (portIndex >= 0) {
//ftpUrl中指定了端口号
port = Integer.parseInt(ftpUrl.substring(portIndex + 1));
} else {
//ftpUrl中未指定端口号,使用默认端口
port = FTP_DEFAULT_PORT;
}
boolean flag;
FTPClient ftpClient = new FTPClient();
try {
ftpClient.connect(ftpUrl, port);
flag = ftpClient.login(username, password);
} catch (IOException e) {
error = "建立FTP连接或者登录出现错误.";
logger.error(error, e);
throw e;
}
if (flag) {
ftpClient.setControlKeepAliveTimeout(FTP_KEEP_ALIVE_TIMEOUT);
ftpClient.setBufferSize(FTP_BUFFER_SIZE);
//尝试进入被动模式
ftpClient.enterLocalPassiveMode();
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用GBK.
try {
if (!FTPReply.isPositiveCompletion(ftpClient.sendCommand("OPTS UTF8", "ON"))) {
localCharset = ENCODING_GBK;
}
} catch (IOException e) {
localCharset = ENCODING_GBK;
}
return ftpClient;
} else {
error = "登录FTP失败.";
logger.error(error);
throw new RuntimeException(error);
}
}
/**
* 尝试关闭ftp实例
*
* @param ftpClient ftp实例
*/
@Override
public void destroyObject(FTPClient ftpClient) {
try {
if (ftpClient != null && ftpClient.isConnected()) {
ftpClient.logout();
}
} catch (Exception e) {
logger.error("ftp client logout failed...", e);
} finally {
if (ftpClient != null) {
try {
ftpClient.disconnect();
} catch (IOException e) {
logger.error("ftp client disconnect failed...", e);
}
}
}
}
/**
* 验证ftp实例是否可用
*
* @param ftpClient ftp实例
* @return 是否可用
*/
@Override
public boolean validateObject(FTPClient ftpClient) {
try {
if (null != ftpClient && ftpClient.isConnected()) {
return ftpClient.sendNoOp();
}
} catch (Exception e) {
logger.error("Failed to validate client: {}", e);
}
return false;
}
/**
* 激活一个实例(ftp连接池不支持该方法)
*
* @param obj ftp实例
* @throws Exception
*/
@Override
public void activateObject(FTPClient obj) throws Exception {
//Do nothing
throw new UnsupportedOperationException();
}
/**
* 反激活一个实例(ftp连接池不支持该方法)
*
* @param obj ftp实例
* @throws Exception
*/
@Override
public void passivateObject(FTPClient obj) throws Exception {
//Do nothing
throw new UnsupportedOperationException();
}
}
}
文件操作
FileUtil.java
package com;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.io.File;
import java.util.ArrayList;
import java.util.List;
/**
* Created by zhy.li on 16/9/22.
*/
public class FileUtil {
private static Logger logger = LoggerFactory.getLogger(FileUtil.class);
private FileUtil() {
}
/**
* 如果文件或目录存在,则删除它们
*
* @param file 文件句柄
* @return 是否删除成功
*/
public static boolean removeFiles(File file) {
try {
if (null == file) {
logger.error("参数错误,待删除的文件句柄为null.");
return false;
}
if (!file.exists()) {
logger.info("文件不存在,无法删除.");
return false;
}
boolean success;
if (file.isFile()) {
//待删除的为文件:直接删除
success = file.delete();
} else if (file.isDirectory()) {
//待删除的为文件夹:先递归删除目录内的子文件/目录,然后删除本目录
File[] files = file.listFiles();
if (null == files) {
logger.error("在获取删除目录内的子文件/目录信息时出现错误.");
return false;
}
for (File sonFile : files) {
if (".".equals(sonFile.getName()) || "..".equals(sonFile.getName())) {
continue;
}
boolean rst = removeFiles(sonFile);
if (!rst) return false;
}
success = file.delete();
} else {
logger.error("待删除文件的类型识别错误.");
return false;
}
return success;
} catch (Exception e) {
logger.error("执行删除文件的过程中,出现异常.", e);
return false;
}
}
/**
* 获取给出的路径的最底层的文件夹路径的集合,以方便按照最底层文件夹级别进行多线程处理
*
* @param file 文件夹路径
* @return 最底层的文件夹路径的集合
*/
public static List<String> getBottomDirs(File file) {
List<String> result = new ArrayList<String>();
if (null == file) {
logger.error("参数错误,文件句柄为null.");
return result;
}
try {
if (!file.isDirectory()) return result;
File[] files = file.listFiles();
if (null == files) return result;
for (File sonFile : files) {
if (".".equals(sonFile.getName()) || "..".equals(sonFile.getName())) {
continue;
}
List<String> sonRst = getBottomDirs(sonFile);
if (!sonRst.isEmpty()) {
//合并子文件夹结果
for (String rst : sonRst) {
result.add(rst);
}
}
}
if (result.isEmpty()) {
//本身就是最底层文件夹
result.add(file.getCanonicalPath());
}
return result;
} catch (Exception e) {
logger.error("获取最底层文件夹路径的集合的过程中,出现异常.");
return new ArrayList<String>();
}
}
/**
* 获取给出的路径的最底层的文件和文件夹路径的集合,以方便按照最底层文件和文件夹级别进行多线程处理
*
* @param file 文件夹路径
* @return 最底层的文件和文件夹路径的集合
*/
public static List<String> getBottomFiles(File file) {
List<String> result = new ArrayList<String>();
if (null == file) {
logger.error("参数错误,文件句柄为null.");
return result;
}
try {
if (!file.exists()) return result;
if (file.isFile()) {
//已经是最底层文件
result.add(file.getCanonicalPath());
return result;
}
File[] files = file.listFiles();
if (null == files) return result;
for (File sonFile : files) {
if (".".equals(sonFile.getName()) || "..".equals(sonFile.getName())) {
continue;
}
List<String> sonRst = getBottomFiles(sonFile);
if (!sonRst.isEmpty()) {
//合并子文件夹结果
for (String rst : sonRst) {
result.add(rst);
}
}
}
if (result.isEmpty()) {
//本身就是最底层文件夹(该文件夹已经没有子文件和子文件夹)
result.add(file.getCanonicalPath());
}
return result;
} catch (Exception e) {
logger.error("获取最底层文件和文件夹路径的集合的过程中,出现异常.");
return new ArrayList<String>();
}
}
/**
* 获取输入的文件和文件夹集合的上一层目录,注意:不验证输入的路径是否存在
*
* @param lowerFiles 某一层的文件和文件夹路径集合
* @return 上一层目录路径的集合
*/
public static List<String> getUpperDirs(List<String> lowerFiles, String separator) {
List<String> result = new ArrayList<String>();
if (null == lowerFiles || lowerFiles.isEmpty()) return result;
for (String lowerFile : lowerFiles) {
//去除末尾的/符号
lowerFile = trimEndSeparator(lowerFile);
String upperDir = lowerFile.substring(0, lowerFile.lastIndexOf(separator));
if (!result.contains(upperDir) && !"".equals(upperDir)) {
result.add(upperDir);
}
}
return result;
}
private static String trimEndSeparator(String oriPath) {
if (null == oriPath) return null;
while ((oriPath.endsWith(File.separator) || oriPath.endsWith(FTPUtil.FTP_PATH_SEPARATOR)) && oriPath.length() > 1) {
oriPath = oriPath.substring(0, oriPath.length() - 1);
}
return oriPath;
}
public static void main(String[] args) {
}
}
Json操作
jsonUtil.java
package com;
import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.JSONArray;
import com.alibaba.fastjson.JSONObject;
import com.alibaba.fastjson.serializer.SerializeConfig;
import com.alibaba.fastjson.serializer.SerializerFeature;
import com.google.common.collect.Sets;
import com.qunar.base.qunit.ex.constants.IgnoreDate;
import com.qunar.base.qunit.fastjson.QunitDoubleSerializer;
import com.qunar.base.qunit.response.Response;
import com.qunar.base.qunit.util.PropertyUtils;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.util.*;
public class JsonUtil {
private static Logger logger = LoggerFactory.getLogger(JsonUtil.class);
private static final String KEY_PREFIX_SEPERATOR = ".";
private static final String AT_MAGIC_FLAG = "__at_magic_flag_411411__";
// Json to Map
@Deprecated
private static List<Map<String, Object>> json2List(Object json) {
JSONArray jsonArr = (JSONArray) json;
List<Map<String, Object>> arrList = new ArrayList<Map<String, Object>>();
for (int i = 0; i < jsonArr.size(); ++i) {
arrList.add(strJson2Map(jsonArr.getString(i)));
}
return arrList;
}
@Deprecated
public static Map<String, Object> strJson2Map(String json) {
JSONObject jsonObject = JSONObject.parseObject(json);
Map<String, Object> resMap = new HashMap<String, Object>();
Iterator<Map.Entry<String, Object>> it = jsonObject.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Object> param = (Map.Entry<String, Object>) it.next();
if (param.getValue() instanceof JSONObject) {
resMap.put(param.getKey(), strJson2Map(param.getValue().toString()));
} else if (param.getValue() instanceof JSONArray) {
resMap.put(param.getKey(), json2List(param.getValue()));
} else {
resMap.put(param.getKey(), JSONObject.toJSONString(param.getValue(), SerializerFeature.WriteClassName));
}
}
return resMap;
}
public static Map<String, Object> json2Map(String json) {
logger.debug("开始将json串转换为Map.");
Map<String, Object> rst = new HashMap<String, Object>();
Object source;
if (StringUtils.isBlank(json)) {
logger.debug("json串为空串,返回空结果");
return rst;
}
//预处理,防止@形式的键值被转换为对象,在转换为map时再替换回来
json = json.replaceAll("@", AT_MAGIC_FLAG);
try {
source = JSON.parse(json);
} catch (Exception e) {
//json不是Json字符串
logger.debug("解析成json格式失败,返回null.待解析的Json内容为:{}", json);
return null;
}
logger.debug("开始循环递归获取基本值的key前缀Map.");
getKeyPrefixMap(null, rst, source);
logger.debug("json串已经转换为map.");
return rst;
}
private static void getKeyPrefixMap(String keyPrefix, Map<String, Object> result, Object object) {
if (null == result) {
String error = "递归获取入key前缀Map时,传入的保存结果的Map为null.";
logger.error(error);
throw new NullPointerException(error);
}
if (object instanceof JSONObject) {
//object为Json串,循环调用该Json串的所有键值
JSONObject jsonObject = (JSONObject) object;
String nextKeyPrefix = keyPrefix == null ? "" : keyPrefix + KEY_PREFIX_SEPERATOR;
for (String key : jsonObject.keySet()) {
getKeyPrefixMap(nextKeyPrefix + key, result, jsonObject.get(key));
}
} else if (object instanceof JSONArray) {
//object为Json数组,循环调用该Json串的所有
JSONArray jsonArray = (JSONArray) object;
String nextKeyPrefix = keyPrefix == null ? "" : keyPrefix + KEY_PREFIX_SEPERATOR;
for (int i = 0; i < jsonArray.size(); i++) {
getKeyPrefixMap(nextKeyPrefix + "[" + i + "]", result, jsonArray.get(i));
}
} else {
//object为基本值,得到一个结果;首先进行@键值的还原
if (object instanceof String) {
object = ((String) object).replaceAll(AT_MAGIC_FLAG, "@");
}
result.put(keyPrefix.replaceAll(AT_MAGIC_FLAG, "@"), object);
}
}
public static Set<String> diffMap(Map<String, Object> source, Map<String, Object> target, String dateIgnore) {
Set<String> result = Sets.newHashSet();
//IgnoreDate type = IgnoreDate.getIgnoreType(ignoreType);
DateProcessor dateProcessor = new DateProcessor();
// todo: fixme:该变量可以下放到对类型type=VALUE处理时
//List<String> datePattern = dateProcessor.getDatePattern(ignoreType);
for (String key : source.keySet()) {
if (target.containsKey(key)) {
//target存在这个key,进行比较值
Object s = source.get(key);
Object t = target.get(key);
// 注意:生成ignore和做全量assert均使用该函数,此处目的为将需要日期忽略的key加入忽略列表.
// 会造成在assert时被直接认为是不同的结果,之所以现在不会出问题,是因为assert比较前已经剔除了忽略的key
if (t != null && !IgnoreDate.NULL.equals(IgnoreDate.getIgnoreType(dateIgnore))) {
if (dateProcessor.isDate(dateIgnore, t.toString())) {
result.add(key);
continue;
}
} /*else if (t != null && type.equals(IgnoreDate.VALUE)) {
for (String pattern : datePattern) {
if (dateProcessor.fuzzyMatch(key, pattern) || dateProcessor.isDate(ignoreType,
t.toString())) {
result.add(key);
continue;
}
}
}*/
if (!isObjectEqual(s, t)) {
result.add(key);
}
} else {
//target不存在这个key
result.add(key);
}
}
for (String key : target.keySet()) {
if (!source.containsKey(key)) {
//else: 会在上面的循环中进行处理
result.add(key);
}
}
return result;
}
public static Set<String> diffMapWithDisorderArray(Map<String, Object> source, Map<String, Object> target, String dateIgnore) {
Set<String> result = Sets.newHashSet();
Set<String> equalRst = Sets.newHashSet(); //空间换时间,记录相等的结果用于全量比较时提效
//IgnoreDate type = IgnoreDate.getIgnoreType(ignoreType);
DateProcessor dateProcessor = new DateProcessor();
// todo: fixme:该变量可以下放到对类型type=VALUE处理时
//List<String> datePattern = dateProcessor.getDatePattern(ignoreType);
for (String key : source.keySet()) {
if (target.containsKey(key)) {
//target存在这个key,进行比较值
Object s = source.get(key);
Object t = target.get(key);
// 注意:生成ignore和做全量assert均使用该函数,此处目的为将需要日期忽略的key加入忽略列表.
// 会造成在assert时被直接认为是不同的结果,之所以现在不会出问题,是因为assert比较前已经剔除了忽略的key
if (t != null && !IgnoreDate.NULL.equals(IgnoreDate.getIgnoreType(dateIgnore))) {
if (dateProcessor.isDate(dateIgnore, t.toString())) {
result.add(key);
continue;
}
} /*else if (t != null && type.equals(IgnoreDate.VALUE)) {
for (String pattern : datePattern) {
if (dateProcessor.fuzzyMatch(key, pattern) || dateProcessor.isDate(ignoreType,
t.toString())) {
result.add(key);
continue;
}
}
}*/
// 数组无序比较之前先同位置比较,在数组基本有序时可以提高效率
if (isObjectEqual(s, t)) {
equalRst.add(key);
continue;
}
// 进行数组无序比较
if (!disorderArrayAssert("", key, source.get(key), target)) {
result.add(key);
} else {
equalRst.add(key);
}
} else {
//target不存在这个key
result.add(key);
}
}
// 全量比较,现在只需要找出在target中有,但在source中不存在的那些key
for (String key : target.keySet()) {
if (!source.containsKey(key) && !equalRst.contains(key)) {
result.add(key);
}
}
return result;
}
private static boolean isObjectEqual(Object s, Object t) {
if (null == s && null == t) {
//2个key都为null
return true;
}
if ((null == s) || (null == t)) {
//只有1个key为null,另1个不为null
return false;
}
//因为sonar检查而注释
// if (s == t) {
// //是同一个对象
// continue;
// }
if (s.equals(t)) {
//两个对象"相等"
return true;
}
if (s.getClass() == t.getClass() && s.toString().equals(t.toString())) {
//两个对象的类型相同,且转换为字符串后比较相同
return true;
}
//非以上情况,视为key的类型不同或者值不同
return false;
}
private static boolean disorderArrayAssert(String keyPrefix, String expKey, Object expValue, Map<String, Object> resultAct) {
int arrayBegin = expKey.indexOf("[");
if (arrayBegin < 0) {
//递归出口:不含有数组, 直接进行比较即可
String fullKey = (StringUtils.isBlank(keyPrefix) ? "" : keyPrefix) + expKey;
if (resultAct.containsKey(fullKey)) {
return isObjectEqual(expValue, resultAct.get(fullKey));
} else {
return false;
}
} else {
// 将最上层的数组循环进行处理, 递归调用求解
int arrayEnd = expKey.indexOf("]");
if (arrayEnd <= arrayBegin) {
logger.error("Error: 字符串格式解析错误,未找到匹配的数组下标,字符串为: {}", expKey);
return false;
}
String arrayPrefix = (StringUtils.isBlank(keyPrefix) ? "" : keyPrefix) + expKey.substring(0, arrayBegin + 1);
String arrayPostfix = expKey.substring(arrayEnd, expKey.length());
int i = 0;
while (true) {
String arrayFull = arrayPrefix + i + arrayPostfix;
if (resultAct.containsKey(arrayFull)) {
//期望中存在对应的数组原因, 递归调用进行比较
if (disorderArrayAssert(arrayPrefix + i + "]", arrayPostfix.substring(1, arrayPostfix.length()), expValue, resultAct)) {
return true;
}
} else {
// 数组越界
return false;
}
i += 1;
}
}
}
public static String response2Json(Response response) {
if (null == response) return null;
//处理body非json的情况
Object body = response.getBody();
Object bodyJson;
try {
if (body instanceof String) {
//字符串被JSON.toJSON()设定为基本类型,不再处理而是直接返回
bodyJson = JSON.parse((String) body);
response.setBody(bodyJson);
} else if (!(body instanceof JSON)) {
bodyJson = JSON.toJSON(body);
response.setBody(bodyJson);
}
} catch (Exception e) {
logger.error("尝试将Response的body转变为Json格式时出错,将直接保持body为原格式不变");
}
Boolean jsonWriteOriginalDoubleValue = Boolean.valueOf(PropertyUtils.getProperty("json_write_original_double_value", "false"));
SerializeConfig config = new SerializeConfig();
if (jsonWriteOriginalDoubleValue) {
config.setAsmEnable(false);
config.put(Double.class, QunitDoubleSerializer.INSTANCE);
}
return JSON.toJSONString(response, config, SerializerFeature.WriteMapNullValue);
}
public static void main(String[] args) {
//{"key1": "1111", "key2" : {"key21": 2121, "key22":[221, 222, "223"]}}
}
}
基于DBUnit的数据表比较
/**
* 比较两个数据表,得到不同的数据表或字段
* @param sourceTable 待比较的数据表
* @param targetTable 待比较的数据表
* @return 2个数据表不同之处,格式:1)table1(col1,col2); 表示:table1的col1和col2字段不同; 2)table1; 表示:table1的整个数据表都不同
*/
public static String generateTableDiff(ITable sourceTable, ITable targetTable) {
logger.info("开始进行数据表的比较");
if ((null == sourceTable || null == sourceTable.getTableMetaData()) && (null == targetTable || null == targetTable.getTableMetaData())) {
logger.info("2个数据表的内容均为空,返回结果:无差异");
return "";
}
if (null == sourceTable || null == sourceTable.getTableMetaData()) {
logger.info("1个数据表的内容不为空,另一个数据表的内容为空,返回结果:整个数据表均有差异");
return targetTable.getTableMetaData().getTableName() + ";";
}
if (null == targetTable || null == targetTable.getTableMetaData()) {
logger.info("1个数据表的内容不为空,另一个数据表的内容为空,返回结果:整个数据表均有差异");
return sourceTable.getTableMetaData().getTableName() + ";";
}
ITableMetaData sourceTableMetaData = sourceTable.getTableMetaData();
ITableMetaData targetTableMetaData = targetTable.getTableMetaData();
logger.info("2个数据表均不为空,2个数据表的表名为:{}和{},开始比较数据表的内容.", sourceTableMetaData.getTableName(), targetTableMetaData.getTableName());
if (!StringUtils.equals(sourceTableMetaData.getTableName(), targetTableMetaData.getTableName())) {
logger.info("2个数据表的表名不相同,返回结果:整个数据表均有差异");
return sourceTableMetaData.getTableName() + ";" + targetTableMetaData.getTableName() + ";";
}
//比较行数
if (sourceTable.getRowCount() != targetTable.getRowCount()) {
logger.info("2个数据表的行数不相同,返回结果:整个数据表均有差异");
return sourceTableMetaData.getTableName() + ";";
}
//比较列
Columns.ColumnDiff columnDiff;
Column[] sourceColumns;
Column[] targetColumns;
try {
sourceColumns = Columns.getSortedColumns(sourceTableMetaData);
targetColumns = Columns.getSortedColumns(targetTableMetaData);
columnDiff = Columns.getColumnDiff(sourceTableMetaData, targetTableMetaData);
} catch (DataSetException e) {
logger.info("读取数据表的字段结构出现异常,返回结果:整个数据表均有差异");
return sourceTableMetaData.getTableName() + ";";
}
//更新:改为仅判断期望是否有差异的字段;
// 因为数据库中为null的实际数据在保存到xml文件中时会丢失,造成在比较时,期望字段比实际数据库字段少
//if (columnDiff.hasDifference()) {
if (columnDiff.getExpected().length > 0) {
//一般case执行后不应该有列的变化;如果列有增删改时,直接返回整个数据表忽略
logger.info("2个数据表的字段结构不一致({}),返回结果:整个数据表均有差异", columnDiff.toString());
return sourceTableMetaData.getTableName() + ";";
} else if (columnDiff.getActual().length > 0) {
logger.info("实际数据表的字段比期望数据表的字段多,一般原因为该数据表有字段的实际取值为nul,因此不会记录在xml文件中导致;" +
"因为不会导致assert失败,故继续比较;内容如下:{}.", columnDiff.toString());
}
//比较列的数据类型
for (int j = 0; j < sourceColumns.length; j++) {
Column sourceColumn = sourceColumns[j];
Column targetColumn = targetColumns[j];
DataType sourceDataType = sourceColumn.getDataType();
DataType targetDataType = targetColumn.getDataType();
if (!(sourceDataType instanceof UnknownDataType) && !(targetDataType instanceof UnknownDataType) && !(sourceDataType.getClass().isInstance(targetColumn))) {
//列的数据类型均存在,且不相同;注:实际上从xml读取的字段类型总是为unknown
logger.info("2个数据表的字段结构不一致(字段{}的数据类型不同),返回结果:整个数据表均有差异", sourceColumn.getColumnName());
return sourceTableMetaData.getTableName() + ";";
}
}
//比较数据
int rowCount = sourceTable.getRowCount();
StringBuilder buf = new StringBuilder();
for (Column column : sourceColumns) {
for (int i = 0; i < rowCount; i++) {
Object sourceValue;
Object targetValue;
try {
sourceValue = sourceTable.getValue(i, column.getColumnName());
} catch (DataSetException e) {
//异常为该行数据无该列字段
sourceValue = null;
}
try {
targetValue = targetTable.getValue(i, column.getColumnName());
} catch (DataSetException e) {
//异常为该行数据无该列字段
targetValue = null;
}
if (!StringUtils.equals(sourceValue != null ? sourceValue.toString() : null, targetValue != null ? targetValue.toString() : null)) {
//发现不同数据,记录该列为diff字段,并无须再比较剩下的行
logger.info("2个数据表的字段:{}出现不同数据,进行记录并继续下个字段的检查.", column.getColumnName());
buf.append(",").append(column.getColumnName());
break;
}
}
}
String diffRst = buf.toString();
if (StringUtils.isNotBlank(diffRst)) {
logger.info("2个数据表的所有字段已经全部比较完毕,差异结果为:{}", diffRst);
return sourceTableMetaData.getTableName() + "(" + diffRst.substring(1) + ");";
}
logger.info("2个数据表的结构和数据完全一致,返回结果:无差异");
return "";
}
对特定格式字符串的处理
/**
* 将字符串形式的DB表达转换成Map方式
* @param dbStr 表示DB的字符串,格式:db1(table1(col1,col2);table2);db2(table21(co21))
* @return 转换后的Map,key为db名称,value为数据表字符串
*/
private static Map<String, String> dbStr2Map(String dbStr) {
//格式:db1(table1(col1,col2);table2);db2(table21(co21))
Map<String, String> dbMap = new HashMap<String, String>();
if (StringUtils.isBlank(dbStr)) return dbMap;
int depth = 0;
String dbName = "";
String tableStr = "";
//以char形式遍历字符串每个字符,解析字符串为Map
for (int i = 0; i < dbStr.length(); i++) {
char c = dbStr.charAt(i);
switch (c) {
case '(':
if (0 != depth) {
tableStr += c;
}
depth++;
break;
case ')':
depth--;
if (0 > depth) {
//左圆括号和右圆括号个数不一致
throw new RuntimeException("输入的DB字符串的格式错误:左圆括号和右圆括号个数不一致");
}
if (0 == depth) {
//又回到顶层,完成了遍历一个DB项,进行记录;并初始化新的遍历
if (StringUtils.isNotBlank(dbName)) {
dbMap.put(dbName, tableStr);
dbName = "";
tableStr = "";
}
} else {
tableStr += c;
}
break;
case ';':
if (0 != depth) {
tableStr += c;
}
break;
default:
if (0 == depth) {
dbName += c;
} else {
tableStr += c;
}
}
}
if (0 != depth) {
//左圆括号和右圆括号个数不一致
throw new RuntimeException("输入的DB字符串的格式错误:左圆括号和右圆括号个数不一致");
}
return dbMap;
}
/**
* 将数据表描述从字符串形式转换为Map形式
*
* @param input 字符串形式: A(a1,a2);B;C(c1)
* @return Map形式:{A=[a1,a2],B=null,C=[c1]}
*/
public static Map<String, List<String>> tablesStr2Map(String input) {
final String SEPARATOR1 = ";"; //key之间的分割符
final String SEPARATOR2 = ","; //value之间的分割符
Map<String, List<String>> result = new HashMap<String, List<String>>();
if (StringUtils.isBlank(input)) return result;
String[] tables = StringUtils.split(input, SEPARATOR1);
for (String table : tables) {
String temp = StringUtils.trim(table);
if (StringUtils.isBlank(temp)) continue;
if (temp.contains("(") && temp.endsWith(")")) {
int index = temp.indexOf("(");
String tableName = temp.substring(0, index);
if (result.containsKey(tableName) && null == result.get(tableName)) {
//此情况说明result表里已经有忽略整个数据表的表名,不应该再添加忽略的字段
continue;
}
String columnStr = temp.substring(index + 1, temp.length() - 1);
String[] columns = StringUtils.split(columnStr, SEPARATOR2);
List<String> columnList = result.get(tableName);
if (columnList == null) {
columnList = new ArrayList<String>();
result.put(tableName, columnList);
}
columnList.addAll(Arrays.asList(columns));
} else {
//if (!result.containsKey(temp)) {
result.put(temp, null);
//}
}
}
//对map的value进行trim()和去重
removeDuplicate(result);
return result;
}
/**
* 将数据表表述从Map形式转换为字符串形式
*
* @param dbMap Map形式:A={[a1,a2],B=null,C=[c1]}
* @return 字符串形式: A(a1,a2);B;C(c1)
*/
public static String tablesMap2Str(Map<String, List<String>> dbMap) {
final String SEPARATOR1 = ";"; //key之间的分割符
final String SEPARATOR2 = ","; //value之间的分割符
if (null == dbMap || dbMap.isEmpty()) return "";
//对map的value进行trim()和去重
removeDuplicate(dbMap);
String result = "";
for (String dbName : dbMap.keySet()) {
if (null != dbName) {
if (null != dbMap.get(dbName) && !dbMap.get(dbName).isEmpty()) {
result += dbName + "(";
List<String> tables = dbMap.get(dbName);
StringBuilder buf = new StringBuilder();
for (String table : tables) {
buf.append(table).append(SEPARATOR2);
}
result += buf.toString();
result = result.substring(0, result.length() - 1);
result += ")" + SEPARATOR1;
} else {
result += dbName + SEPARATOR1;
}
}
}
//去除最后的分号
if (result.endsWith(SEPARATOR1)) {
result = result.substring(0, result.length() - 1);
}
return result;
}
/**
* 1.对map的值进行trim()
* 2.对map的值进行去重
*
* @param dbMap 去重后map
*/
private static void removeDuplicate(Map<String, List<String>> dbMap) {
if (null == dbMap) return;
for (String key : dbMap.keySet()) {
List<String> value = dbMap.get(key);
if (null == value) continue;
Set<String> setValue = new HashSet<String>();
for (int i = 0; i < value.size(); i++) {
String e = value.get(i);
//先做简化处理
if (null != e) {
e = e.replaceAll("\n", "").replaceAll("\r", "").replaceAll("\t", "").trim();
}
if (setValue.contains(e)) {
//该元素已经存在
value.remove(i);
i--;
} else {
//该元素还不存在
setValue.add(e);
value.set(i, e);
}
}
}
}