首先: 一个基础简单的上传页面, 异步上传. 注意-->使用的是ajaxfileupload.js, 需要下载.

最基础页面准备:

<%@ page contentType="text/html;charset=UTF-8" language="java" %>
<%@ page isELIgnored="false" %>
<!DOCTYPE HTML>
<html>
<head>
    <meta charset="UTF-8" />
    <title>FTP测试</title>
</head>
<body>

<h1>选择资源后,点击按钮上传</h1>
<input id="fileToUpload" type="file" size="45" name="multipartFile"
       class="input">
<button class="button" οnclick="ajaxFileUpload()">上传</button>
<br>

</body>
<script type="text/javascript" src="/js/jquery-3.3.1.min.js"></script>
<script type="text/javascript" src="/js/ajaxfileupload.js"></script>
<script>
    function ajaxFileUpload() {
        alert("----------");
        $.ajaxFileUpload({
            url : 'http://192.168.0.226:8086/admin/test/uploadFile.do',//url.我这里是写死的.实际应用中应该写成活的.这点自行百度即可.
            secureuri : false,//是否启用安全提交,默认为false。
            fileElementId : 'fileToUpload',//需要上传的文件域的ID,即<input type="file">的ID。
            dataType : 'String',//服务器返回的数据类型(有json和String)
            data : {filetype : "images"},// 自定义参数
            type: 'post',//当要提交自定义参数时,这个参数要设置成post
            success : function(data, status) {
                alert(data)
            },
            error : function(data, status, e) {
                alert(data);
            }
        })
        return false;
    }
</script>
</html>

第二步: 准备一个从属性文件中读取准备好的FTP的IP,端口,用户名,密码,和基础存储路径的方法.

//properties: 属性文件在项目里的路径 ; key:需要拿到的值
    public String getPropertiesToFTP(String propertiesNam, String key) throws IOException {
        //找到需要读取的properties文件的项目路径.
        //Properties properties = PropertiesLoaderUtils.loadAllProperties("properties/system.properties");
        Properties properties = PropertiesLoaderUtils.loadAllProperties(propertiesNam);
        //从这个属性文件里面拿到具体某一个key对应的值
        String property = properties.getProperty(key);
        //返回值.
        return property;
    }

第三步: 准备一个限制上传资源大小的小方法.想用就用,不想用的话在最后自己删除即可.

/**
     * 判断文件大小
     *
     * @param :multipartFile:上传的文件
     * @param size:                限制大小
     * @param unit:限制单位(B,K,M,G)
     * @return boolean:是否大于
     */
    public static boolean checkFileSize(MultipartFile multipartFile, int size, String unit) {
        long len = multipartFile.getSize();//上传文件的大小, 单位为字节.
        //准备接收换算后文件大小的容器
        double fileSize = 0;
        if ("B".equals(unit.toUpperCase())) {
            fileSize = (double) len;
        } else if ("K".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1024;
        } else if ("M".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1048576;
        } else if ("G".equals(unit.toUpperCase())) {
            fileSize = (double) len / 1073741824;
        }
        //如果上传文件大于限定的容量
        if (fileSize > size) {
            return false;
        }
        return true;
    }

第四步: 准备获取上传文件的工具类, 注意--->这是一个工具类. 主要是通过字节获取资源本身的后缀.

package manage.Utils;

import org.springframework.web.multipart.MultipartFile;

import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Set;

public class FTPFileClass {

    public final static Map<String, String> FILE_TYPE_MAP = new HashMap<String, String>();

    private FTPFileClass() {
    }

    static {
        getAllFileType(); //初始化文件类型信息
    }

