基於MFT文件上傳和下載

1. MFT介紹

Managed File Transfer (“MFT”)是一種安全的數據傳輸軟件,是通過網絡從一臺計算機到另一臺計算機的數據傳輸。

大文件傳輸(MFT)是一種安全的數據傳輸軟件,是通過網絡從一臺計算機到另一臺計算機的數據傳輸。MFT軟件通常基於FTP網路協議。而且,MFT也可彌補FTP的缺陷。

2. 業務背景

2.1 業務流程

做的項目需要與A銀行系統對接,在文件傳輸這塊,A銀行用的是MFT這種方式,並且做了一定程度的封裝。大致流程是:

業務流程

2.2 文件上傳

  1. 我們需要在我們服務器上搭建MFT的客戶端,A銀行會給我們分配一個 USERID ,這個UESRID會指向A銀行在自己MFT服務器上爲我們分配的文件目錄;

  2. 在本地服務器上創建上傳目錄,將要上傳的文件放在該目錄下;

  3. 執行A銀行提供的shell腳本,上傳腳本內容如下

    /home/Axway/Synchrony/SecureClient/sclient script /home/Axway/Synchrony/SecureClient/put
    

    前面腳本都是固定的(MFT客戶端安裝目錄,可配置成環境變量),核心在於put文件(路徑可自定義),put文件示例:

    open ABank
    lcd /home/qy_work/cebfile/upload
    cd USERID
    newjob
    put 20181107.txt
    jobsubmit
    close
    

    其中 /home/qy_work/cebfile/upload 是文件上傳目錄,USERID爲銀行分配的唯一標識,20181107.txt是需要上傳的文件,這裏可以上傳多個文件;

  4. 上傳後返回信息:

    Checking transfer engine status on port 1717: is running
    open cebbank
    Connected
    lchdir /home/qy_work/cebfile/upload
    current working directory: /home/qy_work/cebfile/upload
    chdir USERID
    Directory changed
    newjob
    put 20181107.txt
    inserted in current job
    jobsubmit
    Job submitted: 20
    Close
    Disconnected
    

    這裏需要注意的是Job submitted: 20,這裏稱爲JOBID,用來查詢上傳狀態;

  5. 查詢上傳文件狀態信息:

    /home/Axway/Synchrony/SecureClient/sclientadm displayjob -id JOBID
    
  6. 查詢上傳文件狀態返回信息:

    Ident                            = 20
    Description                      = 
    Site Alias                       = ABank
    Creation Date                    = 2018/11/07 16:25:59
    Start Date                       = 2018/11/07 16:25:59
    End Date                         = 2018/11/07 16:25:59
    Status                           = Finished
    Error Message                    = 
    Percent                          = 100 %
    Total size                       = 20 B (20 bytes)
    Tasks count                      = 1
    Current Task                     = 0
    Connection Retry Count           = 0
    Transfer Retry Count             = 0
    Stop On Error                    = false
    Update Frequency                 = 5000
    

    重點在於 statuspercent兩個字段對應信息,前者標識文件上傳狀態,後者標識文件上傳百分比。

    至此,基於MFT文件上傳的流程全部描述完。接下來說說文件下載的流程;

