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>