SpringMVC文件上传的三种方法

SpringMVC文件上传的三种方法

此文章将介绍使用SpringMVC进行commons-fileupload、FTP、aliyunOSS三种文件上传和下载的案例。文件上传的解决方案还有很多,例如阿里的fastDFS等。这里就不做介绍了,有兴趣的小伙伴可以自行baidu学习~

搭建项目

案例的pom依赖,其中commons-fileupload、aliyun、ftp可以根据自己的需要选择是否导入

<!-- 编码和jdk -->
    <properties>
        <project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
        <maven.compiler.source>1.8</maven.compiler.source>
        <maven.compiler.target>1.8</maven.compiler.target>
    </properties>

    <dependencies>
        <!-- spring相关jar包 -->
        <dependency>
            <groupId>org.springframework</groupId>
            <artifactId>spring-webmvc</artifactId>
            <version>5.1.5.RELEASE</version>
        </dependency>
        <!--servlet相关-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>javax.servlet-api</artifactId>
            <version>3.1.0</version>
            <scope>provided</scope>
        </dependency>
        <!--json相关-->
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-databind</artifactId>
            <version>2.9.5</version>
        </dependency>
        <dependency>
            <groupId>com.fasterxml.jackson.core</groupId>
            <artifactId>jackson-annotations</artifactId>
            <version>2.9.5</version>
        </dependency>
        <!--文件上传-->
        <dependency>
            <groupId>commons-fileupload</groupId>
            <artifactId>commons-fileupload</artifactId>
            <version>1.3</version>
        </dependency>
        <!--引入FTP 依赖-->
        <dependency>
            <groupId>commons-net</groupId>
            <artifactId>commons-net</artifactId>
            <version>3.6</version>
        </dependency>
        <!-- 阿里云OSS -->
        <dependency>
            <groupId>com.aliyun.oss</groupId>
            <artifactId>aliyun-sdk-oss</artifactId>
            <version>3.8.0</version>
        </dependency>
        <!--日志相关-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.2.3</version>
        </dependency>
        <!--二维码依赖-->
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>core</artifactId>
            <version>3.3.0</version>
        </dependency>
        <dependency>
            <groupId>com.google.zxing</groupId>
            <artifactId>javase</artifactId>
            <version>3.3.0</version>
        </dependency>
    </dependencies>

    <build>
        <!--插件-->
        <plugins>
            <plugin>
                <groupId>org.apache.tomcat.maven</groupId>
                <artifactId>tomcat7-maven-plugin</artifactId>
                <version>2.2</version>
                <configuration>
                    <path>/</path>
                    <port>8080</port>
                    <server>tomcat7</server>
                </configuration>
                <executions>
                    <execution>
                        <phase>package</phase>
                        <goals>
                            <goal>run</goal>
                        </goals>
                    </execution>
                </executions>
            </plugin>
        </plugins>
    </build>

logback配置文件,在resources文件夹下创建logback.xml文件,拷贝入如下代码,这里指定了日志输出路径为项目的根目录。

<?xml version="1.0" encoding="UTF-8"?>
<configuration debug="false">

    <!--定义日志文件的存储地址 logs为当前项目的logs目录 还可以设置为../logs -->
    <property name="LOG_HOME" value="logs" />

    <!--控制台日志, 控制台输出 -->
    <appender name="STDOUT" class="ch.qos.logback.core.ConsoleAppender">
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度,%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
    </appender>

    <!--文件日志, 按照每天生成日志文件 -->
    <appender name="FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
        <rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
            <!--日志文件输出的文件名-->
            <FileNamePattern>${LOG_HOME}/TestWeb.log.%d{yyyy-MM-dd}.log</FileNamePattern>
            <!--日志文件保留天数-->
            <MaxHistory>30</MaxHistory>
        </rollingPolicy>
        <encoder class="ch.qos.logback.classic.encoder.PatternLayoutEncoder">
            <!--格式化输出:%d表示日期,%thread表示线程名,%-5level:级别从左显示5个字符宽度%msg:日志消息,%n是换行符-->
            <pattern>%d{yyyy-MM-dd HH:mm:ss.SSS} [%thread] %-5level %logger{50} - %msg%n</pattern>
        </encoder>
        <!--日志文件最大的大小-->
        <triggeringPolicy class="ch.qos.logback.core.rolling.SizeBasedTriggeringPolicy">
            <MaxFileSize>10MB</MaxFileSize>
        </triggeringPolicy>
    </appender>

    <!-- show parameters for hibernate sql 专为 Hibernate 定制 -->
    <logger name="org.hibernate.type.descriptor.sql.BasicBinder" level="TRACE" />
    <logger name="org.hibernate.type.descriptor.sql.BasicExtractor" level="DEBUG" />
    <logger name="org.hibernate.SQL" level="DEBUG" />
    <logger name="org.hibernate.engine.QueryParameters" level="DEBUG" />
    <logger name="org.hibernate.engine.query.HQLQueryPlan" level="DEBUG" />

    <!--myibatis log configure-->
    <logger name="com.apache.ibatis" level="TRACE"/>
    <logger name="java.sql.Connection" level="DEBUG"/>
    <logger name="java.sql.Statement" level="DEBUG"/>
    <logger name="java.sql.PreparedStatement" level="DEBUG"/>

    <!-- 日志输出级别 -->
    <root level="DEBUG">
        <appender-ref ref="STDOUT" />
        <appender-ref ref="FILE"/>
    </root>