    /**
     * getAllFileType:存储常见文件头信息
     */
    private static void getAllFileType() {
        FILE_TYPE_MAP.put("ffd8ffe000104a464946", "jpg"); //JPEG (jpg)
        FILE_TYPE_MAP.put("89504e470d0a1a0a0000", "png"); //PNG (png)
        FILE_TYPE_MAP.put("47494638396126026f01", "gif"); //GIF (gif)
        FILE_TYPE_MAP.put("49492a00227105008037", "tif"); //TIFF (tif)
        FILE_TYPE_MAP.put("424d228c010000000000", "bmp"); //16色位图(bmp)
        FILE_TYPE_MAP.put("424d8240090000000000", "bmp"); //24位位图(bmp)
        FILE_TYPE_MAP.put("424d8e1b030000000000", "bmp"); //256色位图(bmp)
        FILE_TYPE_MAP.put("41433130313500000000", "dwg"); //CAD (dwg)
        FILE_TYPE_MAP.put("3c21444f435459504520", "html"); //HTML (html)
        FILE_TYPE_MAP.put("3c21646f637479706520", "htm"); //HTM (htm)
        FILE_TYPE_MAP.put("48544d4c207b0d0a0942", "css"); //css
        FILE_TYPE_MAP.put("696b2e71623d696b2e71", "js"); //js
        FILE_TYPE_MAP.put("7b5c727466315c616e73", "rtf"); //Rich Text Format (rtf)
        FILE_TYPE_MAP.put("38425053000100000000", "psd"); //Photoshop (psd)
        FILE_TYPE_MAP.put("46726f6d3a203d3f6762", "eml"); //Email [Outlook Express 6] (eml)
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "doc"); //MS Excel 注意:word、msi 和 excel的文件头一样
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "vsd"); //Visio 绘图
        FILE_TYPE_MAP.put("5374616E64617264204A", "mdb"); //MS Access (mdb)
        FILE_TYPE_MAP.put("252150532D41646F6265", "ps");
        FILE_TYPE_MAP.put("255044462d312e350d0a", "pdf"); //Adobe Acrobat (pdf)
        FILE_TYPE_MAP.put("2e524d46000000120001", "rmvb"); //rmvb/rm相同
        FILE_TYPE_MAP.put("464c5601050000000900", "flv"); //flv与f4v相同
        FILE_TYPE_MAP.put("00000020667479706d70", "mp4");
        FILE_TYPE_MAP.put("49443303000000002176", "mp3");
        FILE_TYPE_MAP.put("000001ba210001000180", "mpg"); //
        FILE_TYPE_MAP.put("3026b2758e66cf11a6d9", "wmv"); //wmv与asf相同
        FILE_TYPE_MAP.put("52494646e27807005741", "wav"); //Wave (wav)
        FILE_TYPE_MAP.put("52494646d07d60074156", "avi");
        FILE_TYPE_MAP.put("4d546864000000060001", "mid"); //MIDI (mid)
        FILE_TYPE_MAP.put("504b0304140000000800", "zip");
        FILE_TYPE_MAP.put("526172211a0700cf9073", "rar");
        FILE_TYPE_MAP.put("235468697320636f6e66", "ini");
        FILE_TYPE_MAP.put("504b03040a0000000000", "jar");
        FILE_TYPE_MAP.put("4d5a9000030000000400", "exe");//可执行文件
        FILE_TYPE_MAP.put("3c25402070616765206c", "jsp");//jsp文件
        FILE_TYPE_MAP.put("4d616e69666573742d56", "mf");//MF文件
        FILE_TYPE_MAP.put("3c3f786d6c2076657273", "xml");//xml文件
        FILE_TYPE_MAP.put("494e5345525420494e54", "sql");//xml文件
        FILE_TYPE_MAP.put("7061636b616765207765", "java");//java文件
        FILE_TYPE_MAP.put("406563686f206f66660d", "bat");//bat文件
        FILE_TYPE_MAP.put("1f8b0800000000000000", "gz");//gz文件
        FILE_TYPE_MAP.put("6c6f67346a2e726f6f74", "properties");//bat文件
        FILE_TYPE_MAP.put("cafebabe0000002e0041", "class");//bat文件
        FILE_TYPE_MAP.put("49545346030000006000", "chm");//bat文件
        FILE_TYPE_MAP.put("04000000010000001300", "mxp");//bat文件
        FILE_TYPE_MAP.put("504b0304140006000800", "docx");//docx文件
        FILE_TYPE_MAP.put("d0cf11e0a1b11ae10000", "wps");//WPS文字wps、表格et、演示dps都是一样的
        FILE_TYPE_MAP.put("6431303a637265617465", "torrent");
        FILE_TYPE_MAP.put("6D6F6F76", "mov"); //Quicktime (mov)
        FILE_TYPE_MAP.put("FF575043", "wpd"); //WordPerfect (wpd)
        FILE_TYPE_MAP.put("CFAD12FEC5FD746F", "dbx"); //Outlook Express (dbx)
        FILE_TYPE_MAP.put("2142444E", "pst"); //Outlook (pst)
        FILE_TYPE_MAP.put("AC9EBD8F", "qdf"); //Quicken (qdf)
        FILE_TYPE_MAP.put("E3828596", "pwl"); //Windows Password (pwl)
        FILE_TYPE_MAP.put("2E7261FD", "ram"); //Real Audio (ram)
    }

    /**
     * 得到上传文件的文件头.
     *
     * @param src
     * @return
     */
    public static String bytesToHexString(byte[] src) {
        StringBuilder stringBuilder = new StringBuilder();
        if (src == null || src.length <= 0) {
            return null;
        }
        for (int i = 0; i < src.length; i++) {
            int v = src[i] & 0xFF;
            String hv = Integer.toHexString(v);
            if (hv.length() < 2) {
                stringBuilder.append(0);
            }
            stringBuilder.append(hv);
        }
        return stringBuilder.toString();
    }

    /**使用这个工具类时候, 实际上就是调用这个方法即可.
     * 根据指定文件的文件头判断其文件类型---> 上面两个方法都是准备工作,这个才是最好得到文件类型的主方法.
     *
     * @param :MultipartFile multipartFile: 我这里用的是SSM自带的接收上传文件的类. 你可以根据不同的接收进行转变.
     * @return
     */
    public static String getFileType(MultipartFile multipartFile) {
        String res = null;
        try {
            //如果说要有修改,那就只在这里修改就足够了. 将接收的上传资源转换成文件输入流.
//            FileInputStream is = (FileInputStream) multipartFile.getInputStream();
//            byte[] b = new byte[10];
//            is.read(b, 0, b.length);
//            String fileCode = bytesToHexString(b);

            //用这个和上面一个都可以. 实际上就是得到上传资源的字节.传入bytesToHexString方法中,得到上传文件的文件头
            byte[] bytes = multipartFile.getBytes();
            String fileCode = bytesToHexString(bytes);
            System.out.println(fileCode);


            //这种方法在字典的头代码不够位数的时候可以用但是速度相对慢一点. 根据上传资源的头,得到具体的类型
            Iterator<String> keyIter = FILE_TYPE_MAP.keySet().iterator();
            while (keyIter.hasNext()) {
                String key = keyIter.next();
                if (key.toLowerCase().startsWith(fileCode.toLowerCase()) || fileCode.toLowerCase().startsWith(key.toLowerCase())) {
                    res = FILE_TYPE_MAP.get(key);
                    break;
                }
            }
        } catch (FileNotFoundException e) {
            e.printStackTrace();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }
}

