Spring WEB工程整合使用FTP,ftp文本文件解析入庫(kù),文件上傳下載

友情提示:文章比較長(zhǎng),方法都是有一層層封裝的,閱讀需要按照文章順序閱讀

首先寫(xiě)一個(gè)簡(jiǎn)單的FTP工具類(lèi),先實(shí)現(xiàn)最基本的文件上傳,下載,刪除,拷貝功能。這里操作FTP是用的commons-net-3.3.jar中的org.apache.commons.net.ftp中的對(duì)象

package com.wzh.config.utils;

import org.apache.commons.net.ftp.FTP;
import org.apache.commons.net.ftp.FTPClient;
import org.apache.commons.net.ftp.FTPFile;
import org.apache.commons.net.ftp.FTPReply;
import org.apache.log4j.Logger;

import java.io.*;
import java.nio.charset.Charset;
import java.util.ArrayList;
import java.util.List;

/**
 * @author wzh
 * @create 2018-05-06 23:03
 * @desc ${操作FTP文件工具類(lèi)}
 **/
public class FtpUtils {

    private static Logger log = Logger.getLogger(FtpUtils.class);

    //本地編碼集對(duì)象
    private static String encode = Charset.defaultCharset().toString();

    // FTP編碼為iso-8859-1
    private static final String SERVER_CHARSET = "ISO-8859-1";

    //FTP下載時(shí)讀入內(nèi)存的大小
    private static final int BUFFER_SIZE = 1024000;


    /**
     * 獲取FTP連接對(duì)象,連接FTP成功返回FTP對(duì)象,
     * 連接FTP失敗超過(guò)最大次數(shù)返回null,使用前請(qǐng)判斷是否為空
     * @param ftpHost 服務(wù)器ip
     * @param ftpPort 服務(wù)器端口
     * @param ftpUserName 用戶(hù)名
     * @param ftpPassword 密碼
     * @return FTPClient FTP連接對(duì)象
     */
    public static FTPClient getFTPClient(String ftpHost, int ftpPort, String ftpUserName, String ftpPassword) {

        //FTP連接對(duì)象
        FTPClient ftpClient = null;

        try
        {
            ftpClient = new FTPClient();
            //設(shè)置FTP服務(wù)器IP和端口
            ftpClient.connect(ftpHost,ftpPort);
            //設(shè)置超時(shí)時(shí)間,毫秒
            ftpClient.setConnectTimeout(50000);
            //登錄FTP
            ftpClient.login(ftpUserName,ftpPassword);

            //設(shè)置被動(dòng)傳輸模式
            ftpClient.enterLocalPassiveMode();
            //ftpClient.enterRemotePassiveMode();
            //二進(jìn)制傳輸
            ftpClient.setFileType(FTP.BINARY_FILE_TYPE);
            //設(shè)置讀入內(nèi)存文件大小
            ftpClient.setBufferSize(BUFFER_SIZE);

            //獲取FTP連接狀態(tài)碼 ,大于等于200 小于300狀態(tài)碼表示連接正常
            int connectState = ftpClient.getReplyCode();

            //連接失敗重試
            int reNum = 0;
            while (!FTPReply.isPositiveCompletion(connectState)
                    && reNum < 3)
            {
                ftpClient.disconnect();
                ++reNum;
                ftpClient.login(ftpUserName,ftpPassword);

            }
            if (reNum < 3) {

                log.info("FTP連接成功");

            } else {
                ftpClient = null;
                log.error("FTP連接失敗");
            }

        } catch (Exception e)
        {

            log.error(e.getMessage(), e);
        }

        return ftpClient;
    }

    /**
     *斷開(kāi)FTP
     * @param ftpClient fpt連接對(duì)象
     */
    public static void closeFTP(FTPClient ftpClient) {

        if (null != ftpClient) {
            try {
                //登出FTP
                ftpClient.disconnect();
                log.info("登出FTP成功");
            } catch (IOException e) {
                log.error(e.getMessage(), e);
            } finally {
                try {
                    //斷開(kāi)FTP
                    ftpClient.disconnect();
                    log.info("斷開(kāi)FTP成功");
                } catch (IOException e) {
                    log.error(e.getMessage(), e);
                }
            }

        }
    }