</configuration>

配置aliyun.properties文件,同样在resources文件夹下创建此文件,内容如下,如果不需要使用aliyunOSS可以忽略。

aliyun.AccessKeyID=yourAccessKeyID
aliyun.AccessKeySecret=yourAccessKeySecret
aliyun.Buckets=yourBuckets
aliyun.EndPoint=https://oss-cn-beijing.aliyuncs.com
aliyun.prefix=prefix/

配置SpringMVC文件,在resources目录下创建一个名为spring的文件夹,创建Spring-MVC.xml粘贴如下内容

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:context="http://www.springframework.org/schema/context"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xmlns:mvc="http://www.springframework.org/schema/mvc"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
       http://www.springframework.org/schema/beans/spring-beans.xsd
       http://www.springframework.org/schema/context
       http://www.springframework.org/schema/context/spring-context.xsd
        http://www.springframework.org/schema/mvc
        http://www.springframework.org/schema/mvc/spring-mvc-4.1.xsd">

    <!--扫描包-->
    <context:component-scan base-package="cn.rayfoo"/>
    <!--放行静态资源-->
    <mvc:default-servlet-handler/>
    <!--注解驱动-->
    <!-- 注册MVC注解驱动 -->
    <mvc:annotation-driven>
        <mvc:message-converters register-defaults="true">
            <!--json编码转化-->
            <bean class="org.springframework.http.converter.StringHttpMessageConverter" >
                <property name = "supportedMediaTypes">
                    <list>
                        <value>text/html;charset=utf-8</value>
                        <value>application/json;charset=utf-8</value>
                    </list>
                </property>
            </bean>
            <bean class="org.springframework.http.converter.json.MappingJackson2HttpMessageConverter" >
            </bean>
        </mvc:message-converters>
    </mvc:annotation-driven>
    <!--文件解析器-->
    <bean id="multipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
        <!--文件最大可上传大小,单位byte  此处为10M 1024*1024*10-->
        <property name="maxUploadSize" value="10485760"></property>
    </bean>

</beans>

配置web.xml

<?xml version="1.0" encoding="UTF-8"?>
<web-app xmlns="http://xmlns.jcp.org/xml/ns/javaee"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee http://xmlns.jcp.org/xml/ns/javaee/web-app_4_0.xsd"
         version="4.0">


    <!--请求编码过滤器-->
    <filter>
        <filter-name>characterEncodingFilter</filter-name>
        <filter-class>org.springframework.web.filter.CharacterEncodingFilter</filter-class>
        <!--设置编码格式-->
        <init-param>
            <param-name>encoding</param-name>
            <param-value>UTF-8</param-value>
        </init-param>
        <!--设置请求和响应是否强制编码-->
        <init-param>
            <param-name>forceEncoding</param-name>
            <param-value>true</param-value>
        </init-param>
    </filter>
    <filter-mapping>
        <filter-name>characterEncodingFilter</filter-name>
        <url-pattern>/*</url-pattern>
    </filter-mapping>

    <!--配置前端控制器-->
    <servlet>
        <servlet-name>dispatcherServlet</servlet-name>
        <servlet-class>org.springframework.web.servlet.DispatcherServlet</servlet-class>
        <!--配置有请求访问时加载的文件-->
        <init-param>
            <param-name>contextConfigLocation</param-name>
            <param-value>classpath:spring/Spring-MVC.xml</param-value>
        </init-param>
        <!--配置自动启动前端控制器-->
        <load-on-startup>2</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>dispatcherServlet</servlet-name>
        <url-pattern>/</url-pattern>
    </servlet-mapping>

</web-app>

cn.rayfoo.common包中,添加了一个枚举,用于指定协议的类型

package cn.rayfoo.common;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/21 10:46 上午
 * @Description: 封装http和https
 */
public enum Protocol {

    SECURITY_HTTP("https://"),

    COMMON_HTTP("http://");

    private String protocolType;

    public String getProtocolType() {
        return protocolType;
    }

    public void setProtocolType(String protocolType) {
        this.protocolType = protocolType;
    }

    Protocol(String protocolType) {
        this.protocolType = protocolType;
    }
}

cn.rayfoo.utils中使用了四个工具类,其中AliyunOSSUtils、PropertiesReader用于阿里云OSS,QRCodeUtil为二维码生成,FtpUtil为ftp上传和下载。

PropertiesReader

package cn.rayfoo.utils;

import java.io.InputStream;
import java.util.Properties;

/**
 * Created by rayfoo@qq.com Luna on 2020/4/15 18:38
 * Description : 读取配置文件工具类
 */
public class PropertiesReader {

    //创建Properties对象
    private static Properties property = new Properties();

