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(); 即可解決

 

 

 

 

 

 

 

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