本文主要根据项目中实际遇到情况作为案例简单分享一下思路。
1.现有内网服务器一台,外网服务器一台。已搭建好FTP服务器,并完成了端口映射。实际情况如下:
1.外网服务器部署服务(此处不需要DB相关服务,只需要搭建FTP环境即可),调用外网服务器接口,外网服务将【请求】依据【自定义格式】以及【唯一规则命名】生成文件保存上传FTP输入目录中。
2.内网服务器部署完整的服务,监听FTP输入目录,一旦发现有新文件生成,则读取文件内容解析请求信息,然后调用接口,将【返回数据】依据【自定义格式】以及【唯一规则命名】生成文件保存上传FTP输出目录。(此处不采取外网服务器监听FTP输出目录来解析文件这种方式,考虑到会长期连接FTP服务器,浪费资源,也会出现掉线情况,还得考虑心跳包,而且整体效果不好)
为了模拟正常的前后端交互,便采取了如下方式:
1.客户端发出请求,外网服务器将请求根据【唯一命名】写入文件到FTP输入目录,之后对FTP输出目录进行轮询(根据网络环境需求,我这里最多只轮询了3次,每次间隔1S),如发现有同名文件返回则进行解析然后将数据返回至客户端。
2.在监听输入目录有新文件生成时,解析新文件,根据请求信息来执行业务代码,将返回数据封装成文件(文件命名同收到文件一样)保存至FTP输出目录。
3.注意:在进行解析后的文件都需删除,避免FTP目录中文件过多,导致轮询时间过长,若需要备份,则另外增加备份功能。
1.外网服务器收到请求
@RequestMapping("/list")
//此处参数中的 loraName 为 当前操作用户,用于生成文件命名规则使用,data为请求数据。
public R list(String loginName, String data, HttpServletRequest httpServletRequest) throws IOException {
String accessToken = httpServletRequest.getHeader("accessToken");
//baseUrl 为配置文件中项目请求路径
String url = baseUrl+"/api/adviseManange/car/list";
//此处我自定义的唯一文件名称为 【登录名 + 时间戳(精确到毫秒)】
SimpleDateFormat sdf =new SimpleDateFormat("YYYYMMddHHmmssSSS");
String date = sdf.format(new Date());
String fileName =loginName+"_"+date;
//将文件写入FTP输入目录
ftpServerUtil.writeAndUploadFile(url,data,fileName,localUrl,accessToken);
//轮询 查询FTP输出目录
String backData = FTPSearchFile_out.fileList(host,port,userName,password,basePath,fileName+".txt");
if (StringUtils.isBlank(backData)){
return R.error("连接超时");
}
Map<String,Object> retMap = new Gson().fromJson(backData,Map.class);
return R.ok(retMap);
}
2.FTP写入操作 (此处采用的每次请求都去连接FTP服务器,也可采用先写一个FTP连接池,每次需要的时候就从连接池中借一个,用完还掉。可以根据具体项目来考虑,前者在性能上能满足就不需要使用后者了,后者在性能上更好,但是得增加连接池的维护操作)
@Component
public class FtpServerUtil {
@Autowired
FTPUtils ftpUtils;
@Autowired
//此处实体类已经注入了配置文件中 填写的相关 FTP服务器 配置
private FTPProperties ftpProperties;
public void writeAndUploadFile(String url,String data,String fileName,String localUrl,String accessToken) throws IOException {
Map<String,String> query = new HashMap<>();
query.put("url",url);
query.put("data",data);
if (StringUtils.isNotBlank(accessToken)){
query.put("accessToken",accessToken);
}
String queryData = new Gson().toJson(query);
// 生成的文件路径
String path = localUrl +"\\"+ fileName + ".txt";
File file = new File(path);
if (!file.exists()) {
file.getParentFile().mkdirs();
}
file.createNewFile();
// write 解决中文乱码问题
// FileWriter fw = new FileWriter(file, true);
OutputStreamWriter fw = new OutputStreamWriter(new FileOutputStream(file), "UTF-8");
BufferedWriter bw = new BufferedWriter(fw);
bw.write(queryData);
bw.flush();
bw.close();
fw.close();
uploadFile(fileName+".txt", path);
}
public void uploadFile(String remoteFile, String localFile) {
try {
FTPClient ftp = new FTPClient();
ftp.addProtocolCommandListener(new PrintCommandListener(new PrintWriter(System.out)));
ftp.connect(ftpProperties.getHost(),ftpProperties.getPort());
if(FTPReply.isPositiveCompletion(ftp.getReplyCode()))
{
ftp.login(ftpProperties.getUsername(),ftpProperties.getPassword());
}
int replyCode = ftp.getReplyCode(); //是否成功登录服务器
if(!FTPReply.isPositiveCompletion(replyCode)){
System.out.println("connect failed...ftp服务器");
}
ftp.changeWorkingDirectory(ftpProperties.getBaseUrl());
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);
FileInputStream input = new FileInputStream(new File(localFile));
ftp.storeFile(remoteFile, input);
ftp.disconnect();
input.close();
}catch (Exception e){
e.printStackTrace();
}
}
}
3.内网服务器监听FTP输入目录
@Component
public class FileListenerFactory {
// 设置监听路径 此处是本地测试地址,可以换成服务器地址
private final String monitorDir = "F:\\FTPTestIn";
// 设置轮询间隔
private final long interval = TimeUnit.SECONDS.toMillis(1);
// 自动注入业务服务
@Autowired
private ListenerService listenerService;
public FileAlterationMonitor getMonitor() {
// 创建过滤器
System.out.println("===in FileAlterationMonitor=== ");
IOFileFilter directories = FileFilterUtils.and(
FileFilterUtils.directoryFileFilter(),
HiddenFileFilter.VISIBLE);
IOFileFilter files = FileFilterUtils.and(
FileFilterUtils.fileFileFilter(),
FileFilterUtils.suffixFileFilter(".txt"));
IOFileFilter filter = FileFilterUtils.or(directories, files);
// 装配过滤器
// FileAlterationObserver observer = new FileAlterationObserver(new File(monitorDir));
FileAlterationObserver observer = new FileAlterationObserver(new File(monitorDir), filter);
System.out.println("observer= "+observer);
// 向监听者添加监听器,并注入业务服务
observer.addListener(new FileListener(listenerService));
// observer.addListener(new FileListener());
// 返回监听者
return new FileAlterationMonitor(interval, observer);
}
}
@Component
public class FileListenerRunner implements CommandLineRunner {
@Autowired
private FileListenerFactory fileListenerFactory;
@Override
public void run(String... args) throws Exception {
// 创建监听者
System.out.println("fileListenerFactory= "+fileListenerFactory);
FileAlterationMonitor fileAlterationMonitor = fileListenerFactory.getMonitor();
try {
// do not stop this thread
fileAlterationMonitor.start();
} catch (Exception e) {
e.printStackTrace();
}
}
}
public class FileListener extends FileAlterationListenerAdaptor{
// 声明业务服务
private ListenerService listenerService;
// 采用构造函数注入服务
public FileListener(ListenerService listenerService) {
this.listenerService = listenerService;
}
@Override
public void onStart(FileAlterationObserver observer) {
}
@Override
public void onDirectoryCreate(File directory) {
}
@Override
public void onDirectoryChange(File directory) {
}
@Override
public void onDirectoryDelete(File directory) {
}
@Override
public void onFileCreate(File file) {
try {
listenerService.require(file);
} catch (IOException e) {
e.printStackTrace();
}
}
@Override
public void onFileChange(File file) {
}
@Override
public void onFileDelete(File file) {
}
@Override
public void onStop(FileAlterationObserver observer) {
}
}
监听业务处理类
@Service
public class ListenerService {
@Value("${local-saveUrl}")
private String localUrl;
@Autowired
FtpServerUtil ftpServerUtil;
public void require(File file) throws IOException {
String fileName = file.getName();
fileName = fileName.substring(0,fileName.lastIndexOf("."));
InputStreamReader isr = new InputStreamReader(new FileInputStream(file), "UTF-8");
BufferedReader br = new BufferedReader(isr);
StringBuffer resposeBuffer = new StringBuffer("");
String lineTxt = "";
String b = "";
while ( (b = br.readLine()) != null) {
lineTxt = lineTxt + b ;
}
Map<String,String> map = new HashMap<>();
try {
map = (Map)JSON.parse(lineTxt);
}catch (Exception e){
e.printStackTrace();
}
String url = map.get("url");
String data = map.get("data");
String accessToken = map.get("accessToken");
Map<String,String> dataMap = new HashMap<>();
try {
dataMap = (Map<String,String>)JSON.parse(data);
}catch (Exception e){
e.printStackTrace();
}
if (dataMap == null || dataMap.size()<=0){
dataMap = new HashMap<>();
}
//通过HttpPost 方式调用接口
String retString = HttpUtil.doPost(url, dataMap, accessToken);
//将返回的数据封装并写入FTP输出目录。
ftpServerUtil.writeAndUploadFile(retString,fileName,localUrl);
br.close();
}
}
1.在写入文件时,文件命名唯一的标准。
2.根据服务器,网络环境设置轮询时间。
3.根据项目实际使用情况来选用FTP直连 或者 连接池方式。
此为项目中遇到,特此记录采用的方式,以及解决思路,也许之后经验丰富了,能有更好的解决办法,若有不正确,漏掉的细节部分,望各位大佬留言提醒,谢谢了!【 一位搬砖码农日常】