    //在静态块中加载资源
    static {
        //使用try(){}.. 获取数据源
        //注意 * 这是jdk1.7开始支持的特性,如果使用的是低版本 需要提升jdk版本 或者更改写法
        try (
                InputStream in = PropertiesReader.class.getResourceAsStream("/aliyun.properties");
        ) {
            property.load(in);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    /**
     * 返回Properties对象
     * @return
     */
    public static Properties getProperties(){
        return property;
    }

    /**
     * 获取字符串类型的值
     * @param key
     * @return
     */
    public static String get(String key) {
        return property.getProperty(key);
    }

    /**
     * 获取Integer类型的值
     * @param key
     * @return
     */
    public static Integer getInteger(String key) {
        String value = get(key);
        return null == value ? null : Integer.valueOf(value);
    }

    /**
     * 获取Boolean类型的值
     * @param key
     * @return
     */
    public static Boolean getBoolean(String key) {
        String value = get(key);
        return null == value ? null : Boolean.valueOf(value);
    }

    /**
     * 设置一个键值对
     * @param key
     * @param value
     */
    public static void set(String key,String value){
        property.setProperty(key,value);
    }

    /**
     * 添加一个键值对
     * @param key
     * @param value
     */
    public static void add(String key,Object value){
        property.put(key,value);
    }
}


AliyunOSSUtil

package cn.rayfoo.utils;

import com.aliyun.oss.OSS;
import com.aliyun.oss.OSSClientBuilder;
import org.springframework.web.multipart.MultipartFile;

import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.util.UUID;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/13 3:11 下午
 * @Description:
 */
public class AliyunOSSUtil {

    /**
     * 阿里云的配置参数
     */
    private static String accessKeyId = null;
    private static String accessKeySecret = null;
    private static String endpoint = null;
    private static String bucketName = null;
    /**
     * 存储在OSS中的前缀名
     */
    private static  String file_prefix = null;


    /**
     * 静态块
     */
    static {
        //初始化AccessKey
        accessKeyId = PropertiesReader.get("aliyun.AccessKeyID");
        //初始化AccessKeySecret
        accessKeySecret = PropertiesReader.get("aliyun.AccessKeySecret");
        //初始化Endpoint
        endpoint = PropertiesReader.get("aliyun.EndPoint");
        //初始化bucketName
        bucketName = PropertiesReader.get("aliyun.Buckets");
        //初始化前缀
        file_prefix = PropertiesReader.get("aliyun.prefix");
    }

    /**
     * 私有化构造
     */
    private AliyunOSSUtils() {

    }

    /**
     * 获取图片的URL头信息
     *
     * @return 返回url头信息
     */
    private static String getURLHead() {
        //从哪个位置截取
        int cutPoint = endpoint.lastIndexOf('/') + 1;
        //http头
        String head = endpoint.substring(0, cutPoint);
        //服务器地址信息
        String tail = endpoint.substring(cutPoint);
        //返回结果
        return head + bucketName + "." + tail + "/";
    }

    /**
     * 获取存储在服务器上的地址
     *
     * @param oranName 文件名
     * @return 文件URL
     */
    private static String getRealName(String oranName) {
        return getURLHead() + oranName;
    }

    /**
     * 获取一个随机的文件名
     *
     * @param oranName 初始的文件名
     * @return 返回加uuid后的文件名
     */
    private static String getRandomImageName(String oranName) {
        //获取一个uuid 去掉-
        String uuid = UUID.randomUUID().toString().replace("-", "");
        //查一下是否带路径
        int cutPoint = oranName.lastIndexOf("/") + 1;
        //如果存在路径
        if (cutPoint != 0) {
            //掐头 如果开头是/ 则去掉
            String head = oranName.indexOf("/") == 0 ? oranName.substring(1, cutPoint) : oranName.substring(0, cutPoint);
            //去尾
            String tail = oranName.substring(cutPoint);
            //返回正确的带路径的图片名称
            return file_prefix + head + uuid + tail;
        }
        //不存在 直接返回
        return file_prefix + uuid + oranName;
    }

    /**
     * MultipartFile2File
     * @param multipartFile
     * @return
     */
    private static File transferToFile(MultipartFile multipartFile) {
        //选择用缓冲区来实现这个转换即使用java 创建的临时文件 使用 MultipartFile.transferto()方法 。
        File file = null;
        try {
            //获取文件名
            String originalFilename = multipartFile.getOriginalFilename();
            //获取最后一个"."的位置
            int cutPoint = originalFilename.lastIndexOf(".");
            //获取文件名
            String prefix = originalFilename.substring(0,cutPoint);
            //获取后缀名
            String suffix = originalFilename.substring(cutPoint + 1);
            //创建临时文件
            file = File.createTempFile(prefix, suffix);
            //multipartFile2file
            multipartFile.transferTo(file);
            //删除临时文件
            file.deleteOnExit();
        } catch (IOException e) {
            e.printStackTrace();
        }
        return file;
    }

    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, MultipartFile file) {

        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName);

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 上传文件流
        try (InputStream inputStream = new FileInputStream(transferToFile(file))) {
            //上传到OSS
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        // 关闭OSSClient。
        ossClient.shutdown();

        //返回文件在服务器上的全路径+名称
        return getRealName(objectName);
    }


    /**
     * 上传文件流
     *
     * @param oranFileName 上传到服务器上的文件路径和名称
     * @param file         来自本地的文件或者文件流
     */
    public static String uploadFileInputSteam(String oranFileName, File file) {

        // <yourObjectName>上传文件到OSS时需要指定包含文件后缀在内的完整路径,例如abc/efg/123.jpg
        String objectName = getRandomImageName(oranFileName);

        // 创建OSSClient实例。
        OSS ossClient = new OSSClientBuilder().build(endpoint, accessKeyId, accessKeySecret);

        // 上传文件流。
        try (InputStream inputStream = new FileInputStream(file);) {
            //上传到OSS
            ossClient.putObject(bucketName, objectName, inputStream);
        } catch (Exception ex) {
            ex.printStackTrace();
        }

        // 关闭OSSClient。
        ossClient.shutdown();

        //返回文件在服务器上的全路径+名称
        return getRealName(objectName);
    }

}

QRCodeUtil

package cn.rayfoo.utils;

import com.google.zxing.*;
import com.google.zxing.client.j2se.BufferedImageLuminanceSource;
import com.google.zxing.common.BitMatrix;
import com.google.zxing.common.HybridBinarizer;
import com.google.zxing.qrcode.decoder.ErrorCorrectionLevel;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.RoundRectangle2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.Hashtable;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/13 5:20 下午
 * @Description: 二维码工具类
 */


public class QRCodeUtil {

