我们的需求是将文件上传到另一个文件服务器,不存在本地,然后实现文件的增删查改和预览功能。
小白啊,IO操作什么的基本没弄过,网络学的也不好,就搞这个操作,颇费心力。在网上扒了无数的帖子,最终用了一个多周实现了,总结下来其实也没有那么那么难(当然我只是生搬硬套)。下面把具体的实现过程分享出来。
前端框架:Layui
后端:Springboot
需求:把上传、删除、替换按钮和预览功能放在数据表格中。文件上传至文件服务器。
使用范围:内网用户(外网连接可以在此基础上另外了解)
最终效果:
这部分我写到了另一个博客,Layui 数据表格嵌套文件上传按钮,根据行数据id上传文件。
在网上找了两个教程,跟我当时设置的流程差不多:
只要在网页上输入ftp://192.168.xxx.xxx:端口,然后输入用户名和密码(如果有的话)可以看到文件列表,就说明部署成功了。
我遇到的问题:
在这里走了许多弯路,网上有许多这种代码,大致是相同的,但是又有细微差别。我创建了一个工具类,先把调试正常的代码写出来。遇到的问题后面会提到。
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;
import org.springframework.web.multipart.MultipartFile;
import java.io.*;
import java.net.SocketException;
import java.util.Date;
/**
* @Author 27号白开水
* @Date 2020/6/21 15:54
*/
public class FtpUtil {
private static Logger logger = Logger.getLogger(FtpUtil.class);
//ftp服务器ip地址
private static final String FTP_ADDRESS = "192.168.xxx.xxx";
//端口号
private static final int FTP_PORT = 2333;
//用户名
private static final String FTP_USERNAME = "upload";
//密码
private static final String FTP_PASSWORD = "123456";
//本地字符编码
private static String LOCAL_CHARSET = "GBK";
// FTP协议里面,规定文件名编码为iso-8859-1
private static final String SERVER_CHARSET = "ISO-8859-1";
//附件路径,这里没用到
//private static String FTP_BASEPATH = "";
//连接ftp, 获取到FTPClient对象
public static FTPClient getFTPClient(){
FTPClient ftp = new FTPClient();
try {
int reply;
ftp.connect(FTP_ADDRESS, FTP_PORT);//连接FTP服务器
ftp.login(FTP_USERNAME, FTP_PASSWORD);//登录
ftp.setConnectTimeout(50000);// 设置连接超时时间,5000毫秒
if(!FTPReply.isPositiveCompletion(ftp.getReplyCode())){
logger.info("未连接到FTP,用户名或密码错误");
ftp.disconnect();
return ftp;
}else {
logger.info("FTP连接成功");
}
// 开启服务器对UTF-8的支持,如果服务器支持就用UTF-8编码,否则就使用本地编码(GBK)
if (FTPReply.isPositiveCompletion(ftp.sendCommand("OPTS UTF8", "ON"))) {
LOCAL_CHARSET = "UTF-8";
}
ftp.setControlEncoding(LOCAL_CHARSET);//设置字符集编码方式
} catch (SocketException e) {
e.printStackTrace();
logger.info("FTP的IP地址可能错误,请正确配置");
} catch (IOException e) {
e.printStackTrace();
logger.info("FTP的端口错误,请正确配置");
}
return ftp;
}
//关闭FTP方法
public static boolean closeFTP(FTPClient ftp){
try {
ftp.logout();
} catch (Exception e) {
logger.error("FTP关闭失败");
}finally{
if (ftp.isConnected()) {
try {
ftp.disconnect();
} catch (IOException ioe) {
ioe.printStackTrace();
logger.error("FTP关闭失败");
}
}
}
return false;
}
//上传文件
public static boolean uploadFile(FTPClient ftp, MultipartFile multipartFile, String filePath) throws IOException {
//获取上传的文件流
InputStream inputStream = multipartFile.getInputStream();
String fileName = multipartFile.getOriginalFilename();
boolean success = true;
try {
ftp.enterLocalPassiveMode();//设置被动传输
ftp.setFileType(FTPClient.BINARY_FILE_TYPE);//设置文件传输模式为二进制,可以保证传输的内容不会被改变,ASC容易造成文件损坏
String directory = filePath.substring(0, filePath.lastIndexOf("/") + 1);
// 如果远程目录不存在,则递归创建远程服务器目录,这里是用于多层文件夹嵌套新建的情况,如果只有一层,那么只需要 1:跳转目录 2:不存在就新建
if (!directory.equalsIgnoreCase("/") //忽略大小写进行比较
&& !ftp.changeWorkingDirectory(new String(filePath.getBytes(LOCAL_CHARSET),SERVER_CHARSET))) {
int start = 0;
int end = 0;
if (directory.startsWith("/")) {
start = 1;
} else {
start = 0;
}
end = directory.indexOf("/", start);//查询除开头“/”之外的第一个“/”的位置
while (true) {
String subDirectory = filePath.substring(start, end);
if (!ftp.changeWorkingDirectory(subDirectory)) {//跳转子目录
if (ftp.makeDirectory(new String(subDirectory.getBytes(LOCAL_CHARSET),SERVER_CHARSET))) {//新建子文件夹
ftp.changeWorkingDirectory(subDirectory);//再次尝跳转子目录
} else {
System.out.println("创建目录失败");
success = false;
return success;
}
}
start = end + 1;
end = directory.indexOf("/", start);
// 检查所有目录是否创建完毕
if (end <= start) {
break;
}
}
}
//跳转目标目录
ftp.changeWorkingDirectory(filePath);
success = ftp.storeFile(new String(fileName.getBytes(LOCAL_CHARSET),SERVER_CHARSET), inputStream); //存储
if(success){
logger.info("上传成功");
}else{
logger.error("上传失败");
}
} catch (IOException e) {
e.printStackTrace();
logger.error("上传失败");
} finally {
try {
inputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return success;
}
//替换文件
public static Boolean replaceFile(MultipartFile file, String filePath, String fileName) throws IOException {
Boolean success = false;
FTPClient ftpClient = getFTPClient();
deleteFile(ftpClient, filePath,fileName); //删除文件
uploadFile(ftpClient, file, filePath);
closeFTP(ftpClient);
return success;
}
//删除文件
public static Boolean deleteFile(FTPClient ftpClient, String filePath, String fileName){
boolean flag = false;//转移至目标目录
try {
ftpClient.changeWorkingDirectory(new String(filePath.getBytes(LOCAL_CHARSET), SERVER_CHARSET));//跳转目录
flag = ftpClient.deleteFile(new String(fileName.getBytes(LOCAL_CHARSET), SERVER_CHARSET));//删除文件
if (!flag) {
throw new Exception("FTP附件删除失败!");
}
} catch (IOException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return flag;
}
}
controller或者service调用就是这样:
//文件路径
String filePath = "/2020/";
//调用自定义的FTP工具类上传文件
FTPClient ftpClient = FtpUtil.getFTPClient();
Boolean success = FtpUtil.uploadFile(ftpClient, multipartFile, filePath);//调用工具类上传
System.out.println(success? "上传成功": "上传失败");
FtpUtil.closeFTP(ftpClient);
我这里只是简单地用到了连接、上传、删除,还有5 中一个获取文件流,其他操作可以参考以下两位大佬的代码:
预览的问题困扰了我很久,因为不知道怎么实现。后来看到pdf.js这个插件,只要获取到文件输入流就可以实现预览。中间的心路历程堪称心酸,吃了没文化的苦。
pdf.js的用法很简单,就是下载,然后放到项目静态文件目录中,可以参考这两个文章:
https://blog.csdn.net/semial/article/details/89510312
https://blog.csdn.net/qq_36537546/article/details/105793577
最终的实现过程是这样的:先从FTP获取文件流,在本地生成一个临时文件,然后用pdf.js渲染临时文件。
现在想来,其实关键在于获取临时文件,pdf.js的用处只是渲染的更好看一些。
下面是关键(所有)代码:
js:
//表头渲染,使点击文件名即可预览
, {field: 'file_name', title: '文件名称', width: 480
, templet: function (d) {
if (d.file_name != null && d.file_name != '' && d.file_name != undefined){
return ''+d.file_name+'';
}else {
return '未上传';
}
}
}
//对行操作进行监听,调用pdf.js打开临时文件
window.open("/static/js/mes/fileManagement/web/viewer.html?file=" //前半句是pdf.js的viewer.html的路径
+ encodeURIComponent("/allFiles/showDetail?filePath="+filePath));
//后半句是controller的注解路径加传参,filePath是文件服务器路径+文件名。
//然后对URL进行编码,否则会因为出现两个问号而报错
Controller:
@RequestMapping("/showDetail")
public void showDetail(String filePath, HttpServletRequest request, HttpServletResponse response) throws IOException {
System.out.println("文件查看" + filePath);
// 编辑请求头部信息
// 解决请求头跨域问题(IE兼容性 也可使用该方法)
response.setHeader("Access-Control-Allow-Origin", "*");
response.setContentType("application/pdf");
FTPClient ftpClient = FtpUtil.getFTPClient();
FileInputStream inputStream = FtpUtil.getStream(ftpClient, filePath);
byte[] data = null;
data = new byte[inputStream.available()];
inputStream.read(data);
response.getOutputStream().write(data);
inputStream.close();
FtpUtil.closeFTP(ftpClient);
}
FtpUtil工具类:
//获取预览需要的文件流信息
public static FileInputStream getStream(FTPClient ftpClient, String filePath) throws IOException {//filePath是文件夹名加文件名
//在客户端本地生成一个临时文件
File tempFile = new File("E:/","mesTempFile.pdf");
//将预览文件放到临时文件
OutputStream outputStream = new FileOutputStream(tempFile);
ftpClient.retrieveFile(new String(filePath.getBytes(LOCAL_CHARSET), SERVER_CHARSET),outputStream);
outputStream.close();
//读取临时文件的文件流
FileInputStream fileInputStream = new FileInputStream(tempFile);
return fileInputStream;
}
基本内容就是这些。
不足之处,请多指教。