Linux搭建FTP文档服务器过程记录

前言:

    公司业务需要,现将系统中与文档上传下载预览相关的服务接口从阿里云OSS转移到本地化部署的文档服务器中。现使用FTP文档服务器的功能替换原有接口来完成实现,实现过程记录


目录

一:FTP的搭建:

1.1 准备环境、创建账号

1.2  用户配置、端口配置、用户鉴权

二. 接口实现

1. maven依赖:

2. 上传接口

3. 下载接口

4. 删除接口

5. 接口过程中遇见的问题


一:FTP的搭建:

     需要在Linux系统中搭建FTP服务,实现上传下载功能

1.1 准备环境、创建账号

       1. 准备一台Linux7.0一上版本的服务器,查询是否已经安装FTP服务:rpm -qa |grep vsftpd

如果查询 无结果,则表示该服务器未安装FTP服务。执行命令:yum -y install vsftpd

安装出现:complete表示安装已经完成,默认端口为21

       2. 启动FTP:systemctl start vsftpd.service

           查询FTP启动状态:systemctl status vsftpd.service

           关闭FTP:systemctl stop vsftpd.service

           重启FTP: systemctl restart vsftpd.service

           设置FTP开机自启动:chkconfig vsftpd on

        3. 创建一个FTP操作的目录:mkdir -p /opt/pms/ftptest

        4. 创建FTP用户:useradd -d /opt/pms/ftptest -g ftp -s /sbin/nologin ftptest

 -g ftp 表示该用户属于ftp分组 (ftp分组是内置的,本来就存在,不需要自己创建)
-s /sbin/nologin 表示这个用户不能用来登录secureCRT这样的客户端。 这种不能登陆的用户又叫做虚拟用户
创建过程给出的警告信息是正常的,不用理会

        5. 设置目录权限:

         chown -R ftptest /home/wwwroot/ftptest     #把目录/home/wwwroot/ftptest的拥有者设置为ftptest

         chmod -R 775 /home/wwwroot/ftptest        #使ftptest用户拥有这个目录的读写权限

        6. 设置密码:passwd ftptest

1.2  用户配置、端口配置、用户鉴权

        1. 给创建的用户去掉匿名登录(默认情况下vsftpd服务器是允许匿名登陆的,这样非常不安全,所以要把这个选项关闭掉)

            打开配置文件:vi /etc/vsftpd/vsftpd.conf

            将原先的anonymous_enable=YES   ->>>>>  anonymous_enable=NO,并保存退出:wq

        2. 限制用户访问(如果不做限制,那么使用ftptest登陆之后可以切换到其他敏感目录去,比如切换到/usr目录去,这样就存在巨大的安全隐患)

           打开配置文件:vi /etc/vsftpd/vsftpd.conf

#chroot_list_enable=YES

# (default follows)

#chroot_list_file=/etc/vsftpd.chroot_list

修改为:

chroot_list_enable=YES    #表示对用户的访问进行限制

# (default follows)

chroot_list_file=/etc/vsftpd/chroot_list   #用户清单。在该列表中的用户将允许访问

      3. 编辑用户清单,将我们需要的用户添加至清单

          命令:vi /etc/vsftpd/chroot_list

         打开后是空的,然后将需要的用户添加进去即可。例如:ftptest

   4. 允许写权限

     一旦某个用户被限制访问了,那么默认情况下,该用户的写权限也被剥夺了。 这就导致ftp客户端连接上服务器之后无法上传文件。

通过命令:vi /etc/vsftpd/vsftpd.conf   

最后一行添加:allow_writeable_chroot=YES   

   5. 开放端口

      21端口自是用来监听客户端请求的,还需要开放端口,来完成服务端与客户端传输数据的端口

配置文件:vi /etc/vsftpd/vsftpd.conf

在最后添加:

pasv_enable=YES

pasv_min_port=30000

pasv_max_port=30010

   6. 用户鉴权(因为用户 ftptest 是 nologin的,所以存在鉴权的问题)