    //编码
    private static final String CHARSET = "utf-8";
    //文件格式
    private static final String FORMAT_NAME = "JPG";
    // 二维码尺寸
    private static final int QRCODE_SIZE = 300;
    // LOGO宽度
    private static final int WIDTH = 60;
    // LOGO高度
    private static final int HEIGHT = 60;

    /**
     * 生成二维码
     * @param content 内容
     * @param imgPath logo
     * @param needCompress 是否需要压缩
     * @return java.awt.image.BufferedImage
     * @throws Exception
     */
    private static BufferedImage createImage(String content, String imgPath, boolean needCompress) throws Exception {
        Hashtable hints = new Hashtable();
        hints.put(EncodeHintType.ERROR_CORRECTION, ErrorCorrectionLevel.H);
        hints.put(EncodeHintType.CHARACTER_SET, CHARSET);
        hints.put(EncodeHintType.MARGIN, 1);
        BitMatrix bitMatrix = new MultiFormatWriter().encode(content, BarcodeFormat.QR_CODE, QRCODE_SIZE, QRCODE_SIZE,
                hints);
        int width = bitMatrix.getWidth();
        int height = bitMatrix.getHeight();
        BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
        for (int x = 0; x < width; x++) {
            for (int y = 0; y < height; y++) {
                image.setRGB(x, y, bitMatrix.get(x, y) ? 0xFF000000 : 0xFFFFFFFF);
            }
        }
        if (imgPath == null || "".equals(imgPath)) {
            return image;
        }
        // 插入图片
        QRCodeUtil.insertImage(image, imgPath, needCompress);
        return image;
    }

    /**
     * 插入logo
     * @param source 二维码图片
     * @param imgPath logo路径
     * @param needCompress 是否压缩
     * @throws Exception
     */
    private static void insertImage(BufferedImage source, String imgPath, boolean needCompress) throws Exception {
        File file = new File(imgPath);
        if (!file.exists()) {
            System.err.println("" + imgPath + "   该文件不存在!");
            return;
        }
        Image src = ImageIO.read(new File(imgPath));
        int width = src.getWidth(null);
        int height = src.getHeight(null);
        if (needCompress) { // 压缩LOGO
            if (width > WIDTH) {
                width = WIDTH;
            }
            if (height > HEIGHT) {
                height = HEIGHT;
            }
            Image image = src.getScaledInstance(width, height, Image.SCALE_SMOOTH);
            BufferedImage tag = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            Graphics g = tag.getGraphics();
            g.drawImage(image, 0, 0, null); // 绘制缩小后的图
            g.dispose();
            src = image;
        }
        // 插入LOGO
        Graphics2D graph = source.createGraphics();
        int x = (QRCODE_SIZE - width) / 2;
        int y = (QRCODE_SIZE - height) / 2;
        graph.drawImage(src, x, y, width, height, null);
        Shape shape = new RoundRectangle2D.Float(x, y, width, width, 6, 6);
        graph.setStroke(new BasicStroke(3f));
        graph.draw(shape);
        graph.dispose();
    }

    public static void mkdirs(String destPath) {
        File file = new File(destPath);
        // 当文件夹不存在时,mkdirs会自动创建多层目录,区别于mkdir.(mkdir如果父目录不存在则会抛出异常)
        if (!file.exists() && !file.isDirectory()) {
            file.mkdirs();
        }
    }