    /**
     * 根據(jù)FTP編碼集轉(zhuǎn)換文件路徑,防止中文亂碼,并設(shè)置FTP連接的編碼集
     * @param ftpClient FTP連接對(duì)象
     * @param path 文件路徑
     * @return 轉(zhuǎn)碼后的文件路徑
     */
    public static String changeEncode(FTPClient ftpClient,String path) throws IOException
    {
        synchronized (encode)
        {
            int status = ftpClient.sendCommand("OPTS UTF8","ON");
            //判斷FTP服務(wù)器是否支持UTF -8,支持使用UTF-8 否則使用本地編碼集
            if(FTPReply.isPositiveCompletion(status))
            {
                encode = "UTF-8";
            }

            log.info("FTP使用編碼集:" + encode);

            ftpClient.setControlEncoding(encode);
            path = new String(path.getBytes(encode),SERVER_CHARSET);
        }

        return path;
    }

    /**
     * 獲取文件后綴
     * @param fileName 文件名或文件全路徑
     * @return 文件后綴
     */
    public static String getSuffix(String fileName)
    {
        String suffix = "";

        int index = fileName.lastIndexOf(".");
        if(index != -1)
        {
            suffix = fileName.substring(index);
            log.info("獲取文件后綴名成功,文件名:" + fileName + " 后綴名:" + suffix);
        }else{
            log.warn("獲取文件后綴名失敗,文件名" + fileName);
        }
        return suffix;
    }