2.3 文件下載

  1. 文件下載同樣也需要執行A銀行提供的腳本,下載腳本如下:

    /home/Axway/Synchrony/SecureClient/sclient script /home/Axway/Synchrony/SecureClient/get
    

    前面腳本都是固定的(MFT客戶端安裝目錄,可配置成環境變量),核心在於get文件(路徑可自定義),get文件示例:

    open cebbank
    lcd /home/qy_work/cebfile/download
    cd USERID
    newjob
    get 2018110702.txt
    jobsubmit
    close
    

    可以發現,和put腳本區別主要在於putget命令區別,其中 /home/qy_work/cebfile/download 是文件下載目錄,USERID爲銀行分配的唯一標識,20181107.txt是需要下載的文件,這裏可以下載多個文件;

  2. 下載後返回信息:

    Checking transfer engine status on port 1717: is running
    open ABank
    Connected
    lchdir /home/qy_work/cebfile/download
    current working directory: /home/qy_work/cebfile/download
    chdir USERID
    Directory changed
    newjob
    get 2018110702.txt
    inserted in current job
    jobsubmit
    Job submitted: 23
    Close
    Disconnected
    

    這裏需要注意的是Job submitted: 20,這裏稱爲JOBID,用來查詢下載狀態;

  3. 查詢上傳文件狀態信息:

    /home/Axway/Synchrony/SecureClient/sclientadm displayjob -id JOBID
    
  4. 查詢上傳下載狀態返回信息:

    Ident                            = 23
    Description                      = 
    Site Alias                       = ABank
    Creation Date                    = 2018/11/07 17:30:14
    Start Date                       = 2018/11/07 17:30:14
    End Date                         = 2018/11/07 17:30:14
    Status                           = Finished
    Error Message                    = 
    Percent                          = 100 %
    Total size                       = 20 B (20 bytes)
    Tasks count                      = 1
    Current Task                     = 0
    Connection Retry Count           = 0
    Transfer Retry Count             = 0
    Stop On Error                    = false
    Update Frequency                 = 5000
    

    重點在於 statuspercent兩個字段對應信息,前者標識文件下載狀態,後者標識文件下載百分比。

    到這裏整個文件上傳和下載的流程以及整個上傳下載的過程已經全部走通。但是這是基於Linux下執行腳本來上傳和下載文件。現在需要把這些過程轉換成java代碼。

3. 業務難點

  • put、get腳本不是固定的,需要傳入文件路徑、文件名稱以及用戶ID,其中用戶ID可能是唯一的,文件路徑和文件名稱需要靈活配置;
  • java操作shell腳本;
  • java接收shell腳本執行後的流,並轉成字符流,截取相應的上傳、下載狀態來判定文件上傳、下載的最終狀態;

4. 技術方案

4.1 腳本生成

對於put、get腳本生成,這裏採用靜態模板Velocity技術,動態生成put/get腳本。

4.1.1 添加依賴

 <!-- 模板引擎 -->
 <dependency>
   <groupId>org.apache.velocity</groupId>
   <artifactId>velocity-engine-core</artifactId>
   <version>2.0</version>
 </dependency>

4.1.2 腳本模板

MFTModel.vm

open cebbank
lcd $!{dirPath}
cd $!{userId}
newjob
$!{manageType} $!{fileName}
jobsubmit
close

4.1.3 生成腳本的工具類

package com.ceb.mental.util;

import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.runtime.RuntimeConstants;
import org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader;

import java.io.*;


public class VelocityUtil {
    private final static String CHARSET = "utf8";