    /**
     * 生成二维码,输出到指定路径
     * @param content 内容
     * @param imgPath logo路径
     * @param destPath 输出路径
     * @param needCompress 是否压缩
     * @throws Exception
     */
    public static void encode(String content, String imgPath, String destPath, boolean needCompress) throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
        mkdirs(destPath);
        // String file = new Random().nextInt(99999999)+".jpg";
        // ImageIO.write(image, FORMAT_NAME, new File(destPath+"/"+file));
        ImageIO.write(image, FORMAT_NAME, new File(destPath));
    }

    /**
     * 生成二维码,获得到输入流 log内嵌
     * @param content 内容
     * @param imgPath logo路径
     * @param output 输入流
     * @param needCompress 是否压缩
     * @throws Exception
     */
    public static void encode(String content, String imgPath, OutputStream output, boolean needCompress)
            throws Exception {
        BufferedImage image = QRCodeUtil.createImage(content, imgPath, needCompress);
        ImageIO.write(image, FORMAT_NAME, output);
    }

    /**
     * 获取指定的logo文件输入流
     * @param logoPath logo路径
     * @return java.io.InputStream
     */
    public static InputStream getResourceAsStream(String logoPath) {
        return QRCodeUtil.class.getResourceAsStream(logoPath);
    }

    /**
     * 将要解析的二维码的存放路径
     * @param file 要解析的二维码
     * @return
     * @throws Exception
     */
    public static String decode(File file) throws Exception {
        BufferedImage image;
        image = ImageIO.read(file);
        if (image == null) {
            return null;
        }
        BufferedImageLuminanceSource source = new BufferedImageLuminanceSource(image);
        BinaryBitmap bitmap = new BinaryBitmap(new HybridBinarizer(source));
        Result result;
        Hashtable hints = new Hashtable();
        hints.put(DecodeHintType.CHARACTER_SET, CHARSET);
        result = new MultiFormatReader().decode(bitmap, hints);
        String resultStr = result.getText();
        return resultStr;
    }

    /**
     * 解析二维码
     * @param path 要解析的二维码路径
     * @return
     * @throws Exception
     */
    public static String decode(String path) throws Exception {
        return QRCodeUtil.decode(new File(path));
    }
}

FtpUtil

package cn.rayfoo.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 java.io.*;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/21 9:01 上午
 * @Description:
 */
public class FtpUtil {

    /**
     * FTP服务器hostname
     */
    private static String HOST = "59.110.218.81";
    /**
     * FTP服务器端口
     */
    private static int PORT = 21;
    /**
     * FTP登录账号
     */
    private static String USERNAME = "root";
    /**
     * FTP登录密码
     */
    private static String PASSWORD = "root";
    /**
     * FTP服务器基础目录 可以不填
     */
    private static String BASEPATH = "";
    /**
     * FTP客户端
     */
    private static FTPClient ftp;