    /**
     * 獲取FTP指定文件大小
     * @param ftpClient ftp連接對(duì)象
     * @param fileName ftp文件服務(wù)器路徑 如:/public/file/xxx.text
     * @return 文件大小,獲取失敗返回-1
     */
    public static Long getFtpFileSize(FTPClient ftpClient, String fileName)
    {
        FTPFile[] files = null;
        Long fileSize = -1L;
        try {

            files = ftpClient.listFiles(changeEncode(ftpClient,fileName));

            //因?yàn)橹付司唧w的文件名,這里只取數(shù)組0位
            if (null != files && files.length > 0)
            {
                log.info("文件個(gè)數(shù):" + files.length + " 文件名:" +
                        files[0].getName() + " 文件大?。? + files[0].getSize());

                fileSize = files[0].getSize();
            }
        } catch (IOException e) {

            fileSize = 1L;
            log.error(e.getMessage(),e);
        }

        return fileSize;
    }

    /**
     *下載FTP上指定文件路徑文件
     * @param ftpClient FTP連接對(duì)象
     * @param filePath 文件路徑+文件名 例如:/public/file/a.txt
     * @param downPath 下載文件保存的路徑
     * @param newFileName 新的文件名 例如:newFileName
     * @return
     */
    public static boolean downLoadFtpFile(FTPClient ftpClient, String filePath,String downPath, String newFileName)
    {
        //默認(rèn)失敗
        boolean flag = false;

        //獲取文件后綴
        String suffix = getSuffix(filePath);

        //下載的文件對(duì)象
        File dwonFile = new File(downPath + File.separator + newFileName + suffix);
        try
        {
            OutputStream out = new FileOutputStream(dwonFile);
            flag = ftpClient.retrieveFile(changeEncode(ftpClient,filePath),out);
            out.flush();
            out.close();

            if(flag)
            {
                log.info("下載文件成功,文件路徑:" + filePath);
            }else{
                log.error("下載文件失敗,文件路徑:" + filePath);
            }
        }
        catch (Exception e)
        {
            log.error(e.getMessage(),e);
        }

        return flag;
    }

    /**
     * FTP文件上傳工具類(lèi)
     * @param ftpClient 連接對(duì)象
     * @param filePath 本地文件路徑 /xxxx/xx.txt
     * @param ftpPath ftp儲(chǔ)存路徑
     * @param newFileName ftp保存的文件名
     * @return true 下載成功,false 下載失敗
     */
    public static boolean uploadFile(FTPClient ftpClient,String filePath,String ftpPath, String newFileName)
    {
        boolean flag = false;

        InputStream in = null;

        try {
            //獲取文件后綴
            String suffix = getSuffix(filePath);

            //路徑轉(zhuǎn)碼,處理中文
            ftpPath = changeEncode(ftpClient,ftpPath);
            newFileName = changeEncode(ftpClient,newFileName + suffix);

            //判斷目標(biāo)文件夾是否存在,不存在就創(chuàng)建
            if(!ftpClient.changeWorkingDirectory(ftpPath))
            {
                ftpClient.makeDirectory(ftpPath);
                ftpClient.changeWorkingDirectory(ftpPath);
            }

            //上傳文件
            File file = new File(filePath);
            in = new FileInputStream(file);
            flag = ftpClient.storeFile(newFileName,in);
            if(flag)
            {
                log.info("文件上傳成功:" + filePath);
            }
        }
        catch (Exception e)
        {
            log.error(e.getMessage(),e);
        }
        finally
        {
            try {
                if(in != null)
                {
                    in.close();
                }
            } catch (IOException e) {
                log.error(e.getMessage(),e);
            }

        }

        return flag;
    }

    /**
     * FTP上文復(fù)制文件到另外一個(gè)路徑
     * @param ftpClient ftp連接對(duì)象
     * @param oldFtpPath 源文件儲(chǔ)存路徑 xxx/xxx.txt
     * @param newFtpPath 新路徑 /public/file/
     * @param newFileName 新文件名
     * @return true 下載成功,false 下載失敗
     */
    public static boolean copyFile(FTPClient ftpClient,String oldFtpPath,String newFtpPath, String newFileName)
    {
        boolean flag = false;

        ByteArrayInputStream in = null;
        ByteArrayOutputStream out = null;

        try {
            out = new ByteArrayOutputStream();

            //獲取文件后綴
            String suffix = getSuffix(oldFtpPath);

            //先讀入內(nèi)存,綁定out輸出流,然后再轉(zhuǎn)換為輸入流
            String encodeOldPath = changeEncode(ftpClient,oldFtpPath);
            ftpClient.retrieveFile(encodeOldPath,out);
            in = new ByteArrayInputStream(out.toByteArray());

            //切換工作目錄,沒(méi)有就創(chuàng)建
            String encodeNewPath = changeEncode(ftpClient,newFtpPath);
            if(!ftpClient.changeWorkingDirectory(encodeNewPath))
            {
                ftpClient.makeDirectory(encodeNewPath);
                ftpClient.changeWorkingDirectory(encodeNewPath);
            }

            //復(fù)制文件
            flag = ftpClient.storeFile(changeEncode(ftpClient,newFileName + suffix),in);
            out.flush();
            out.close();
            in.close();
            if (flag) {
                log.info("文件復(fù)制成功,源文件:" + oldFtpPath + " 新路徑:" + newFtpPath + newFileName);
            } else {
                throw new BusinessException("文件復(fù)制失敗,源文件:" + oldFtpPath);
            }

        }
        catch (Exception e)
        {
            log.error(e.getMessage(),e);
        }

        return flag;
    }

    /**
     * 刪除Ftp上的文件
     * @param ftpClient 連接對(duì)象
     * @param filePath 服務(wù)器文件路徑 /public/file/xxx.txt
     * @return true 成功,false 失敗
     */
    public static boolean delectFile(FTPClient ftpClient,String filePath)
    {
        boolean flag = false;

        try {
            flag = ftpClient.deleteFile(changeEncode(ftpClient,filePath));
            if(flag)
            {
                log.info("刪除文件成功:" + filePath);
            }else{
                log.error("刪除文件失?。? + filePath);
            }
        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }

        return flag;
    }

    /**
     * 文件移動(dòng)
     * @param ftpClient fpt連接對(duì)象
     * @param oldFtpPath 文件原路徑 /public/old/xxx.txt
     * @param newFtpPath 文件新路徑 /public/new/
     * @param newFileName 文件名
     * @return true 成功,false 失敗
     */
    public static boolean moveFile(FTPClient ftpClient,String oldFtpPath,String newFtpPath, String newFileName)
    {
        boolean flag = false;

        try {
            //文件后綴
            String suffix = getSuffix(oldFtpPath);
            //路徑編碼
            String encodeOldPath = changeEncode(ftpClient,oldFtpPath);
            String encodeNewPath = changeEncode(ftpClient,newFtpPath);
            String encodeNewFileName = changeEncode(ftpClient,newFileName + suffix);

            //切換工作目錄
            if(!ftpClient.changeWorkingDirectory(encodeNewPath))
            {
                ftpClient.makeDirectory(encodeNewPath);
                ftpClient.changeWorkingDirectory(encodeNewPath);
            }

            //轉(zhuǎn)存
            flag = ftpClient.rename(encodeOldPath, encodeNewFileName);
            if(flag)
            {
                log.info("文件轉(zhuǎn)存成功:" + oldFtpPath);
            }else {
                log.error("文件轉(zhuǎn)存失?。? + oldFtpPath);
            }

        } catch (IOException e) {
            log.error(e.getMessage(),e);
        }
        return flag;
    }

    /**
     * 讀取Ftp文本文件,返回行數(shù)據(jù)集合
     * @param ftpClient ftp連接對(duì)象
     * @param filePath 文件路徑 /public/file/xxx.txt
     * @param encode 解析文件編碼集
     * @return 行數(shù)據(jù)集合
     */
    public static List<String> redFtpFileWithLine(FTPClient ftpClient, String filePath, String encode)
    {
        List<String> lineList = new ArrayList<String>();
        InputStream in = null;
        BufferedReader reader = null;

        try {
            //獲取文件流數(shù)據(jù)
            in = ftpClient.retrieveFileStream(changeEncode(ftpClient,filePath));
            if(in == null)
            {
                throw new BusinessException("獲取文件流失?。? + filePath);
            }
            reader = new BufferedReader(new InputStreamReader(in,encode));
            String inLine;
            while ((inLine = reader.readLine()) != null)
            {
                lineList.add(inLine);
            }

            //關(guān)閉流
            if(reader != null)
            {
                reader.close();
            }
            in.close();
            /*
            retrieveFileStream使用了流,需要釋放一下,不然會(huì)返回null
            方法一:主動(dòng)調(diào)用一次getReply()把接下來(lái)的226消費(fèi)掉
            方法二:主動(dòng)調(diào)用一次completePendingCommand(),把流釋放掉
             */
            ftpClient.getReply();

        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }


        return lineList;
    }

    /**
     * 獲取文件輸出流
     * @param ftpClient ftp連接對(duì)象
     * @param filePath 文件路徑
     * @param out 文件輸出流
     */
    public void readFileWithOutputStream(FTPClient ftpClient, String filePath, OutputStream out)
    {
        try {
            if(out == null)
            {
                throw new BusinessException("輸出流為null");
            }

            ftpClient.retrieveFile(filePath,out);

            out.flush();
            out.close();

        } catch (Exception e) {
            log.error(e.getMessage(),e);
        }
    }

}


上面只是最簡(jiǎn)單的,可以在本地操作ftp上傳下載,在web里面還是有些區(qū)別。這里既然是通過(guò)spring進(jìn)行操作,我們不妨在做一些封裝,簡(jiǎn)化在項(xiàng)目中的操作。例如我們操作FTP的時(shí)候,必要的東西例如,ip,端口,賬號(hào),密碼,這些都是很少進(jìn)行變化的,但是又是在項(xiàng)目中經(jīng)常用的,這里提供一個(gè)思路,可以在項(xiàng)目啟動(dòng)的時(shí)候,把這些信息加載到內(nèi)存或者緩存中。

FtpBean對(duì)象,這里展示是省略了get set那些方法的。

package com.wzh.config.framework.domain;


import org.apache.ibatis.type.Alias;

/**
 * @author wzh
 * @create 2018-05-27 20:14
 * @desc ${ftp 對(duì)象,用于存儲(chǔ)ftp賬戶(hù)信息}
 **/
@Alias("ftpBean")
public class FtpBean {

    /**
     * fpt別名
     */
    private String ftpName;

    /**
     * ftp服務(wù)器ip
     */
    private String ftpHost;

    /**
     * ftp服務(wù)器端口
     */
    private String ftpPort;

    /**
     * 賬號(hào)名
     */
    private String ftpUserName;

    /**
     * 密碼
     */
    private String ftpPassword;

Spring 在項(xiàng)目啟動(dòng)的時(shí)候加載一些從數(shù)據(jù)庫(kù)中查詢(xún)的常量方式很多,可以在xml中配置bean,然后寫(xiě)init方法,也可以使用注解,或者繼承某些類(lèi)。這篇博文寫(xiě)得比較詳細(xì),想詳細(xì)了解的可以看下這篇文章https://blog.csdn.net/honghailiang888/article/details/73333821
因?yàn)檎系膁emo是基于SpringBoot做的,boot中提倡少xml配置文件,所以這里提供一種基于@PostConstruct注解的方式,加載了此注解的方法,會(huì)在Spring啟動(dòng)完成后第一時(shí)間執(zhí)行。

package com.wzh.config.framework.frameworkInit;

import com.wzh.config.framework.domain.FtpBean;
import com.wzh.config.framework.service.InitFrameWorkConstantService;
import com.wzh.demo.dao.UserDao;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;

import javax.annotation.PostConstruct;
import javax.annotation.Resource;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wzh
 * @create 2018-05-21 23:58
 * @desc ${系統(tǒng)加載時(shí)初始化常量}
 **/
@Component
public class InitConstant  {

    /**
     * ftp賬號(hào)信息對(duì)象,靜態(tài),加載到內(nèi)存中
     */
    private static Map<String,Object> ftpInfoMap;

    public static Map<String, Object> getFtpInfoMap() {
        return ftpInfoMap;
    }

    @Resource
    @Qualifier(value = "initFrameWorkConstantService")
    private InitFrameWorkConstantService initFrameWorkConstantService;

    /**
     * 初始化ftp賬號(hào)信息對(duì)象
     */
    @PostConstruct
    public void initFtpInfo()
    {
        ftpInfoMap = new HashMap<String, Object>();

        List<FtpBean> ftpList = initFrameWorkConstantService.initFtpInfo();
        if(!ftpList.isEmpty())
        {
            for (FtpBean bean : ftpList)
            {
                //鍵值對(duì)方式存放,key為ftp的別名,方便取
                ftpInfoMap.put(bean.getFtpName(),bean);
            }
        }
    }
}

把之前的FtpUtils再進(jìn)行一下封裝,這里特別說(shuō)一下,部分方法沒(méi)有測(cè)試,只是單純的寫(xiě)了,如果拿來(lái)實(shí)際使用,需要再測(cè)試一下。這里先把文件在線解析,上傳下載,文件流的方法單獨(dú)說(shuō)明,其他的方法就不一一演示了,在文末會(huì)把代碼貼出來(lái)。

在項(xiàng)目中有這么一個(gè)場(chǎng)景,就是用戶(hù)上傳實(shí)體文件入庫(kù),有excel的,這種場(chǎng)景一般是在用戶(hù)端直接頁(yè)面操作,解析后入庫(kù),還有一種是跨平臺(tái)實(shí)體文件同步,有的時(shí)候有某些大批量的數(shù)據(jù)需要跨平臺(tái)同步,如果直接通過(guò)接口的方式調(diào)用,文件條數(shù)如果是幾十萬(wàn),接口性能并不高,這個(gè)時(shí)候可以通過(guò)ftp文件服務(wù)器的方式同步,大體有兩種,第一種是接口主動(dòng)通知服務(wù)端,文件已經(jīng)放到ftp服務(wù)器上,還有一種就是定時(shí)任務(wù)固定時(shí)間掃碼目錄,兩種方式只是觸發(fā)機(jī)制不一樣,當(dāng)時(shí)處理邏輯都是相同的。

文本文件解析方法,這里為了作為通用方法,用了下泛型和反射

    /**
     * 解析txt實(shí)體文件并轉(zhuǎn)換為對(duì)應(yīng)的list集合,如果沒(méi)有分隔符,用String接收
     * 需注意實(shí)體類(lèi)與字符串拆分后的順序需相同,排除final 屬性不進(jìn)行設(shè)置值
     * 其實(shí)還有一種方案就是可以用xml等配置文件進(jìn)行配置文件映射,屬性類(lèi)型,這里為了簡(jiǎn)單就直接要求順序相同
     * @param ftpName          ftp別名
     * @param filePath         服務(wù)器文件路徑
     * @param encode           文件編碼集
     * @param regex            文件數(shù)據(jù)分隔符
     * @param obj              解析映射的對(duì)象
     * @param simpleDateFormat 解析映射的對(duì)象
     * @param <T>              對(duì)象泛型
     * @return 返回解析后的集合
     */
    public <T> List<T> readFileWithLine(String ftpName, String filePath, String encode, String regex,
                                        String simpleDateFormat,T obj) throws Exception{

        //解析后返回的數(shù)據(jù)集合
        List<T> clazzes = new ArrayList<T>();

        FTPClient ftpClient = linkFtp(ftpName);

        if(null != ftpClient)
        {
            List<String> info = FtpUtils.redFtpFileWithLine(ftpClient, filePath, encode);
            if(null != info && !info.isEmpty())
            {
                if(StringUtils.isBlank(regex))
                {
                    // 無(wú)分隔符,判斷為String 集合
                    clazzes.addAll((Collection<? extends T>) info);

                }else {

                    for(String str : info)
                    {
                        // 拆分行數(shù)據(jù)
                        String [] line = str.split(regex);

                        //因?yàn)镴DK用的1.9 所以沒(méi)有直接newInstance,如果是低版本的jdk 可以直接getClass().newInstance
                        T t = (T) obj.getClass().getDeclaredConstructor().newInstance();
                        // 獲取文件屬性數(shù)組
                        Field[] fielders = t.getClass().getDeclaredFields();

                        // 循環(huán)排除final屬性
                        List<Field> fieldList = new ArrayList<Field>();
                        for(Field cell : fielders)
                        {
                            if(!Modifier.isFinal(cell.getModifiers()))
                            {
                                // 非final的屬性才進(jìn)行處理
                                fieldList.add(cell);
                            }
                        }

                        // 數(shù)據(jù)和對(duì)象映射要求完全對(duì)應(yīng),所以這里取對(duì)象下標(biāo)
                        for(int i = 0; i < fieldList.size(); i++)
                        {
                            Field field = fieldList.get(i);
                            // 設(shè)置權(quán)限
                            field.setAccessible(true);

                            // 判斷數(shù)據(jù)類(lèi)型進(jìn)行轉(zhuǎn)換,這里只做了幾種常見(jiàn)類(lèi)型的判斷,如果有需要可以繼續(xù)添加
                            String type = field.getType().getName();
                            try {

                                if("java.lang.Integer".equals(type) || "int".equals(type))
                                {
                                    field.set(t,NumberUtils.toInt(line[i]));
                                }
                                else if("java.lang.Double".equals(type) || "double".equals(type))
                                {
                                    field.set(t,NumberUtils.toDouble(line[i]));
                                }
                                else if("java.lang.Float".equals(type) || "float".equals(type))
                                {
                                    field.set(t,NumberUtils.toFloat(line[i]));
                                }
                                else if("java.lang.Long".equals(type) || "long".equals(type))
                                {
                                    field.set(t,NumberUtils.toLong(line[i]));
                                }
                                else if("java.lang.Short".equals(type) || "short".equals(type))
                                {
                                    field.set(t,NumberUtils.toShort(line[i]));
                                }
                                else if("java.lang.Boolean".equals(type) || "boolean".equals(type))
                                {
                                    field.set(t, BooleanUtils.toBoolean(line[i]));
                                }
                                else if("java.util.Date".equals(type) || "Date".equals(type))
                                {
                                    SimpleDateFormat sdf=new SimpleDateFormat(simpleDateFormat);
                                    if(StringUtils.isBlank(line[i]))
                                    {
                                        field.set(t, null);
                                    }else{
                                        field.set(t, sdf.parse(line[i]));
                                    }
                                }
                                else {
                                    field.set(t, line[i]);
                                }

                            }catch (Exception e){
                                log.error(e.getMessage(),e);
                            }

                        }

                        // 添加數(shù)據(jù)
                        clazzes.add(t);
                    }
                }

            }

        }
        return clazzes;
    }

測(cè)試一下,首先弄一個(gè)實(shí)體文件


image.png

一個(gè)實(shí)體類(lèi)User,省略get set 方法

package domin;

import java.util.Date;

/**
 * <一句話功能描述>
 * <功能詳細(xì)描述>
 *
 * @author wzh
 * @version 2018-06-18 16:43
 * @see [相關(guān)類(lèi)/方法] (可選)
 **/
public class User {

    private String name;

    private int age;

    private Date birthday;

    public User() {
        super();
    }
}

junit 測(cè)試

import base.BaseJunit;
import com.wzh.config.utils.FtpManagerUtils;
import domin.User;
import org.junit.Ignore;
import org.junit.Test;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;

import java.util.Date;
import java.util.List;

/**
 * <一句話功能描述>
 * <功能詳細(xì)描述>
 * @author wzh
 * @version 2018-06-18 16:16
 * @see [相關(guān)類(lèi)/方法] (可選)
 **/
public class ftpTest extends BaseJunit {
    @Autowired
    @Qualifier(value = "ftpManagerUtils")
    private FtpManagerUtils ftpManagerUtils;

    @Test
    public void readTextTest()
    {

        User user = new User();
        try {
            //對(duì)象文本文件
            List<User> list = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM",
                    "/file/userinfo.txt","utf-8","\\|","yyyy-MM-dd",user);

            //字符串集合文件文件
            List<String> strlist = ftpManagerUtils.readFileWithLine("FTP_USER_SYSTEM",
                    "/file/userinfo.txt","utf-8","","yyyy-MM-dd",new String());
            System.out.println(list);
            System.out.println(strlist);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }
}

通過(guò)截圖我們可以看到不管是字符串接收行數(shù)據(jù)還是對(duì)象接收,都成功解析了,這個(gè)時(shí)候就可以根據(jù)自身的業(yè)務(wù)邏輯進(jìn)行處理入庫(kù)等操作了。


image.png

在文件上傳服務(wù)器這一塊,處理的思路一般有兩種,如果項(xiàng)目比較小,就直接上傳到工程目錄下,或者把工程目錄掛載出去,把其他文件服務(wù)器的磁盤(pán)掛到工程目錄下的文件服務(wù)器。還有一種就是專(zhuān)門(mén)的文件服務(wù)器,所有的文件都上傳到文件服務(wù)器,這里做一個(gè)用戶(hù)端上傳文件后直接把文件上傳到ftp服務(wù)器上的處理方式。

文件上傳工具類(lèi),這里是用戶(hù)上傳后直接上傳ftp的場(chǎng)景,如果是先存工程,再傳服務(wù)器,可以用另外一個(gè)方法

    /**
     * 上傳文件到ftp服務(wù)器
     * @param ftpName     ftp別名
     * @param file        文件對(duì)象
     * @param ftpPath     ftp 服務(wù)器保存路徑
     * @param newFileName 保存的文件名
     * @return 上傳是否成功過(guò)
     */
    public boolean upLoadFile(String ftpName, MultipartFile file, String ftpPath, String newFileName){
        //默認(rèn)失敗
        boolean flag = false;

        FTPClient ftpClient = linkFtp(ftpName);
        InputStream in = null;

        if (null != ftpClient) {
            try {
                // 獲取文件名
                String fileName = file.getOriginalFilename();

                // 獲取文件后綴名
                String suffix = FtpUtils.getSuffix(fileName);

                // 路徑轉(zhuǎn)碼,處理中文
                ftpPath = FtpUtils.changeEncode(ftpClient,ftpPath);
                newFileName = FtpUtils.changeEncode(ftpClient,newFileName + suffix);

                // 判斷目標(biāo)文件夾是否存在,不存在就創(chuàng)建
                if(!ftpClient.changeWorkingDirectory(ftpPath))
                {
                    ftpClient.makeDirectory(ftpPath);
                    ftpClient.changeWorkingDirectory(ftpPath);
                }
                in = file.getInputStream();
                flag = ftpClient.storeFile(newFileName,in);
                if(flag)
                {
                    log.info("文件上傳成功:" + fileName);
                }

            }catch (Exception e)
            {
                log.error("文件上傳失?。? + e.getMessage(),e);

            }finally
            {
                try {
                    if(in != null)
                    {
                        in.close();
                    }
                } catch (IOException e) {
                    log.error(e.getMessage(),e);
                }

                // 關(guān)閉連接
                FtpUtils.closeFTP(ftpClient);
            }

        }

        return flag;
    }

一個(gè)頁(yè)面,這里用的ftl

<#import "spring.ftl" as spring />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="${request.contextPath }/ftp/upload.do" method="POST" enctype="multipart/form-data">
        文件:<input type="file" name="file"/>
             <input type="submit"/>
    </form>
</body>
</html>

一個(gè)簡(jiǎn)單的controller ,里面有把MultipartFile 轉(zhuǎn)換為File 其實(shí)方法也可以直接傳輸入流,這里沒(méi)過(guò)多糾結(jié),如果需要流的場(chǎng)景可以重載寫(xiě)一個(gè)

package com.wzh.demo.controller;

import com.wzh.config.utils.FtpManagerUtils;
import com.wzh.config.utils.FtpUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.multipart.MultipartFile;

import javax.annotation.Resource;
import java.io.File;
import java.io.IOException;
import java.util.UUID;

/**
 * <ftp文件上傳控制器>
 * <功能詳細(xì)描述>
 *
 * @author wzh
 * @version 2018-06-18 17:55
 * @see [相關(guān)類(lèi)/方法] (可選)
 **/
@Controller
@RequestMapping("/ftp")
public class FtpController {

    @Autowired
    @Qualifier("ftpManagerUtils")
    private FtpManagerUtils ftpManagerUtils;

    @RequestMapping(value = "/upload.do",method = RequestMethod.GET)
    public String toFileUpload()
    {
        return "/test/fileUpload";
    }

    @RequestMapping(value = "/upload.do", method = RequestMethod.POST)
    public String FileUpload(@RequestParam("file") MultipartFile file)
    {
        String suffix = FtpUtils.getSuffix(file.getOriginalFilename());

        //根據(jù)自身業(yè)務(wù)做處理重命名
        String newFileName = UUID.randomUUID().toString().replace("-", "");

        //文件上傳
        ftpManagerUtils.upLoadFile("FTP_USER_SYSTEM",file,"/file/test/",newFileName);


        return "/test/fileUpload";
    }
}

頁(yè)面上傳


image.png

去服務(wù)器查看,有uuid重命名的文件,切能正常打開(kāi)


image.png

用戶(hù)頁(yè)面操作,從FTP下載文件,這個(gè)沒(méi)什么特別好說(shuō)的,一般這用用在應(yīng)用服務(wù)器數(shù)據(jù)庫(kù)中只存儲(chǔ)了用戶(hù)的文件名和基本路徑,服務(wù)器放在文件服務(wù)器上,當(dāng)用戶(hù)需要下載的時(shí)候,從ftp下載文件。

文件下載工具類(lèi),這里就沒(méi)有寫(xiě)特別復(fù)雜,就直接流的方式就可以了

/**
     * 獲取文件流
     * @param ftpName  ftp別名
     * @param filePath ftp服務(wù)器上文件路徑
     * @param out      輸出流
     */
    public void readFileWithOutputStream(String ftpName, String filePath, OutputStream out)
    {
        FTPClient ftpClient = linkFtp(ftpName);

        if(null != ftpClient)
        {

            // 綁定輸出流
            FtpUtils.readFileWithOutputStream(ftpClient, filePath, out);

            // 關(guān)閉連接
            FtpUtils.closeFTP(ftpClient);
        }
    }

一個(gè)簡(jiǎn)單的頁(yè)面

<#import "spring.ftl" as spring />
<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
        "http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
    <title>Title</title>
</head>
<body>
    <form action="${request.contextPath }/ftp/down.do" method="POST" enctype="multipart/form-data">
        文件:<input type="text" name="fileName"/>
             <input type="submit"/>
    </form>
</body>
</html>

controller控制器

 @RequestMapping(value = "/down.do",method = RequestMethod.GET)
    public String toFileDown()
    {

        return "test/fileDown";
    }

    @RequestMapping(value = "/down.do", method = RequestMethod.POST)
    public String fileDown(HttpServletResponse response, String fileName) {
        try {
            // 設(shè)置返回編碼及瀏覽器響應(yīng)類(lèi)型
            response.setCharacterEncoding("UTF-8");
            response.setContentType("multipart/form-data;charset=UTF-8");

            //獲取文件后綴名
            String suffix = FtpUtils.getSuffix(fileName);

            //根據(jù)自身業(yè)務(wù)做處理重命名
            String downName = UUID.randomUUID().toString().replace("-", "") + suffix;

            response.setHeader("Content-Disposition", "attachment;fileName=" + downName);


            //文件下載,大多數(shù)業(yè)務(wù)邏輯都是頁(yè)面?zhèn)鬟f文件名或ID,通過(guò)數(shù)據(jù)庫(kù)或其他文件查詢(xún)出具體文件路徑,這里為了測(cè)試,寫(xiě)死
            ftpManagerUtils.readFileWithOutputStream("FTP_USER_SYSTEM", "/file/test/" + fileName,
                    response.getOutputStream());
        } catch (Exception e) {
            e.printStackTrace();
        }

        return "test/fileDown";
    }

測(cè)試一下,在代碼中也有說(shuō)明,正常的下載是文件名傳遞,路徑后臺(tái)控制,這里為了測(cè)試,寫(xiě)死了


image.png

點(diǎn)擊查詢(xún)下載成功,文件也能正常打開(kāi)


image.png

這里大概就是spring 整合ftp的常用操作,寫(xiě)得比較長(zhǎng),常見(jiàn)的功能都實(shí)現(xiàn)了,這里再次說(shuō)明,很多方式?jīng)]有經(jīng)過(guò)嚴(yán)格的測(cè)試,如果需要在項(xiàng)目中使用,需要再次測(cè)試。

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時(shí)請(qǐng)結(jié)合常識(shí)與多方信息審慎甄別。
平臺(tái)聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點(diǎn),簡(jiǎn)書(shū)系信息發(fā)布平臺(tái),僅提供信息存儲(chǔ)服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容