    /**
     * 生成MFT 上傳、下載腳本
     *
     * @param manageType 上傳或下載 put/get
     * @param dirPath    上傳文件所在文件夾或下載文件存放文件夾
     * @param fileName   上傳文件名稱或下載文件名稱
     * @param dest       生成腳本存放路徑(文件夾+文件名稱)
     * @param userId     銀行分配的id
     * @param encode     編碼格式
     */
    public static String createMFTCommandFile(String manageType,
                                              String dirPath,
                                              String fileName,
                                              String dest,
                                              String userId,
                                              String encode) {
        FileOutputStream outStream = null;
        BufferedWriter sw = null;
        try {
            VelocityEngine ve = new VelocityEngine();
            ve.setProperty(RuntimeConstants.RESOURCE_LOADER, "classpath");
            ve.setProperty("classpath.resource.loader.class", ClasspathResourceLoader.class.getName());
            ve.init();
            Template t = ve.getTemplate("MFTModel.vm", encode);
            VelocityContext ctx = new VelocityContext();
            ctx.put("dirPath", dirPath);
            ctx.put("userId", userId);
            ctx.put("fileName", fileName);
            ctx.put("manageType", manageType);
            //確定靜態文檔在共享文件目錄的完整存儲路徑
            File file = new File(dest);
            if (!file.getParentFile().exists()) {
                file.getParentFile().mkdirs();
            }
            outStream = new FileOutputStream(file);
            OutputStreamWriter oswriter = new OutputStreamWriter(outStream, encode);
            sw = new BufferedWriter(oswriter);
            t.merge(ctx, sw);
            sw.flush();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            try {
                if (sw != null) {
                    sw.close();
                }
                if (outStream != null) {
                    outStream.close();
                }
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        return dest;
    }

    /**
     * 生成MFT 上傳腳本
     *
     * @param manageType 上傳或下載 put/get
     * @param dirPath    上傳文件所在文件夾或下載文件存放文件夾
     * @param fileName   上傳文件名稱或下載文件名稱
     * @param dest       生成腳本存放路徑(文件夾+文件名稱)
     * @param userId     銀行分配的id
     */
    public static String createMFTCommandFile(String manageType,
                                              String dirPath,
                                              String fileName,
                                              String dest,
                                              String userId) {
        return createMFTCommandFile(manageType, dirPath, fileName, dest, userId, CHARSET);
    }

    public static void main(String[] args) throws IOException {
        System.out.println(createMFTCommandFile("put", "/home/qy_work/cebfile/upload",
                "20181107.txt", "D:/file/put01", "USERID"));
    }
}

4.2 執行腳本

4.2.1 java執行Shell命令介紹

每個Java應用程序都有一個Runtime類實例,使應用程序能夠與其運行的環境相連接。可以通過getRuntime方法獲取當前運行時環境。 java執行shell命令介紹
應用程序不能創建自己的Runtime類實例。
介紹幾個主要方法:

  • Process exec(String command)
    ​ 在單獨的進程中執行指定的字符串命令。
  • Process exec(String command, String[] envp)
    ​ 在指定環境的單獨進程中執行指定的字符串命令。
  • Process exec(String command, String[] envp, File dir)
    ​ 在有指定環境和工作目錄的獨立進程中執行指定的字符串命令。
  • Process exec(String[] cmdarray)
    ​ 在單獨的進程中執行指定命令和變量。
  • Process exec(String[] cmdarray, String[] envp)
    ​ 在指定環境的獨立進程中執行指定命令和變量。
  • Process exec(String[] cmdarray, String[] envp, File dir)
    ​ 在指定環境和工作目錄的獨立進程中執行指定的命令和變量。
    command:一條指定的系統命令。
    envp:環境變量字符串數組,其中每個環境變量的設置格式爲name=value;如果子進程應該繼承當前進程的環境,則該參數爲null。
    dir:子進程的工作目錄;如果子進程應該繼承當前進程的工作目錄,則該參數爲null。
    cmdarray:包含所調用命令及其參數的數組。

4.2.2 執行shell命令後會返回inputStream

例如:

public class Test {  
    public static void main(String[] args){  
        InputStream in = null;  
        try {  
            Process pro = Runtime.getRuntime().exec(new String[]{"sh",  
                                     "/home/test/test.sh","select admin from M_ADMIN",  
                                     "/home/test/result.txt"});  
            pro.waitFor();  
            in = pro.getInputStream();  
            BufferedReader read = new BufferedReader(new InputStreamReader(in));  
            String result = read.readLine();  
            System.out.println("INFO:"+result);  
        } catch (Exception e) {  
            e.printStackTrace();  
        }  
    }  
}  

此時通過pro.getInputStream(); 獲取inputstream,再將流轉成字符流,此時便能輸出執行shell命令後返回的信息;

4.2.3 結合業務場景的工具類

package com.ceb.mental.util;


import org.apache.commons.lang.StringUtils;

import java.io.BufferedReader;
import java.io.InputStreamReader;

public class RunShell {


    private final static String BASH_PATH_PUT = "/home/Axway/Synchrony/SecureClient/sclient script ";
    private final static String BASH_PATH_STATUS = "/home/Axway/Synchrony/SecureClient/sclientadm displayjob -id ";
    private final static String GET = "get";
    private final static String PUT = "put";

    /**
     * MFT上傳文件
     *
     * @param dirPath  上傳文件目錄
     * @param fileName 上傳文件名稱
     * @param dest     上傳腳本存放路徑
     * @param userId   光大分配id
     * @return
     */
    public static boolean putMFTFile(String dirPath, String fileName, String dest, String userId) {
        return manageMFTFile(dirPath, fileName, dest, userId, PUT);
    }

    /**
     * MFT下載文件
     *
     * @param dirPath  下載文件存放的目錄
     * @param fileName 下載文件名稱
     * @param dest     下載腳本存放路徑
     * @param userId   光大分配id
     * @return
     */
    public static boolean getMFTFile(String dirPath, String fileName, String dest, String userId) {
        return manageMFTFile(dirPath, fileName, dest, userId, GET);
    }

    public static boolean manageMFTFile(String dirPath, String fileName, String dest, String userId, String manageType) {
        //生成put文件
        String commandFile = VelocityUtil.createMFTCommandFile(manageType, dirPath, fileName, dest, userId);
        //組裝put腳本
        String command = BASH_PATH_PUT + commandFile;
        System.out.println("==========>執行腳本:" + command);
        //執行put上傳命令
        String result = runCommand(command, 12);
        if (StringUtils.isNotBlank(command)) {
            //截取jobId
            String jobId = result.substring(result.lastIndexOf(":") + 1).trim();
            System.out.println("截取的jobId:" + jobId);
            //查詢狀態
            String statusCommand = BASH_PATH_STATUS + jobId;
            System.out.println("==========>查詢文件狀態腳本:" + statusCommand);
            String statusResult = runCommand(statusCommand, 7);
            String status = statusResult.substring(statusResult.lastIndexOf("=") + 1).trim();
            System.out.println("上傳結果:" + status);
            if (StringUtils.isNotBlank(status) && status.equals("Finished")) {
                return true;
            }
        }
        return false;
    }

    /**
     * 運行腳本,並讀取特定行信息
     *
     * @param commond  shell腳本
     * @param readLine 從第幾行讀取
     * @return
     */
    private static String runCommand(String commond, int readLine) {
        String result = null;
        try {
            Process ps = Runtime.getRuntime().exec(commond);
            ps.waitFor();
            BufferedReader br = new BufferedReader(new InputStreamReader(ps.getInputStream()));
            StringBuffer sb = new StringBuffer();
            int iLine = 1;
            String line;
            while ((line = br.readLine()) != null) {
                if (iLine == readLine) {
                    sb.append(line).append("\n");
                }
                iLine++;
            }
            result = sb.toString();
            System.out.println(result);
        } catch (Exception e) {
            e.printStackTrace();
        }
        return result;
    }

}

4.2.4 業務測試

編寫controller.

package com.ceb.mental.controller;

import com.ceb.mental.util.RunShell;
import com.ceb.mental.util.VelocityUtil;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;

@Controller
@RequestMapping("/mft")
public class MFTController {

    @RequestMapping("/upload")
    public void putFile(String fileName){
        RunShell.putMFTFile("/home/qy_work/cebfile/upload", fileName,
                "/home/qy_work/cebfile/upload/put", "USERID");
    }

    @RequestMapping("/download")
    public void getFile(String fileName){
        RunShell.getMFTFile("/home/qy_work/cebfile/download", fileName,
                "/home/qy_work/cebfile/upload/get", "USERID");
    }

}

將項目部署到MTF的客戶端,訪問相應的URL,

4.2.5 檢查是否上傳成功

/home/Axway/Synchrony/SecureClient/sclient cebbank list USERID

這裏查詢的是銀行MTF端上傳文件的列表。查詢結果,如圖:

查詢結果

5. 總結

剛開始都不知道MFT是啥,然後從搭建客戶端,在客戶端一點點嘗試操作,查閱相關資料,摸清原理,理清業務及其中的技術點,評估技術方案,積跬步、解決bug,最終完成了這個看似很難的需求。

生命不息、戰鬥不止!

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