    /**
     * @Description 初始化FTP客户端
     * @Author xw
     * @Date 12:34 2020/2/5
     * @Param []
     * @return boolean
     **/
    public static boolean initFtpClient(){
        ftp = new FTPClient();
        int reply;
        try {
            // 连接FTP服务器
            ftp.connect(HOST, PORT);
            //登录, 如果采用默认端口,可以使用ftp.connect(host)的方式直接连接FTP服务器
            ftp.login(USERNAME, PASSWORD);
            ftp.setBufferSize(10240);
            //设置传输超时时间为60秒
            ftp.setDataTimeout(600000);
            //连接超时为60秒
            ftp.setConnectTimeout(600000);
            //FTP以二进制形式传输
            ftp.setFileType(FTP.BINARY_FILE_TYPE);
            reply = ftp.getReplyCode();
            if (!FTPReply.isPositiveCompletion(reply)) {
                ftp.disconnect();
                return false;
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return true;
    }


    /**
     * Description: 向FTP服务器上传文件
     * @param filePath FTP服务器文件存放路径。例如分日期存放:/2015/01/01。文件的路径为basePath+filePath
     * @param filename 上传到FTP服务器上的文件名
     * @param input 本地要上传的文件的 输入流
     * @return 成功返回true,否则返回false
     */
    public static boolean uploadFile(String filePath, String filename, InputStream input) {
        boolean result = false;
        try {
            filePath = new String(filePath.getBytes("GBK"),"iso-8859-1");
            filename = new String(filename.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return result;
            };
            //切换到上传目录
            ftp.enterLocalPassiveMode();
            if (!ftp.changeWorkingDirectory(BASEPATH+filePath)) {
                //如果目录不存在创建目录
                String[] dirs = filePath.split("/");
                String tempPath = BASEPATH;
                for (String dir : dirs) {
                    if (null == dir || "".equals(dir)){
                        continue;
                    }
                    tempPath += "/" + dir;
                    if (!ftp.changeWorkingDirectory(tempPath)) {
                        if (!ftp.makeDirectory(tempPath)) {
                            return result;
                        } else {
                            ftp.changeWorkingDirectory(tempPath);
                        }
                    }
                }
            }
            //设置上传文件的类型为二进制类型
            ftp.setFileType(FTP.BINARY_FILE_TYPE);
            //上传文件
            ftp.enterLocalPassiveMode();
            if (!ftp.storeFile(filename, input)) {
                return result;
            }
            input.close();
            ftp.logout();
            result = true;
        }
        catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return result;
    }

    /**
     * Description: 从FTP服务器下载文件
     * @param remotePath FTP服务器上的相对路径
     * @param fileName 要下载的文件名
     * @return
     */
    public static boolean downloadFile( String remotePath,String fileName,String localPath) {
        boolean result = false;

        try {
            remotePath = new String(remotePath.getBytes("GBK"),"iso-8859-1");
            fileName = new String(fileName.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return result;
            };
            // 转移到FTP服务器目录
            ftp.changeWorkingDirectory(remotePath);
            ftp.enterLocalPassiveMode();
            FTPFile[] fs = ftp.listFiles();
            for (FTPFile ff : fs) {
                if (ff.getName().equals(fileName)) {
                    ftp.enterLocalPassiveMode();
                    FileOutputStream outputStream = new FileOutputStream(new File(localPath));
                    ftp.retrieveFile(remotePath+"/"+fileName,outputStream);
                    result = true;
                    outputStream.close();
                }
            }
            ftp.logout();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return result;
    }

    /**
     * @Description 从ftp服务器下载文件到指定输出流
     * @Author xw
     * @Date 22:30 2020/3/5
     * @Param [remotePath, fileName, outputStream]
     * @return boolean
     **/
    public static boolean downloadFile(String remotePath, String fileName, OutputStream outputStream) {
        boolean result = false;
        try {
            remotePath = new String(remotePath.getBytes("GBK"),"iso-8859-1");
            fileName = new String(fileName.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return result;
            };
            // 转移到FTP服务器目录
            ftp.changeWorkingDirectory(remotePath);
            ftp.enterLocalPassiveMode();
            FTPFile[] fs = ftp.listFiles();
            for (FTPFile ff : fs) {
                if (ff.getName().equals(fileName)) {
                    ftp.enterLocalPassiveMode();
                    ftp.retrieveFile(remotePath+"/"+fileName,outputStream);
                    result = true;
                }
            }
            ftp.logout();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return result;
    }

    /**
     * @Description 删除文件
     * @Author xw
     * @Date 11:38 2020/2/6
     * @Param [remotePath, fileName]
     * @return void
     **/
    public static boolean deleteFile( String remotePath,String fileName){
        boolean flag = false;
        try {
            remotePath = new String(remotePath.getBytes("GBK"),"iso-8859-1");
            fileName = new String(fileName.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return flag;
            };
            // 转移到FTP服务器目录
            ftp.changeWorkingDirectory(remotePath);
            ftp.enterLocalPassiveMode();
            FTPFile[] fs = ftp.listFiles();
            for (FTPFile ff : fs) {
                if ("".equals(fileName)){
                    return flag;
                }
                if (ff.getName().equals(fileName)){
                    String filePath = remotePath + "/" +fileName;
                    ftp.deleteFile(filePath);
                    flag = true;
                }
            }
            ftp.logout();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return flag;
    }

    /**
     * @Description 删除文件夹
     * @Author xw
     * @Date 11:38 2020/2/6
     * @Param [remotePath, fileName]
     * @return void
     **/
    public static boolean deleteFolder( String remotePath){
        boolean flag = false;
        try {
            remotePath = new String(remotePath.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return flag;
            };
            // 转移到FTP服务器目录
            ftp.changeWorkingDirectory(remotePath);
            ftp.enterLocalPassiveMode();
            FTPFile[] fs = ftp.listFiles();
            if (fs.length==0){
                ftp.removeDirectory(remotePath);
                flag = true;
            }
            ftp.logout();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return flag;
    }

    /**
     * @Description 修改文件名称或者文件夹名
     * @Author xw
     * @Date 21:18 2020/2/11
     * @Param [oldAllName, newAllName]
     * @return boolean
     **/
    public static boolean reNameFile( String oldAllName,String newAllName){
        boolean flag = false;
        try {
            oldAllName = new String(oldAllName.getBytes("GBK"),"iso-8859-1");
            newAllName = new String(newAllName.getBytes("GBK"),"iso-8859-1");
            if (!initFtpClient()){
                return flag;
            };
            ftp.enterLocalPassiveMode();
            ftp.rename(oldAllName,newAllName);
            flag = true;
            ftp.logout();
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (ftp.isConnected()) {
                try {
                    ftp.disconnect();
                } catch (IOException ioe) {
                }
            }
        }
        return flag;
    }

    /**
     * 私有化构造
     */
    private FtpUtil(){}

}

cn.rayfoo.web包中创建了BaseController和FileUploadController,原本只想写文件上传,一不小心给文件下载和分享也写了,所以不要纠结类名啦~

BaseController

package cn.rayfoo.web;

import cn.rayfoo.common.Protocol;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.bind.annotation.ModelAttribute;
import org.springframework.web.multipart.MultipartFile;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.servlet.http.HttpSession;
import java.util.UUID;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/20 4:49 下午
 * @Description: controller的公共内容  这里为了简化FileUploadController的代码,将非请求方法都放入了baseController中
 */
public class BaseController {

    /**
     * 日志记录
     */
    Logger logger = LoggerFactory.getLogger(BaseController.class);

    /**
     * 用于初始化请求、响应、session
     */
    protected HttpServletResponse response;
    protected HttpServletRequest request;
    protected HttpSession session;

    //server上传的基础路径
    protected String fieldPath = "/upload/";

    /**
     * 在所有请求执行之前执行 初始化一些参数
     * @param request
     * @param response
     */
    @ModelAttribute
    protected void init(HttpServletRequest request,HttpServletResponse response){
        this.request = request;
        this.response = response;
        this.session = request.getSession();
    }

    /**
     * 获取服务器的url
     * @return
     */
    protected String getServerURL(){
        return Protocol.COMMON_HTTP.getProtocolType() + request.getLocalAddr() + ":" + request.getLocalPort() + request.getContextPath();
    }

    /**
     * 获取服务器的一个文件夹路径 会在末尾加上/
     * @param dirName 文件夹名称
     * @return
     */
    protected String getServerPath(String dirName){
        return session.getServletContext().getRealPath( dirName) + "/";
    }

    /**
     * 获取服务器上的某个文件路径
     * @param dirName 文件夹名称
     * @param fileName 文件名称
     * @return
     */
    protected String getServerFile(String dirName,String fileName){
        return getServerPath(dirName) + fileName;
    }

    /**
     * 使用uuid替换文件名
     * @param file MultipartFile对象
     * @return
     */
    protected String replaceFileName(MultipartFile file){
        //获取文件名
        String fileName = file.getOriginalFilename();
        //获取文件后缀名
        String suffix = fileName.substring(fileName.lastIndexOf("."));
        //使用uuid替换文件名
        fileName = UUID.randomUUID().toString().replace("-", "") + suffix;
        //返回文件名
        return fileName;
    }

}

FileUploadController

package cn.rayfoo.web;

import cn.rayfoo.utils.AliyunOSSUtil;
import cn.rayfoo.utils.FtpUtil;
import cn.rayfoo.utils.QRCodeUtil;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;

import java.io.*;
import java.net.URLEncoder;
import java.util.UUID;

/**
 * @Author: rayfoo@qq.com
 * @Date: 2020/7/20 4:22 下午
 * @Description:
 */
@Controller
public class FileUploadController extends BaseController {

    @PostMapping("/serverUpload")
    @ResponseBody
    public String fileUpload1(@RequestParam("file") MultipartFile file) {
        //如果文件为空 返回错误信息
        if(file.isEmpty()){
            return "file not found";
        }
        //获取路径
        String uploadPath = getServerPath(fieldPath);
        //创建文件对象
        File dir = new File(uploadPath);
        //判断文件夹是否存在
        if (!dir.exists()) {
            //不存在创建文件夹
            dir.mkdir();
        }
        //使用UUID替换文件名
        String fileName = replaceFileName(file);
        //文件上传逻辑
        try {
            //文件上传
            file.transferTo(new File(dir, fileName));
            //记录日志
            logger.debug("文件上传成功:" + uploadPath + fileName);
            //文件保存在服务器的URL
            String fileURL = getServerURL() + fieldPath + fileName;
            //生成二维码
            QRCodeUtil.encode(fileURL, getServerFile("/img/", "logo.jpg"), getServerFile(fieldPath, fileName + ".png"), true);
            //返回文件的url,二维码的url=文件url+.png
            return "文件上传成功:" + fileURL;
        } catch (Exception ex) {
            ex.printStackTrace();
            //记录日志
            logger.debug("文件上传出现异常:" + ex.getMessage());
            //返回错误信息
            return "文件上传出现异常:" + ex.getMessage();
        }
    }

    @PostMapping("/ftpUpload")
    @ResponseBody
    public String ftpUpload(MultipartFile file) {
        //如果文件为空 返回错误信息
        if(file.isEmpty()){
            return "file not found";
        }
        //获取文件名
        String fileName = replaceFileName(file);
        //上传的目录,如果没有回会动创建
        String filePath = "/img";
        try {
            //上传文件
            boolean reslut = FtpUtil.uploadFile(filePath, fileName, file.getInputStream());
            //判断是否上传成功
            if (reslut) {
                logger.debug("通过ftp文件上传成功!");
                return "success";
            }
            logger.debug("ftp文件上传失败!");
            return "error";
        } catch (IOException e) {
            e.printStackTrace();
            logger.debug("ftp上传服务器发生异常!");
            return "error";
        }
    }

    @GetMapping(value = "/ftpDownload/{fileId}")
    @ResponseBody
    public String ftpDownload(@PathVariable Integer fileId) {
        //校验是否有ftp权限。。。
        //ftp目录,模拟下载  这里的文件路径和文件名是提前定义的 可以通过id从数据库中查到
        String filePath = "/img";
        //获取完整文件名
        String realName = "d795d3b7acd94a0aacba096aca1c2927.jpg";
        //去FTP上拉取
        try {
            OutputStream os = new BufferedOutputStream(response.getOutputStream());
            response.setCharacterEncoding("utf-8");
            // 设置返回类型
            response.setContentType("multipart/form-data");
            // 文件名转码一下,不然会出现中文乱码
            response.setHeader("Content-Disposition", "attachment;fileName=" + URLEncoder.encode(realName, "UTF-8"));
            //下载文件
            boolean flag = FtpUtil.downloadFile(filePath, realName, os);
            os.flush();
            os.close();
            if (flag) {
                logger.debug("ftp文件下载成功");
                return "success";
            }
            throw new RuntimeException("文件下载失败");
        } catch (Exception e) {
            e.printStackTrace();
            logger.debug("ftp文件下载失败:" + e.getMessage());
            return "error";
        }
    }


    @GetMapping(value = "/serverDownload/{fileName}/{fileType}")
    public ResponseEntity<byte[]> serverDownload(@PathVariable String fileName, @PathVariable String fileType) {
        //由于get请求无法正常携带.所以需要添加文件类型即后缀
        File file = new File(getServerPath(fieldPath) + fileName + "." + fileType);
        //创建字节数组,用于返回
        byte[] body = null;
        //初始化文件流
        InputStream is = null;
        try {
            is = new FileInputStream(file);
            //解析文件
            body = new byte[is.available()];
            //将文件解析为文件流
            is.read(body);
        } catch (Exception e) {
            e.printStackTrace();
            logger.debug("server文件下载出现异常:" + e.getMessage());
        }
        //设置响应头
        HttpHeaders headers = new HttpHeaders();
        headers.add("Content-Disposition", "attchement;filename=" + file.getName());
        //返回状态码、文件
        HttpStatus statusCode = HttpStatus.OK;
        ResponseEntity<byte[]> entity = new ResponseEntity<>(body, headers, statusCode);
        logger.debug("server文件已下载");
        return entity;
    }

    @PostMapping("/aliOSSUpload")@ResponseBody
    public String aliOSSUpload(@RequestParam("file")MultipartFile file){
        //如果文件为空 返回错误信息
        if(file.isEmpty()){
            return "file not found";
        }
        //获取原文件名
        String fileName = replaceFileName(file);

        //返回图片的url
        String fileURL = AliyunOSSUtil.uploadFileInputSteam(fileName,file);
        logger.debug("阿里云OSS文件上传成功!");
        try {
            //生成二维码  由于阿里云OSS工具类又在文件之前加了UUID,所以可能会导致文件名和二维码不一致
            QRCodeUtil.encode(fileURL, getServerFile("/img/", "logo.jpg"), getServerFile(fieldPath, fileName + ".png"), true);
            logger.debug("阿里云OSS文件二维码生成成功!");
            //需要将二维码上传到服务器,可以使用getServerFile(fieldPath, fileName + ".png"创建File对象,使用AliyunOSSUtil.uploadFileInputSteam(fileName,file);上传
        } catch (Exception e) {
            e.printStackTrace();
            logger.debug("阿里云OSS文件上传失败:" + e.getMessage());
            return "error";
        }
        //在URL显示文件的URL,可以直接通过URL下载
        return fileURL;
    }

}

前端页面

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>文件上传Demo</title>
    <link rel="stylesheet"  href="css/bootstrap.min.css">
    <script type="application/javascript" src="js/bootstrap.min.js"></script>
    <style>
        #img{
             100px;
            height: 100px;
        }
    </style>
</head>
<body>
    <img id="img" src="" alt="" class="img-thumbnail img-responsive">
    <h2>服务器文件上传</h2>
    <form action="/serverUpload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" onchange="uploadImg(this)">
        <input type="submit" id="submit">
    </form>
    <a href="/serverDownload/3badc9a7099f4a5aa5f5ca58cb9d7e01/jpg">文件下载模拟,真实场景是通过数据库将数据绑定至a标签</a>
    <hr>
    <h2>ftp文件上传</h2>
    <form action="/ftpUpload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" onchange="uploadImg(this)">
        <input type="submit">
    </form>
    <hr>
    <h2>OSS文件上传</h2>
    <form action="/aliOSSUpload" method="post" enctype="multipart/form-data">
        <input type="file" name="file" onchange="uploadImg(this)">
        <input type="submit">
    </form>

    <script>
        //选择图片,马上预览
        function uploadImg(obj) {
            var file = obj.files[0];
            //file.size 单位为byte  可以做上传大小控制
            if(file.size>10485760){
                alert("最大支持10M的图片!");
                //大于10M禁止上传,这里只做了一个处理,其他的方法相同。
                document.getElementById("submit").disabled = "disabled";
                return;
            }
            var reader = new FileReader();
            //读取文件过程方法
            reader.onloadstart = function (e) {
                console.log("开始读取....");
            }
            reader.onprogress = function (e) {
                console.log("正在读取中....");
            }
            reader.onabort = function (e) {
                console.log("中断读取....");
            }
            reader.onerror = function (e) {
                console.log("读取异常....");
            }
            reader.onload = function (e) {
                console.log("成功读取....");
                var img = document.getElementById("img");
                img.src = e.target.result;
                //或者 img.src = this.result;  //e.target == this
            }
            reader.readAsDataURL(file)
        }
    </script>
</body>
</html>

源码:gtiee地址github地址

原文地址:https://www.cnblogs.com/zhangruifeng/p/13355881.html