基于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
    
    • 1

    前面脚本都是固定的(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
    
    • 1
  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
    
    • 1

    前面脚本都是固定的(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
    
    • 1
  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
  • 1

这里查询的是银行MTF端上传文件的列表。查询结果,如图:

查询结果

5. 总结

刚开始都不知道MFT是啥,然后从搭建客户端,在客户端一点点尝试操作,查阅相关资料,摸清原理,理清业务及其中的技术点,评估技术方案,积跬步、解决bug,最终完成了这个看似很难的需求。

生命不息、战斗不止!

在Python中,你可以使用`boto3`库来操作AWS S3服务,并结合一些文件系统操作库如`psutil`来处理MFT (Master File Table)数据。以下是基本步骤: 1. 安装所需库: ``` pip install boto3 psutil mftwmi ``` 2. 首先导入必要的模块: ```python import boto3 from mftwmi import MFT ``` 3. 初始化S3客户端: ```python s3 = boto3.client('s3', aws_access_key_id='your_access_key', aws_secret_access_key='your_secret_key', region_name='your_bucket_region') ``` 或者如果你使用的是配置文件,则无需提供凭证,只需: ```python s3 = boto3.resource('s3') ``` 4. 使用`mftwmi`库读取MFT: ```python mft = MFT() mft.parse_mft() files_to_upload = [entry for entry in mft.entries if 'filename' in entry] ``` 5. 筛选出需要上传的文件(这一步可以根据实际情况调整,比如只选择特定类型的文件)。 6. 对每个文件,下载并上传到S3: ```python for file_entry in files_to_upload: source_path = file_entry['filename'] target_key = f"folder/in/s3/{file_entry['filename']}" # 设置S3目标路径 with open(source_path, 'rb') as file: s3.upload_fileobj(file, 'your_bucket_name', target_key) ``` 7. 检查上传是否成功,如果有错误处理机制,可以添加适当错误处理代码。 **注意:** - 确保已经设置了AWS的访问权限,并且对MFT解析有适当的权限。 - 处理MFT通常涉及到低级操作系统细节,对于大规模文件或复杂的文件结构,你可能需要更复杂的数据处理逻辑。
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值