OSS上传文件返回前端上传进度三种方式
一、采用前端定时轮询请求接口
注意:采用session存放文件上传进度数据,适合jsp或者模板引擎页面,但是如果前后的分离,请采用redis或者MQ(消息队列)方式,将进度存入仓库中,然后通过标识读取数据,完成之后请重置或者删除标识。
1.1、上传文件
@Value("${oss.endpoint}")
private String endpoint;
@Value("${oss.accessKeyId}")
private String accessKeyId;
@Value("${oss.accessKeySecret}")
private String accessKeySecret;
@Value("${oss.bucketName}")
private String bucketName;
@PostMapping("/upload")
public Object doUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file == null || file.isEmpty()) {
return BaseResponse.fail("上传的文件是空文件!");
}
String fileName = file.getOriginalFilename();
FileUtils utils = new FileUtils();
File files = null;
try {
files = File.createTempFile("tmp", null);
file.transferTo(files);
files.deleteOnExit();
} catch (Exception e) {
log.error("流转文件失败:{}",e);
}
String url = utils.doUpload(fileName, files, endpoint, accessKeyId, accessKeySecret, bucketName, request.getSession());
Map<String, Object> map = new HashMap<>(1);
map.put("url", url);
map.put("fileName", fileName);
return map;
}
1.2、获取进度
/** * 获取进度数据 * @param request * @return */
@GetMapping("/percent")
public Object getUploadPercent(HttpServletRequest request){
HttpSession session = request.getSession();
int percent = session.getAttribute("upload_percent") == null ? 0: (Integer)session.getAttribute("upload_percent");
Map<String, Object> map = new HashMap<>(1);
map.put("percent", percent);
return map;
}
/** * 重置上传进度 * @param request */
@GetMapping("/percent/reset")
public void resetPercent(HttpServletRequest request){
HttpSession session = request.getSession();
session.setAttribute("upload_percent",0);
}
1.3、上传文件工具类
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.model.PutObjectRequest;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
public class FileUtils {
public String doUpload(String key, File files, String endpoint, String accessKeyId, String accessKeySecret, String bucketName, HttpSession session) {
String keys = "test/"+key;
try {
ClientConfiguration config = new ClientConfiguration();
config.setProtocol(Protocol.HTTPS);
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret, config);
ossClient.putObject(new PutObjectRequest(bucketName, keys, files).withProgressListener(new PutObjectProgressListener(session)));
Date expiration = new Date(System.currentTimeMillis() + EXPIRE_TIME);
URL url = ossClient.generatePresignedUrl(bucketName, keys, expiration);
ossClient.shutdown();
return String.valueOf(url);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new IllegalStateException("文件上传到阿里云OSS服务报错!", e);
}
}
}
1.4、获取进度工具类
import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressEventType;
import com.aliyun.oss.event.ProgressListener;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpSession;
/** * ossClient.putObject, ossClient.getObject, ossClient.uploadPart方法支持进度条功能. * ossClient.uploadFile和ossClient.downloadFile方法不支持进度条功能. * 简单上传进度条监听器 * https://help.aliyun.com/document_detail/84796.html?spm=a2c4g.11186623.6.791.40554d83cDiWSN */
@Slf4j
public class PutObjectProgressListener implements ProgressListener {
private long bytesWritten = 0;
private long totalBytes = -1;
private HttpSession session;
private boolean succeed = false;
private int percent = 0;
public PutObjectProgressListener(HttpSession mSession) {
this.session = mSession;
session.setAttribute("upload_percent", percent);
}
@Override
public void progressChanged(ProgressEvent progressEvent) {
long bytes = progressEvent.getBytes();
ProgressEventType eventType = progressEvent.getEventType();
switch (eventType) {
case TRANSFER_STARTED_EVENT:
log.debug("Start to upload......");
break;
case REQUEST_CONTENT_LENGTH_EVENT:
this.totalBytes = bytes;
log.debug(this.totalBytes + " bytes in total will be uploaded to OSS");
break;
case REQUEST_BYTE_TRANSFER_EVENT:
this.bytesWritten += bytes;
if (this.totalBytes != -1) {
int percent = (int)(this.bytesWritten * 100.0 / this.totalBytes);
log.debug(bytes + " bytes have been written at this time, upload progress: " + percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
session.setAttribute("upload_percent", percent);
} else {
log.debug(bytes + " bytes have been written at this time, upload ratio: unknown" + "(" + this.bytesWritten + "/...)");
}
break;
case TRANSFER_COMPLETED_EVENT:
this.succeed = true;
log.debug("Succeed to upload, " + this.bytesWritten + " bytes have been transferred in total");
break;
case TRANSFER_FAILED_EVENT:
log.debug("Failed to upload, " + this.bytesWritten + " bytes have been transferred");
break;
default:
break;
}
}
public boolean isSucceed(){
return succeed;
}
}
二、采用stomp+websocket推送模式
2.1、依赖
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-messaging</artifactId>
<version>5.2.6.RELEASE</version>
</dependency>
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-websocket</artifactId>
<version>5.1.8.RELEASE</version>
</dependency>
2.2、客户端配置
import org.springframework.context.annotation.Configuration;
import org.springframework.messaging.simp.config.MessageBrokerRegistry;
import org.springframework.web.socket.config.annotation.AbstractWebSocketMessageBrokerConfigurer;
import org.springframework.web.socket.config.annotation.EnableWebSocketMessageBroker;
import org.springframework.web.socket.config.annotation.StompEndpointRegistry;
//注解开启STOMP协议来传输基于代理的消息,此时控制器支持使用@MessageMapping
@Configuration
@EnableWebSocketMessageBroker
public class WebSocketConfig extends AbstractWebSocketMessageBrokerConfigurer {
@Override
public void configureMessageBroker(MessageBrokerRegistry config) {
//topic用来广播,user用来实现p2p
config.enableSimpleBroker("/topic","/user");
}
@Override
public void registerStompEndpoints(StompEndpointRegistry registry) {
registry.addEndpoint("/webServer").withSockJS();
//注册两个STOMP的endpoint,分别用于广播和点对点
registry.addEndpoint("/queueServer").withSockJS();
}
}
2.3、上传文件
@Value("${oss.endpoint}")
private String endpoint;
@Value("${oss.accessKeyId}")
private String accessKeyId;
@Value("${oss.accessKeySecret}")
private String accessKeySecret;
@Value("${oss.bucketName}")
private String bucketName;
@PostMapping("/upload")
public Object doUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file == null || file.isEmpty()) {
return BaseResponse.fail("上传的文件是空文件!");
}
String fileName = file.getOriginalFilename();
FileUtils utils = new FileUtils();
File files = null;
try {
files = File.createTempFile("tmp", null);
file.transferTo(files);
files.deleteOnExit();
} catch (Exception e) {
log.error("流转文件失败:{}",e);
}
String url = utils.doUpload(fileName, files, endpoint, accessKeyId, accessKeySecret, bucketName, request.getSession());
Map<String, Object> map = new HashMap<>(1);
map.put("url", url);
map.put("fileName", fileName);
return map;
}
2.4、上传文件工具类
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.model.PutObjectRequest;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
public class FileUtils {
public String doUpload(String key, File files, String endpoint, String accessKeyId, String accessKeySecret, String bucketName, HttpSession session) {
String keys = "test/"+key;
try {
ClientConfiguration config = new ClientConfiguration();
config.setProtocol(Protocol.HTTPS);
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret, config);
ossClient.putObject(new PutObjectRequest(bucketName, keys, files).withProgressListener(new PutObjectProgressListener(session)));
Date expiration = new Date(System.currentTimeMillis() + EXPIRE_TIME);
URL url = ossClient.generatePresignedUrl(bucketName, keys, expiration);
ossClient.shutdown();
return String.valueOf(url);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new IllegalStateException("文件上传到阿里云OSS服务报错!", e);
}
}
}
2.5、推送方式
import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressEventType;
import com.aliyun.oss.event.ProgressListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.messaging.simp.SimpMessagingTemplate;
import org.springframework.stereotype.Service;
import javax.servlet.http.HttpSession;
/** * ossClient.putObject, ossClient.getObject, ossClient.uploadPart方法支持进度条功能. * ossClient.uploadFile和ossClient.downloadFile方法不支持进度条功能. * 简单上传进度条监听器 * https://help.aliyun.com/document_detail/84796.html?spm=a2c4g.11186623.6.791.40554d83cDiWSN */
@Slf4j
@Service
public class PutObjectProgressListener implements ProgressListener {
@Autowired
public SimpMessagingTemplate simpMessagingTemplate;
private long bytesWritten = 0;
private long totalBytes = -1;
private HttpSession session;
private boolean succeed = false;
private int percent = 0;
public PutObjectProgressListener(HttpSession mSession) {
this.session = mSession;
}
@Override
public void progressChanged(ProgressEvent progressEvent) {
long bytes = progressEvent.getBytes();
ProgressEventType eventType = progressEvent.getEventType();
switch (eventType) {
case TRANSFER_STARTED_EVENT:
log.debug("Start to upload......");
break;
case REQUEST_CONTENT_LENGTH_EVENT:
this.totalBytes = bytes;
log.debug(this.totalBytes + " bytes in total will be uploaded to OSS");
break;
case REQUEST_BYTE_TRANSFER_EVENT:
this.bytesWritten += bytes;
if (this.totalBytes != -1) {
int percent = (int)(this.bytesWritten * 100.0 / this.totalBytes);
log.debug(bytes + " bytes have been written at this time, upload progress: " + percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
// 通过websocket 推送
Map<String, Object> map = new HashMap<>(1);
map.put("percent", percent);
simpMessagingTemplate.convertAndSend("/topic/percent", map);
} else {
log.debug(bytes + " bytes have been written at this time, upload ratio: unknown" + "(" + this.bytesWritten + "/...)");
}
break;
case TRANSFER_COMPLETED_EVENT:
this.succeed = true;
log.debug("Succeed to upload, " + this.bytesWritten + " bytes have been transferred in total");
break;
case TRANSFER_FAILED_EVENT:
log.debug("Failed to upload, " + this.bytesWritten + " bytes have been transferred");
break;
default:
break;
}
}
public boolean isSucceed(){
return succeed;
}
}
三、采用Servlet3.1长轮询模式
3.1、长轮询获取进度
/** * guava中的Multimap,多值map,对map的增强,一个key可以保持多个value */
private Multimap<String, DeferredResult<Object>> watchRequests = Multimaps.synchronizedSetMultimap(HashMultimap.create());
/** * 前端长轮询 当接口返回304 一直请求 直到返回结果 */
@RequestMapping(value = "/watch", method = RequestMethod.GET, produces = "text/html")
public DeferredResult<Object> watch(HttpServletRequest request) {
log.info("Request received");
ResponseEntity<Object> NOT_MODIFIED_RESPONSE = new ResponseEntity<>(HttpStatus.NOT_MODIFIED);
DeferredResult<Object> deferredResult = new DeferredResult<>(5000L, NOT_MODIFIED_RESPONSE);
/** * 当deferredResult完成时(不论是超时还是异常还是正常完成),移除watchRequests中相应的watch key */
deferredResult.onCompletion(() -> {
log.info("remove key:" + request.getSession().getId());
watchRequests.remove(request.getSession().getId(), deferredResult);
});
deferredResult.onTimeout(()-> log.info("onTimeout()" ));
watchRequests.put(request.getSession().getId(), deferredResult);
log.info("Servlet thread released");
return deferredResult;
}
/** * 发布数据 请求接口方式 反射调用方式 * @param sessionId * @return */
@RequestMapping(value = "/publish", method = RequestMethod.GET, produces = "text/html")
public Object publish(@RequestParam("sessionId") String sessionId, @RequestParam("percent") Integer percent) {
if (watchRequests.containsKey(sessionId)) {
Collection<DeferredResult<Object>> deferredResults = watchRequests.get(sessionId);
//通知所有watch这个sessionId变更的长轮训配置变更结果
for (DeferredResult<Object> deferredResult : deferredResults) {
//deferredResult一旦执行了setResult()方法,就说明DeferredResult正常完成了,会立即把结果返回给客户端
Map<String, Object> map = new HashMap<>(2);
map.put("sessionId", sessionId);
map.put("percent", percent);
deferredResult.setResult(map);
}
}
Map<String, Object> map = new HashMap<>(2);
map.put("code", "success");
return map;
}
3.2、发布数据
import com.aliyun.oss.event.ProgressEvent;
import com.aliyun.oss.event.ProgressEventType;
import com.aliyun.oss.event.ProgressListener;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.stereotype.Component;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Method;
/** * ossClient.putObject, ossClient.getObject, ossClient.uploadPart方法支持进度条功能. * ossClient.uploadFile和ossClient.downloadFile方法不支持进度条功能. * 简单上传进度条监听器 * https://help.aliyun.com/document_detail/84796.html?spm=a2c4g.11186623.6.791.40554d83cDiWSN */
@Slf4j
@Component
public class PutObjectProgressListener implements ProgressListener {
@Autowired
private ApplicationContext applicationContext;
private long bytesWritten = 0;
private long totalBytes = -1;
private HttpSession session;
private boolean succeed = false;
private int percent = 0;
public PutObjectProgressListener(HttpSession mSession) {
this.session = mSession;
}
@Override
public void progressChanged(ProgressEvent progressEvent) {
long bytes = progressEvent.getBytes();
ProgressEventType eventType = progressEvent.getEventType();
switch (eventType) {
case TRANSFER_STARTED_EVENT:
log.debug("Start to upload......");
break;
case REQUEST_CONTENT_LENGTH_EVENT:
this.totalBytes = bytes;
log.debug(this.totalBytes + " bytes in total will be uploaded to OSS");
break;
case REQUEST_BYTE_TRANSFER_EVENT:
this.bytesWritten += bytes;
if (this.totalBytes != -1) {
int percent = (int)(this.bytesWritten * 100.0 / this.totalBytes);
log.debug(bytes + " bytes have been written at this time, upload progress: " + percent + "%(" + this.bytesWritten + "/" + this.totalBytes + ")");
publish(session.getId(), percent);
} else {
log.debug(bytes + " bytes have been written at this time, upload ratio: unknown" + "(" + this.bytesWritten + "/...)");
}
break;
case TRANSFER_COMPLETED_EVENT:
this.succeed = true;
log.debug("Succeed to upload, " + this.bytesWritten + " bytes have been transferred in total");
break;
case TRANSFER_FAILED_EVENT:
log.debug("Failed to upload, " + this.bytesWritten + " bytes have been transferred");
break;
default:
break;
}
}
public boolean isSucceed(){
return succeed;
}
/** * 通过反射调用接口 */
private void publish(String sessionId, Integer percent) {
Class<?> clazz = ReflectUtil.forName("cn.com.*.controller.upload.FileUploadController");
Object bean = applicationContext.getBean(clazz);
Method method = ReflectUtil.findMethod(clazz, "publish", String.class, Integer.class);
ReflectUtil.invokeMethod(method, bean, sessionId, percent);
}
}
3.3、上传文件
@Value("${oss.endpoint}")
private String endpoint;
@Value("${oss.accessKeyId}")
private String accessKeyId;
@Value("${oss.accessKeySecret}")
private String accessKeySecret;
@Value("${oss.bucketName}")
private String bucketName;
@PostMapping("/upload")
public Object doUpload(@RequestParam("file") MultipartFile file, HttpServletRequest request) {
if (file == null || file.isEmpty()) {
return BaseResponse.fail("上传的文件是空文件!");
}
String fileName = file.getOriginalFilename();
FileUtils utils = new FileUtils();
File files = null;
try {
files = File.createTempFile("tmp", null);
file.transferTo(files);
files.deleteOnExit();
} catch (Exception e) {
log.error("流转文件失败:{}",e);
}
String url = utils.doUpload(fileName, files, endpoint, accessKeyId, accessKeySecret, bucketName, request.getSession());
Map<String, Object> map = new HashMap<>(1);
map.put("url", url);
map.put("fileName", fileName);
return map;
}
3.4、上传文件工具类
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.common.comm.Protocol;
import com.aliyun.oss.model.PutObjectRequest;
import lombok.extern.slf4j.Slf4j;
import javax.servlet.http.HttpSession;
import java.io.*;
import java.net.URL;
import java.util.Date;
import java.util.concurrent.TimeUnit;
@Slf4j
public class FileUtils {
public String doUpload(String key, File files, String endpoint, String accessKeyId, String accessKeySecret, String bucketName, HttpSession session) {
String keys = "test/"+key;
try {
ClientConfiguration config = new ClientConfiguration();
config.setProtocol(Protocol.HTTPS);
OSSClient ossClient = new OSSClient(endpoint, accessKeyId, accessKeySecret, config);
ossClient.putObject(new PutObjectRequest(bucketName, keys, files).withProgressListener(new PutObjectProgressListener(session)));
Date expiration = new Date(System.currentTimeMillis() + EXPIRE_TIME);
URL url = ossClient.generatePresignedUrl(bucketName, keys, expiration);
ossClient.shutdown();
return String.valueOf(url);
} catch (Exception e) {
log.error(e.getMessage(), e);
throw new IllegalStateException("文件上传到阿里云OSS服务报错!", e);
}
}
}
3.5、反射工具
import org.springframework.context.ApplicationContext;
import java.beans.PropertyDescriptor;
import java.lang.reflect.Method;
/** * 反射调用工具类 */
public final class ReflectUtil {
/** * 调用Spring容器中bean的方法 * * @param applicationContext 容器对象 * @param methodDescriptor 方法描述符,类的全限定名#方法名称 * @param parameterTypes 方法参数类型列表 * @param params 方法参数列表 * @param <T> 返回的数据类型 * @return 调用结果 */
public static <T> T invoke(ApplicationContext applicationContext, String methodDescriptor,
Class<?>[] parameterTypes, Object... params) {
String[] array = methodDescriptor.split("#");
Class<?> beanClass = forName(array[0]);
Object bean = applicationContext.getBean(beanClass);
Method method = findMethod(beanClass, array[1], parameterTypes);
return invokeMethod(method, bean, params);
}
/** * 寻找类中的方法 * * @param targetClass 需要查找的类型 * @param methodName 方法名称 * @param parameterTypes 参数类型列表,必须严格一致 * @return 类中的方法 */
public static Method findMethod(Class<?> targetClass, String methodName, Class<?>... parameterTypes) {
try {
Method method = targetClass.getMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
/** * 反射调用方法 * * @param method 方法 * @param target 需要调用的对象 * @param parameters 方法参数 * @param <T> 返回的结果类型 * @return 调用结果 */
@SuppressWarnings("unchecked")
public static <T> T invokeMethod(Method method, Object target, Object... parameters) {
try {
return (T) method.invoke(target, parameters);
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
/** * 根据类的全限定名获取对应的class对象 * * @param className 类全限定名 * @param <T> 返回的class对象类型 * @return class对象 */
@SuppressWarnings("unchecked")
public static <T> Class<T> forName(String className) {
try {
return (Class<T>) Class.forName(className);
} catch (ClassNotFoundException e) {
throw new IllegalArgumentException(e);
}
}
/** * 调用javaBean的get方法 * * @param target javaBean实体 * @param prop 属性名称 * @param classOfT 该属性对应的类型 * @param <T> 该属性对应的类型 * @return 调用结果 */
@SuppressWarnings("unchecked")
public static <T> T invokeGetter(Object target, String prop, Class<T> classOfT) {
try {
PropertyDescriptor pd = new PropertyDescriptor(prop, target.getClass());
Object value = pd.getReadMethod().invoke(target);
if (value == null) {
return null;
}
if (classOfT.isInstance(value)) {
return (T) value;
} else {
throw new IllegalArgumentException("属性: " + prop + " 的类型不是: " + classOfT);
}
} catch (Exception e) {
throw new IllegalArgumentException(e);
}
}
private ReflectUtil() {
}
}
还没有评论,来说两句吧...