vi /etc/pam.d/vsftpd

注释掉:#auth required pam_shells.so

保存即可

  7. 重启FTP,通过工具连接该服务器的FTP,即可完成文件的上传下载功能 

二. 接口实现

        搭建的FTP服务器,想通过java接口的形式完成上传下载删除的功能,以满足本地化文档服务器的需求。这里使用的是FTPClient对象的相关API完成。

1. maven依赖:

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

2. 上传接口

(将文件通过前端上传,接口接受后存放在FTP 服务器)

 /**
     * 文件上传
     *
     * @param fileName    : 上传的文件名
     * @param inputStream 文件的流文件
     * @return map  fileName: 文件原本名 | ftpFileName:上传至FTP文件名 | status:true:成功、false:失败 | path:上传的文档路径
     */
    @Override
    public Map<String, Object> uploadFile(String fileName, InputStream inputStream) {
        Map<String, Object> map = new HashMap<>();
        map.put("fileName", fileName);
        //将文件名添加时间戳
        fileName = DateUtil.dateToUnixSecond(new Date()) + fileName;
        map.put("ftpFileName", fileName);
        //文件上传到FTP的路径
        String path = ftpConfig.getFilePath()+ DateUtil.getDateStr();
        String fullPath = path;
        boolean flag = false;
        //1. 创建ftpclient对象
        FTPClient ftpClient = new FTPClient();
        //2. 设置编码 格式
        ftpClient.setControlEncoding("UTF-8");
        try {
            //连接FTP服务器
            ftpClient.connect(ftpConfig.getHostName(), ftpConfig.getPort());
            //登录FTP服务器
            ftpClient.login(ftpConfig.getUserName(), ftpConfig.getPassWord());
            //是否成功登录FTP服务器
            int replyCode = ftpClient.getReplyCode();
            if(!FTPReply.isPositiveCompletion(replyCode)){
                map.put("status", flag);
                return map;
            }
            ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);
            //makeDirectory()方法创建目录,返回true表示服务器中不存在此目录,false表示已经存在此目录
            ftpClient.makeDirectory(path);
            //变更路径
            ftpClient.changeWorkingDirectory(path);
            //此段代码是做上传文件分文件夹处理
            /*FTPFile[] ftpFiles = ftpClient.listFiles();
            //当该文件夹的文件数量大于5000时,则创建新的文件,路径为yyyyMMdd_1--yyyyMMdd_10 共十个文件夹
            if(ftpFiles.length >= MAX_SIZE) {
                for (int i = 1; i <= 10; i++) {
                    fullPath  = path + "_"+ i;
                    ftpClient.makeDirectory(fullPath);
                    ftpClient.changeWorkingDirectory(fullPath);
                    FTPFile[] charFtpFiles = ftpClient.listFiles();
                    if(charFtpFiles.length < MAX_SIZE) {
                        break;
                    }
                }
            }*/
            map.put("path", fullPath);
            ftpClient.storeFile(fileName, inputStream);
            inputStream.close();
            ftpClient.logout();
            flag = true;
            map.put("status", flag);
        } catch (Exception e) {
            GwsLogger.error("文件上传失败!,文件名={}", map.get("fileName"));
            map.put("status", flag);
            return map;
        } finally{
            if(ftpClient.isConnected()){
                try {
                    ftpClient.disconnect();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }
        return map;
    }

3. 下载接口

(通过文件名+文件路径参数进行下载,可将文件名与路径存放在数据库, 前端点击下载时参数传过来直接下载,此接口可通过response返回,在前端请求直接弹出下载框)

/**
     * 下载文件
     *
     * @param filename :下载的文件名
     * @param path :下载的文件路径
     * @param response 响应体(直接弹窗下载)
     */
    @Override
    public boolean downloadFile(String filename, String path, HttpServletResponse response) {
        filename = filename.trim();
        boolean flag = false;
        FTPClient ftpClient = new FTPClient();
        try {
            //连接FTP服务器
            ftpClient.connect(ftpConfig.getHostName(), ftpConfig.getPort());
            //登录FTP服务器
            ftpClient.login(ftpConfig.getUserName(), ftpConfig.getPassWord());
            //验证FTP服务器是否登录成功
            int replyCode = ftpClient.getReplyCode();
            if(!FTPReply.isPositiveCompletion(replyCode)){
                return flag;
            }
            ftpClient.setControlEncoding("UTF-8");
            //切换FTP目录,按照ISO_8859_1编码
            ftpClient.changeWorkingDirectory(new String(path.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            FTPFile[] ftpFiles = ftpClient.listFiles();
            if(null == ftpFiles || ftpFiles.length == 0) {
                return false;
            }
            for(FTPFile file : ftpFiles){
                if(filename.equalsIgnoreCase(file.getName())){
                    File localFile = new File("/" + file.getName());
                    OutputStream os = new FileOutputStream(localFile);
                    String  ftpFileName = new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                    ftpClient.retrieveFile(ftpFileName, os);
                    InputStream input = new FileInputStream(localFile);
                    byte[] data = IOUtils.toByteArray(input);
                    response.setContentType("application/binary;charset=UTF-8");
                    response.setHeader("Content-disposition", "attachment;filename=" + URLEncoder.encode(filename, "utf-8"));
                    response.setContentLength(data.length);
                    response.getOutputStream().write(data);
                    response.getOutputStream().flush();
                    response.getOutputStream().close();
                    os.flush();
                    os.close();
                    input.close();
                    localFile.delete();
                }
            }
            ftpClient.logout();
            flag = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(ftpClient.isConnected()){
                try {
                    ftpClient.logout();
                } catch (IOException e) {

                }
            }
        }
        return flag;
    }

4. 删除接口

(通过文件路径与文件名进行删除)

/**
     * 删除文件
     * @param path :文件路径
     * @param filename :文件名
     */
    @Override
    public boolean deleteFile(String path, String filename) {
        boolean flag = false;
        FTPClient ftpClient = new FTPClient();
        try {
            //连接FTP服务器
            ftpClient.connect(ftpConfig.getHostName(), ftpConfig.getPort());
            //登录FTP服务器
            ftpClient.login(ftpConfig.getUserName(), ftpConfig.getPassWord());
            //验证FTP服务器是否登录成功
            int replyCode = ftpClient.getReplyCode();
            if(!FTPReply.isPositiveCompletion(replyCode)){
                return flag;
            }
            ftpClient.setControlEncoding("UTF-8");
            //切换FTP目录
            ftpClient.changeWorkingDirectory(new String(path.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            ftpClient.dele(new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            ftpClient.logout();
            flag = true;
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(ftpClient.isConnected()){
                try {
                    ftpClient.logout();
                } catch (IOException e) {

                }
            }
        }
        return flag;
    }

5. 预览接口

 

服务器安装openoffice,通过openoffice完成文档的预览功能。安装openoffice方法参考博客:https://www.cnblogs.com/Oliver-rebirth/p/Linux_openOffice.html

public RetResult filePreview(HttpServletRequest request, String filename, String path, HttpServletResponse response) {
        Map<String, Object> map = new HashMap<>();
        filename = filename.trim();
        String pdfFileName = PrimaryKeyGeneratorTool.generateKey32() + filename;
        boolean flag = false;
        boolean overUpload = false;
        FTPClient ftpClient = new FTPClient();
        try {
            //连接FTP服务器
            ftpClient.connect(ftpConfig.getHostName(), ftpConfig.getPort());
            //登录FTP服务器
            ftpClient.login(ftpConfig.getUserName(), ftpConfig.getPassWord());
            //验证FTP服务器是否登录成功
            int replyCode = ftpClient.getReplyCode();
            if(!FTPReply.isPositiveCompletion(replyCode)){
                return RetResult.setError("error", "文档服务器连接失败,请联系管理员");
            }
            ftpClient.setControlEncoding("UTF-8");
            //切换FTP目录,按照ISO_8859_1编码
            ftpClient.changeWorkingDirectory(new String(path.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
            FTPFile[] ftpFiles = ftpClient.listFiles();
            if(null == ftpFiles || ftpFiles.length == 0) {
                return RetResult.setError("empty", "该文档不存在!");
            }
            for(FTPFile file : ftpFiles){
                if(filename.equalsIgnoreCase(file.getName())){
                    flag = true;
                    File localFile = new File("/opt/pms/ftptest/" + file.getName());
                    OutputStream os = new FileOutputStream(localFile);
                    String ftpFileName = new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
                    ftpClient.retrieveFile(ftpFileName, os);
                    InputStream input = new FileInputStream(localFile);

                    String type = filename.substring(filename.lastIndexOf("."));
                    if(".xls".equals(type) || ".xlsx".equals(type)){
                        String htmlString = ExcelToHTML.readExcelToHtmlString(input,true);
                        map.put("htmlString", htmlString);
                        localFile.delete();
                        return RetResult.setSuccess(map);
                    }else if(".pdf".equals(type)) {
                        FileInputStream in = new FileInputStream(localFile);
                        response.setContentType("application/pdf");
                        OutputStream out = response.getOutputStream();
                        byte[] b = new byte[1024];
                        while ((in.read(b))!=-1) {
                            out.write(b);
                        }
                        out.flush();
                        in.close();
                        out.close();
                    }else{
                        Doc2PDFUtil doc2PDFUtilInstance = Doc2PDFUtil.getDoc2PDFUtilInstance();
                        FileInputStream in = doc2PDFUtilInstance.file2pdfFile(input,type,request);
                        response.setContentType("application/pdf");
                        OutputStream out = response.getOutputStream();
                        byte[] b = new byte[1024];
                        while ((in.read(b))!=-1) {
                            out.write(b);
                        }
                        out.flush();
                        in.close();
                        out.close();
                        in.close();
                    }
                    os.flush();
                    os.close();
                    localFile.delete();
                }
            }
            ftpClient.logout();
            if(!flag) {
                return RetResult.setError("emply", "文档不存在,预览失败!");
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally{
            if(ftpClient.isConnected()){
                try {
                    ftpClient.logout();
                } catch (IOException e) {
                    GwsLogger.error("IO异常");
                }
            }
        }
        return RetResult.setSuccess("ok");
    }
public static FileInputStream file2pdfFile(InputStream fromFileInputStream, String type, HttpServletRequest request) throws IOException {

        final String PATH= request.getSession().getServletContext().getRealPath(File.separator);
        GwsLogger.info(PATH);
        Date date = new Date();
        SimpleDateFormat sdf = new SimpleDateFormat("yyyyMMddHHmmss");
        String timesuffix = sdf.format(date);
        String docFileName = null;
        String htmFileName = null;
        if(".doc".equals(type)){
            docFileName = "doc_" + timesuffix + ".doc";
            htmFileName = "doc_" + timesuffix + ".pdf";
        }else if(".docx".equals(type)){
            docFileName = "docx_" + timesuffix + ".docx";
            htmFileName = "docx_" + timesuffix + ".pdf";
        }else if(".xls".equals(type)){
            docFileName = "xls_" + timesuffix + ".xls";
            htmFileName = "xls_" + timesuffix + ".pdf";
        }else if(".ppt".equals(type)){
            docFileName = "ppt_" + timesuffix + ".ppt";
            htmFileName = "ppt_" + timesuffix + ".pdf";
        }else if(".xlsx".equals(type)){
            docFileName = "xls_" + timesuffix + ".xls";
            htmFileName = "xls_" + timesuffix + ".pdf";
        } else{
            throw  new ImplException("1","当前文件格式不支持转换为pdf格式");
        }
        File pdfOutputFile = new File(PATH+ File.separatorChar + htmFileName);
        File docInputFile = new File(PATH + File.separatorChar + docFileName);
        if (pdfOutputFile.exists())
            pdfOutputFile.delete();
        pdfOutputFile.createNewFile();
        if (docInputFile.exists())
            docInputFile.delete();
        docInputFile.createNewFile();
        /*** 由fromFileInputStream构建输入文件  */
        try {
            OutputStream os = new FileOutputStream(docInputFile);
            int bytesRead = 0;
            byte[] buffer = new byte[1024 * 8];
            while ((bytesRead = fromFileInputStream.read(buffer)) != -1) {
                os.write(buffer, 0, bytesRead);
            }
            os.close();
            fromFileInputStream.close();
        } catch (IOException e) {

        }
        // 连接服务
        OpenOfficeConnection connection = new SocketOpenOfficeConnection(8900);
        try {
            connection.connect();
        } catch (ConnectException e) {
            GwsLogger.error("文件转换出错,请检查OpenOffice服务是否启动。");
        }
        FileInputStream pdfStream=null;
        // convert 转换
        try {
            DocumentConverter converter = new StreamOpenOfficeDocumentConverter(connection);
            converter.convert(docInputFile, pdfOutputFile);
            connection.disconnect();
            pdfStream=new FileInputStream(pdfOutputFile);
            return pdfStream;
        }catch (Exception e){
            GwsLogger.error("文件转换异常"+e.getMessage());
        }finally {
            // 转换完之后删除word文件
            docInputFile.delete();
            pdfOutputFile.delete();
        }
        return null;
    }

 

6. 接口过程中遇见的问题

问题:切换FTP目录失败、创建文件目录失败:

ftpClient.makeDirectory、ftpClient.changeWorkingDirectory方法失败,返回报错550

原因:因为FTP的账号配置问题,在Linux中FTP的配置文件做了用户限制访问,也就是打开了chroot_list_enable=YES配置,并在chroot_list_file=/etc/vsftpd/chroot_list列表中将当前接口登录的FTP账号添加了进去,导致FTP目录创建/切换失败

解决方案:将FTP的用户限制访问配置接触即可,详情可看上面的用户限制配置

 

问题:中文文件名下载后内容为空

原因:因为获取FTP文件名时,编码问题导致。

解决方案:

1. 将FTP的编码方式切换为ftpClient.setControlEncoding("UTF-8");
2. 切换路径时,将路径的编码方式修改为UTF-8以及.ISO_8859_1
ftpClient.changeWorkingDirectory(new String("/opt/pms/ftptest/".getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));
3. 获取文件名时,将文件名的编码方式设置为UTF-8
String ftpFileName = new String(file.getName().getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1);
ftpClient.retrieveFile(ftpFileName, os);

 

问题:FTP调用dele()方法失败,提示550 Delete operation failed

原因:因为中文文件名删除失败

解决方案:将文件名通过UTF-8进行编码以及ISO_8859_1即可完成删除方法
ftpClient.dele(new String(filename.getBytes(StandardCharsets.UTF_8), StandardCharsets.ISO_8859_1));

问题:获取上传类型为 MultipartFile的文件名

解决方案:fileName = file.getOriginalFilename();

问题:查询FTP目录是否存在

解决方案:调用ftpClient.makeDirectory(目录地址); 返回结zd果true或false,true代表创建成功,即目录不存在,false表示创建失败,表示该路径已经存在了。

问题:doc转PDF后中文不显示,只显示英文

解决方案:将windows系统下的中文字体文件(C:\Windows\Fonts),放到/usr/share/fonts下,必须重启openoffice。

问题:上传时,报错500 Illegal PORT command

原因:是因为上传时,被动模式没有开启

解决方案:将FTPClient设置为被动模式:ftpClient.enterLocalPassiveMode(); 即可解决

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章