第五步: 写一个登录FTP,开始上传文件的小方法.

//连接ftp.(ftp的创建搜索百度即可,至于用户名和密码, 我这里实际上就是你安装FTP的那台电脑某一个用户的名和密码.这一点很坑! )
    public String ftpConnect(String ip, int port, String userName, String password, MultipartFile multipartFile, String folderNmae) {

        //创建由 apache 提供的连接ftp工具类FTPClient
        FTPClient ftpClient = new FTPClient();

        //准备返回的文件存储路径和名称(用了FTP存储文件之后,你肯定要返回一个虚拟访问路径存储在数据库里面.这个返回的用处就是这个了)
        String fictitious = "";

        try {

            //开始连接.IP,端口
            //(IP举例: 如果用浏览器访问FTP那就是 ftp://192.168.0.111:21; 而这个connect方法只需要192.168.0.111这种形式即可)
            //(端口举例: 也就是你在搭建FTP服务器时候填写的端口, 创建时候默认是21,如果你修改了请自行更改.)
            ftpClient.connect(ip, port);

            //用户名和密码.(实际上就是创建FTP服务器的那台电脑的登录用户的账号密码而已,自己创建个用户都行)
            ftpClient.login(userName, password);

            //查看尝试连接后的返回值.(没有深入研究,不过我这里连接成功返回的是230, 连接失败返回的是530)
            int replyCode = ftpClient.getReplyCode();

            //查看是否连接FTP成功.
            if (!FTPReply.isPositiveCompletion(replyCode)) {
                System.out.println("拒绝连接");
                //断开连接
                ftpClient.disconnect();
                //终止这个工具方法.
                return "false";
            } else {
                //否则
                System.out.println("连接成功");

                /** ----------  连接成功后,就要开始准备好接收上传的资源. -------------- */
                //获取上传文件的详细信息.
                boolean empty = multipartFile.isEmpty();//上传的资源是否为null(这个判断实际上应该在使用这个工具方法之前就进行一次判断.)
                String originalFilename = multipartFile.getOriginalFilename();//获取上传的文件名
                byte[] bytes = multipartFile.getBytes();//字节
                long size = multipartFile.getSize();//大小

                /**--------------以下是获取一个用于最终存储的新的文件名称--------------*/
                //先拿到上传资源的后缀名.
                String suffix = originalFilename.substring(originalFilename.lastIndexOf("."));
                //得到此时时间的毫秒数
                long millisecond = System.currentTimeMillis();
                //以毫秒数为新文件名,与后缀相连--产生一个新的文件名称;  newFileName: 就是存储在FTP服务器上的具体文件名称.(不改名可能导致重名覆盖)
                String newFileName = millisecond + suffix;


                /**-------以下是生成新的存储目录(以传递进来的基础目录为基准, 加上上传的年月日,组成新的存储目录)-------*/
                SimpleDateFormat simpleDateFormat = new SimpleDateFormat("yyyy/MM/dd");
                String format = simpleDateFormat.format(new Date());
                //路径为: /准备好的目录/文件类型/年/月/日;如上传为图片则: /基础文件名/image/2018/06/20
                folderNmae = folderNmae + "/" + format;


                /**-----------  查询, ftp指定物理路径下面, 有没有这个文件夹. 如果没有则创建(这里是多层目录).并且指定该目录为存储路径-------------*/
                //第一次查询,是否目录已经存在.true为存在,false为不存在
                boolean exist = ftpClient.changeWorkingDirectory(folderNmae);
                //如果有这个路径.则直接指定该路径为文件具体存放位置
                if (exist) {
                    //指定存储路径
                    ftpClient.changeWorkingDirectory(folderNmae);
                } else {
                    //如果没有: 由于apache提供的FTPClient中没有一次性创建多层目录的方法.
                    // 这里将准备创建的这个路径进行遍历,一个个的创建文件夹进行遍历.
                    String[] split = folderNmae.split("/");
                    String path = "";
                    for (int i = 0; i < split.length; i++) {
                        //用于每次遍历时候,判断是否有这个文件夹(也叫目录)
                        boolean isExistence = true;

                        if (i == 0) {
                            path = "/" + split[i];//"/"+文件名
                            isExistence = ftpClient.changeWorkingDirectory(path);//查询一次
                        } else {
                            path = path + "/" + split[i];//"/"+文件名
                            isExistence = ftpClient.changeWorkingDirectory(path);//查询一次
                        }
                        //如果路径不存在
                        if (!isExistence) {
                            //则创建该路径
                            ftpClient.makeDirectory(path);
                        }
                    }
                    //所有文件夹都创建完成之后.将创建好的路径指定为文件具体存储路径.
                    ftpClient.changeWorkingDirectory(path);
                }

                // 写明文件类型: 支持二进制文件
                ftpClient.setFileType(FTPClient.BINARY_FILE_TYPE);

                /**------------- 以上: 1.生成了新的文件名称; 2.创建了存储文件的路径; 3.指定了存储路径以及支持的文件类型--------------*/

                /**最后:  调用FTPClient的上传方法.
                 * newFileName: 文件名(就只是一个名字而已);
                 * multipartFile: 是ssm框架接收到的上传的资源转. 这里应storeFile这个方法需要, 转换成输入流.*/
                ftpClient.storeFile(newFileName, multipartFile.getInputStream());

                //至此,上传完毕. 最后我们需要返回 存储路径和文件名的拼接字符串.在代码里将这一字符串存入数据库.为了以后展示该图片
                //存储目录名 + "/" + 新生成的文件名.
                fictitious = folderNmae + "/" + newFileName;
            }
        } catch (SocketException e) {
            e.printStackTrace();//可能是连接FTP时候传递的IP错误
        } catch (IOException e) {
            e.printStackTrace();//可能是连接FTP时候传递的port错误
        } finally {
            //最后,退出FTP,并断开连接
            try {
                //退出
                ftpClient.logout();
                //断开连接
                ftpClient.disconnect();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        //最后,如果上传成功了,就返回这个文件的路径和它的文件名.
        return fictitious;
    }

第六步: 以上就是几个小方法(小方法可以写在上传类里面), 还有一个分辨文件后缀的工具类. 最后, 写一个SSM的方法.

@RequestMapping(value = "/uploadFile", method = RequestMethod.POST)
    @ResponseBody
    public String uploadFile(HttpServletRequest request, MultipartFile multipartFile) throws Exception {

        /**上传之前进行判断.*/
        //这里是大小的限制判断.目前限制资源大小在 100MB以内
        boolean flag = checkFileSize(multipartFile, 100, "M");

        //准备返回容器
        String result = null;
        //存储完文件之后,得到的 存储路径和新生成的文件名称
        String fictitious = null;

        //如果上传的资源大小没有超过100MB
        if (flag) {
            //准备容器---从属性文件中读取到的有用的值;
            String ftpIp = "";
            String ftpPort = "";
            String username = "";
            String password = "";
            String realFilepath = "";

            //利用写好的小方法读取指定的某一个属性文件里面的值
            ftpIp = getPropertiesToFTP("properties/system.properties", "ftp.ip");
            ftpPort = getPropertiesToFTP("properties/system.properties", "ftp.port");
            username = getPropertiesToFTP("properties/system.properties", "ftp.username");
            password = getPropertiesToFTP("properties/system.properties", "ftp.password");
            //realFilepath: 实际上就是在FTP指定的物理路径下面先自己创建一个基础文件夹(我是以基础文件夹来分类,比如说以功能模块进行区分.)
            realFilepath = getPropertiesToFTP("properties/system.properties", "ftp.realFilepath");

            //查看是否有上传资源.
            boolean empty = multipartFile.isEmpty();
            //如果不为null,则开始连接FTP,进行资源存储
            if (!empty) {
                String filetype = FTPFileClass.getFileType(multipartFile);

                //存储的指定文件夹加传递文件的类型,组成一个新的文件夹.比如图片则是: /基础文件夹/jpg
                realFilepath += "/" + filetype;

                // 登录到指定的ftp服务器上:ip,端口,用户名,密码,接收到的具体的上传资源,具体上传路径(基础路径)
                fictitious = ftpConnect(ftpIp, Integer.parseInt(ftpPort), username, password, multipartFile, realFilepath);
            } else {
                result = JSON.toJSONString("上传资源为NULL");
            }
            //判断是否上传成功.并返回提示信息
            if ("false".equals(fictitious)) {
                result = JSON.toJSONString("上传失败");
            } else {
                result = JSON.toJSONString("上传成功");
            }
        }
        return result;
    }



注意: 我这里写的就只是上上传一个资源到FTP, 并返回虚拟路径, 并没有往数据库存储.