SpringBoot实现oss文件的上传与下载

最近项目中需要通过OSS来实现文件的上传和下载以及根据oss文件(word模板)生成Word,特此记录,以便日后查阅。

一、相关概述

OSS对象存储
  阿里云对象存储服务(Object Storage Service,简称 OSS),是阿里云提供的海量、安全、低成本、高可靠的云存储服务。OSS可用于图片、音视频、日志等海量文件的存储。各种终端设备、Web网站程序、移动应用可以直接向OSS写入或读取数据。

OSS相关概念
   (1)Endpoint外网访问oss服务器的域名,通过该域名可以访问OSS服务的API,进行文件上传、下载等操作。

  (2)Bucket存储空间,是存储对象的容器,所有存储对象都必须隶属于某个存储空间。

  (3)Object对象对象是 OSS 存储数据的基本单元,也被称为 OSS 的文件(可以是.png,.txt等任意文件)。

  (4)AccessKey访问密钥,指的是访问身份验证中用到的 AccessKeyId 和 AccessKeySecret。

二、准备工作

1.开通OSS服务

开通阿里云的OSS服务,开通之后创建Bucket并设置AccessKey ID。
得到相关的配置信息:endPoint、accessKeyId、accessKeySecret、bucketName。

注册阿里云:

https://www.aliyun.com/?utm_content=se_1007692031

有账号的直接登录。

开通OSS:

https://oss.console.aliyun.com/?spm=5176.8466032.recommends.doss.28131450C6FqWR

如何创建Bucket和设置AccessKey ID?
答: 可以参考开通阿里云服务存储OSS中的介绍,介绍的挺详细的,这里就不在累赘,感谢该博主的分享。

2.创建SpringBoot项目

快捷创建链接地址:https://start.spring.io/

三、代码实现

1.实现oss文件上传与下载

官方文档:https://help.aliyun.com/document_detail/32009.html

1.1 添加必要依赖

       <!-- web支持 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- OSS SDK 相关依赖 --><dependency><groupId>com.aliyun.oss</groupId><artifactId>aliyun-sdk-oss</artifactId><version>3.6.0</version></dependency><!-- 文件上传 --><dependency><groupId>commons-fileupload</groupId><artifactId>commons-fileupload</artifactId><version>1.3.3</version></dependency><dependency><groupId>commons-io</groupId><artifactId>commons-io</artifactId><version>2.4</version></dependency><!-- word导出--><!-- poi-tl是基于Apache POI的Word模板引擎。poi-tl依赖的是poi3.16版本 --><dependency><groupId>com.deepoove</groupId><artifactId>poi-tl</artifactId><version>1.7.3</version></dependency><!--  上面需要的依赖--><dependency><groupId>org.apache.poi</groupId><artifactId>poi-ooxml</artifactId><version>4.1.2</version></dependency><dependency><groupId>org.apache.poi</groupId><artifactId>poi</artifactId><version>4.1.2</version></dependency><!-- 对JSP的支持 --><dependency><groupId>javax.servlet</groupId><artifactId>javax.servlet-api</artifactId></dependency><dependency><groupId>org.apache.tomcat.embed</groupId><artifactId>tomcat-embed-jasper</artifactId></dependency><dependency><groupId>javax.servlet</groupId><artifactId>jstl</artifactId></dependency>

application.properties:


#设置Tomcat端口号,默认8083
server.port=8085
#设置项目ContextPath  项目访问前缀
server.context-path=/
#设置Tomcat编码
server.tomcat.uri-encoding=UTF-8
#设置视图解析器路径
spring.mvc.view.prefix=/WEB-INF/views/
#设置视图解析器后缀
spring.mvc.view.suffix=.jsp#配置文件上传的文件大小限制
spring.servlet.multipart.maxFileSize=300MB
spring.servlet.multipart.maxRequestSize=500MB#静态资源路径
web.upload-path=D:/mimi/
spring.mvc.static-path-pattern=/**
spring.resources.static-locations=classpath\:/META-INF/resources/,classpath\:/resources/,classpath\:/static/,classpath\:/public/,file\:${web.upload-path}

1.2 创建OSS常量类

package com.example.ossdemo.common;/*** OSS 常量类* @author Administrator**/
public class OSSConstant {//oss对外服务的访问域名public static final String OSS_ENDPOINT = "http://oss-cn-shanghai.aliyuncs.com/";//访问身份验证中用到的用户标识public static final String OSS_ACCESSKEYID = "your AccessKeyId";//用户用于加密签名字符串和oss用来验证签名字符串的 密钥public static final String OSS_ACCESSKEYSECRET = "your AccessKeySecret";//oss的存储空间public static final String OSS_BUCKET = "cf-001";//阿里云OSS文件地址public static final String OSS_PIC_URL = "http://cf-001.oss-cn-shanghai.aliyuncs.com/";
}

1.3添加OSS业务接口OssService

package com.example.ossdemo.service;import java.io.InputStream;import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;public interface OssService {/*** 文件上传* @param filepath* @param inputstream* @return*/public boolean uploadFile(String filepath, InputStream inputstream);/*** 下载oss文件* @param request* @param response*/public void downFile(HttpServletRequest request,HttpServletResponse response);
}

1.4 添加OSS业务接口OssService的实现类OssServiceImpl(含oss各种功能方法)

package com.example.ossdemo.service.impl;import java.io.BufferedOutputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.springframework.stereotype.Service;
import com.aliyun.oss.ClientConfiguration;
import com.aliyun.oss.OSSClient;
import com.aliyun.oss.model.ListObjectsRequest;
import com.aliyun.oss.model.OSSObject;
import com.aliyun.oss.model.OSSObjectSummary;
import com.aliyun.oss.model.ObjectListing;
import com.aliyun.oss.model.PutObjectResult;
import com.example.ossdemo.common.OSSConstant;
import com.example.ossdemo.service.OssService;@Service("OssService")
public class OssServiceImpl implements OssService {private String[] fileTypes = new String[]{"gif", "jpg", "jpeg", "png", "bmp"};/*** 上传文件处理逻辑*/@Overridepublic boolean uploadFile(String filepath, InputStream inputstream) {return upload(filepath, inputstream);}/*** oss文件下载处理逻辑*/@Overridepublic void downFile(HttpServletRequest request, HttpServletResponse response) {// oss文件地址String oss_url = request.getParameter("url");// 获取域名后面的内容String oss_domain = OSSConstant.OSS_PIC_URL;String file_name = oss_url.replace(oss_domain, "");// 获取oss文件byte[]byte[] oss_byte = getOssFileByteArray(file_name);// 后缀名String fileExt = oss_url.substring(oss_url.lastIndexOf(".") + 1).toLowerCase();try {// 清空responseresponse.reset();// 设置response的Headerresponse.setHeader("Content-Disposition","attachment;filename=" + new SimpleDateFormat("yyyyMMddHHmm").format(new Date()) + "." + fileExt);// response.addHeader("Content-Length", "" + file.length());OutputStream toClient = new BufferedOutputStream(response.getOutputStream());response.setContentType("application/octet-stream");toClient.write(oss_byte);// 以流的形式下载文件。toClient.flush();toClient.close();} catch (IOException ex) {ex.printStackTrace();}}/*** 上传文件*/public boolean upload(String filepath, InputStream inputstream) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filepath != null && !"".equals(filepath.trim())) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 上传ossClient.putObject(OSS_BUCKET, filepath, inputstream);result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件上传异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 列出oss bucket中的文件及目录** @param dirname 目录名称,如: image/ , image/20201224/*/public List<Map<String,Object>> list_file(String dirname) {// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;List<Map<String,Object>> fileList = new ArrayList<Map<String,Object>>();OSSClient ossClient = null;try {if (dirname != null && !"".equals(dirname.trim()) && dirname.endsWith("/")) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);final int maxKeys = 200;String nextMarker = null;ObjectListing objectListing = null;ListObjectsRequest listObjectsRequest = new ListObjectsRequest(OSS_BUCKET);listObjectsRequest.withMarker(nextMarker);listObjectsRequest.withMaxKeys(maxKeys);listObjectsRequest.withPrefix(dirname);objectListing = ossClient.listObjects(listObjectsRequest);List<OSSObjectSummary> sums = objectListing.getObjectSummaries();SimpleDateFormat sf = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss");for (OSSObjectSummary s : sums) {String key = s.getKey();String lastmodifytime = sf.format(s.getLastModified());System.out.println(key);long size = s.getSize();if (!key.equals(dirname)) {Map<String,Object> file = new HashMap<String, Object>();file.put("lastmodifytime",lastmodifytime);//最新修改时间file.put("filesize",size);//文件尺寸String sub_filepath = key.substring((key.indexOf(dirname) + dirname.length()), key.length());int count = appearNumber(sub_filepath, "/");if (count == 1 && sub_filepath.endsWith("/")) { // 子目录file.put("is_dir",true);//是否目录boolean hasfile = check_has_file(sums, dirname + sub_filepath);file.put("has_file",hasfile);//是否含有子文件file.put("is_photo",false);//是否图片file.put("filetype","");//文件类型String filename = sub_filepath.substring(0, sub_filepath.length() - 1);file.put("filename",filename);//文件名fileList.add(file);} else if (count == 0) { // 文件file.put("is_dir",false);//是否目录file.put("has_file",false);//是否含有子文件String fileExt = key.substring(key.lastIndexOf(".") + 1).toLowerCase();file.put("is_photo",Arrays.<String>asList(fileTypes).contains(fileExt));//是否图片file.put("fileExt",fileExt);//文件类型file.put("filename",sub_filepath);//文件名fileList.add(file);}}}nextMarker = objectListing.getNextMarker();}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("获取文件列表异常");} finally {if (ossClient != null) {// 关闭clientossClient.shutdown();}}return fileList;}/*** public int indexOf(int ch, int fromIndex) 返回在此字符串中第一次出现指定字符处的索引,从指定的索引开始搜索* * @param srcText* @param findText* @return*/public static int appearNumber(String srcText, String findText) {int count = 0;int index = 0;while ((index = srcText.indexOf(findText, index)) != -1) {index = index + findText.length();count++;}return count;}private boolean check_has_file(List<OSSObjectSummary> sums, String dirname) {boolean result = false;for (OSSObjectSummary s : sums) {String path = s.getKey();if (!dirname.equals(path) && path.startsWith(dirname)) {result = true;break;}}return result;}/*** 检验oss bucket 某个目录下是否存在文件** @param dirname 目录名称,如: image/ , image/20201224/*/public boolean check_is_empty(String dirname) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (dirname != null && !"".equals(dirname.trim()) && dirname.endsWith("/")) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 列举ObjectObjectListing objectListing = ossClient.listObjects(OSS_BUCKET, dirname);List<OSSObjectSummary> sums = objectListing.getObjectSummaries();if (sums.size() > 0) {result = true;}System.out.println("检索目录:" + dirname + " 是否存在:" + result);}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("校验文件异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 检验oss bucket中是否存在目录** @param dirname 目录名称,如: image/ , image/20201224/*/public boolean check_dir_exist(String dirname) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (dirname != null && !"".equals(dirname.trim()) && dirname.endsWith("/")) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// Object是否存在result = ossClient.doesObjectExist(OSS_BUCKET, dirname.trim());System.out.println("检索目录:" + dirname + " 是否存在:" + result);}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件校验异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 在oss bucket中创建目录** @param dirname 目录名称,如: image/ , image/20201224/*/public boolean create_dir(String dirname) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (dirname != null && !"".equals(dirname.trim()) && dirname.endsWith("/")) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);PutObjectResult r = ossClient.putObject(OSS_BUCKET, dirname, new ByteArrayInputStream(new byte[0]));result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("目录创建异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 上传文件*/public boolean upload(String filepath, FileInputStream fileinputstream) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filepath != null && !"".equals(filepath.trim())) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 上传ossClient.putObject(OSS_BUCKET, filepath, fileinputstream);result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件上传异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 上传文件*/public boolean upload(String filepath, byte[] filecontent) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filepath != null && !"".equals(filepath.trim())) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 上传ossClient.putObject(OSS_BUCKET, filepath, new ByteArrayInputStream(filecontent));result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件上传异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 上传文件*/public boolean upload(String filepath, ByteArrayInputStream bytestream) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filepath != null && !"".equals(filepath.trim())) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 上传ossClient.putObject(OSS_BUCKET, filepath, bytestream);result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件上传异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 删除bucket中的文件*/public boolean delete(String filename) {boolean result = false;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filename != null && !"".equals(filename.trim())) {filename = filename.trim();String key = filename.substring(OSS_BUCKET.length() - 1, filename.length());// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 删除ossClient.deleteObject(OSS_BUCKET, key);result = true;}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件删除异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 获取oss文件byte[]*/public byte[] getOssFileByteArray(String filepath) {byte[] result = null;// 初始化配置参数String OSS_ENDPOINT = OSSConstant.OSS_ENDPOINT;String OSS_ACCESSKEYID = OSSConstant.OSS_ACCESSKEYID;String OSS_ACCESSKEYSECRET = OSSConstant.OSS_ACCESSKEYSECRET;String OSS_BUCKET = OSSConstant.OSS_BUCKET;OSSClient ossClient = null;try {if (filepath != null && !"".equals(filepath.trim())) {// 创建ClientConfiguration实例,按照您的需要修改默认参数ClientConfiguration conf = new ClientConfiguration();// 开启支持CNAME选项conf.setSupportCname(true);ossClient = new OSSClient(OSS_ENDPOINT, OSS_ACCESSKEYID, OSS_ACCESSKEYSECRET, conf);// 上传OSSObject ossObj = ossClient.getObject(OSS_BUCKET, filepath);if (ossObj != null) {InputStream is = ossObj.getObjectContent();result = InputStreamToByteArray(is);}}} catch (Exception e) {e.printStackTrace();throw new RuntimeException("文件下载异常");} finally {// 关闭clientossClient.shutdown();}return result;}/*** 1.图片转为字节数组 * 图片到程序FileInputStream * 程序到数组 ByteArrayOutputStream*/public static byte[] InputStreamToByteArray(InputStream is) {// 1.创建源与目的的byte[] dest = null;// 在字节数组输出的时候是不需要源的。// 2.选择流,选择文件输入流ByteArrayOutputStream os = null;// 新增方法try {os = new ByteArrayOutputStream();// 3.操作,读文件byte[] flush = new byte[1024 * 10];// 10k,创建读取数据时的缓冲,每次读取的字节个数。int len = -1;// 接受长度;while ((len = is.read(flush)) != -1) {// 表示当还没有到文件的末尾时// 字符数组-->字符串,即是解码。os.write(flush, 0, len);// 将文件内容写出字节数组}os.flush();return os.toByteArray();} catch (FileNotFoundException e) {// TODO Auto-generated catch blocke.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {// 4.释放资源try {if (is != null) {// 表示当文打开时,才需要通知操作系统关闭is.close();}} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();}}return null;}/*** 2.将字节数组还原为文件* 字节数组到程序ByteArrayInputStream* 程序到文件。FileOutputStream*/public static void byteArrayToFile(byte[] src, String FilePath) {//1.创建源File dest = new File(FilePath);//该文件是不存在的,会在相应的路径下创建// 2.选择流FileOutputStream os = null;ByteArrayInputStream is = null;try {//3.操作os = new FileOutputStream(dest);is = new ByteArrayInputStream(src);//将内容写出byte[] flush = new byte[1024 * 10];//创建读取数据时的缓冲,每次读取的字节个数。int len = -1;//接受长度;while ((len = is.read(flush)) != -1) {//表示当还没有到文件的末尾时//字符数组-->字符串,即是解码。os.write(flush, 0, len);//}os.flush();} catch (FileNotFoundException e) {e.printStackTrace();} catch (IOException e) {// TODO Auto-generated catch blocke.printStackTrace();} finally {//4.释放资源try {if (null != os) {os.close();}} catch (Exception e) {// TODO: handle exception}}}}

1.5添加OSSFileUploadController,实现oss文件上传、下载

package com.example.ossdemo.controller;import java.text.SimpleDateFormat;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import org.apache.commons.collections4.map.HashedMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.multipart.MultipartFile;
import org.springframework.web.servlet.ModelAndView;import com.example.ossdemo.common.OSSConstant;
import com.example.ossdemo.service.OssService;/*** oss文件上传、下载* * @author Administrator**/
@RequestMapping("/oss_file")
@RestController
public class OSSFileUploadController {@Autowiredprivate OssService ossService;//定义允许上传的文件扩展名private static HashMap<String, String> extMap = new HashMap<String, String>();static {extMap.put("image", "gif,jpg,jpeg,png,bmp");extMap.put("flash", "swf,flv");extMap.put("media", "swf,flv,mp3,wav,wma,wmv,mid,avi,mpg,asf,rm,rmvb");extMap.put("file", "pdf,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2,java,php,exe,gif,jpg,jpeg,png,bmp");}//最大文件大小 100Mlong maxSize = 104857600;//最大文件大小 10Mlong maxImages = 10485760;/*** 进入oss文件上传、下载页面* @return*/@RequestMapping("/index")public ModelAndView toIndex(){return new ModelAndView("oss/index");}/*** 图片上传* @param file* @return*/@RequestMapping(value = "/upload_img", method = RequestMethod.POST)public Map<String,Object> uploadImg(@RequestParam("file") MultipartFile file) {Map<String,Object> result = new HashedMap<String, Object>();//允许上传的文件扩展名String fileTypes = "gif,jpg,jpeg,png,bmp";String fileName = file.getOriginalFilename();String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();long fileSize = file.getSize();try {if (fileSize > maxImages) {result.put("code", "fail");result.put("msg", "上传图片大小超过限制" + (maxImages / 1024) + "KB");result.put("error", 1);// 富文本框图片上传,失败返回值} else if (!Arrays.<String>asList(fileTypes.split(",")).contains(fileExt)) {result.put("code", "fail");result.put("msg", "图片格式有误,\n只允许" + fileTypes + "格式。");result.put("error", 1);// 富文本框图片上传,失败返回值} else {// 生成新的文件名String newfilename = "image/";Date now = new Date();SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");newfilename += date.format(now) + "/";SimpleDateFormat time = new SimpleDateFormat("HHmmssSSS");newfilename += time.format(now);newfilename += "_" + new Random().nextInt(1000) + "." + fileExt;ossService.uploadFile(newfilename, file.getInputStream());//返回文件访问路径String url = OSSConstant.OSS_PIC_URL + newfilename;result.put("code", "success");result.put("url", url);result.put("error", 0);// 富文本框图片上传,成功返回值}} catch (Exception e) {result.put("code", "fail");result.put("msg", "系统异常");e.printStackTrace();}return result;}/*** 普通文件上传* @param file* @return*/@RequestMapping(value = "/upload_attachment", method = RequestMethod.POST)public Map<String,Object> uploadAttachment(@RequestParam("file") MultipartFile file,HttpServletRequest request) {Map<String,Object> result = new HashedMap<String, Object>();//允许上传的文件扩展名String fileTypes = "pdf,doc,docx,xls,xlsx,ppt,htm,html,txt,zip,rar,gz,bz2,java,php,exe,gif,jpg,jpeg,png,bmp";String fileName = file.getOriginalFilename();String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();long fileSize = file.getSize();try {if (fileSize > maxImages) {result.put("code", "fail");result.put("msg", "上传文件大小超过限制" + (maxImages / 1024) + "KB");} else if (!Arrays.<String>asList(fileTypes.split(",")).contains(fileExt)) {result.put("code", "fail");result.put("msg", "文件格式有误,\n只允许" + fileTypes + "格式。");} else {//生成新的文件名String newfilename = "file/";Date now = new Date();SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");newfilename += date.format(now) + "/";SimpleDateFormat time = new SimpleDateFormat("HHmmssSSS");newfilename += time.format(now);newfilename += "_" + new Random().nextInt(1000) + "." + fileExt;ossService.uploadFile(newfilename, file.getInputStream());//返回文件访问路径String url = OSSConstant.OSS_PIC_URL + newfilename;result.put("code", "success");result.put("url", url);}} catch (Exception e) {result.put("code", "fail");result.put("msg", "系统异常");e.printStackTrace();}return result;}  /*** oss文件下载*/@RequestMapping(value = "/downFile", method = {RequestMethod.GET})public void downFile(HttpServletRequest request,HttpServletResponse response){ossService.downFile(request, response);}
}

1.6 创建jsp文件,调用上传、下载方法

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%><!DOCTYPE html>
<html>
<head>
<!-- easyUI -->
<link rel="stylesheet" href="<%=basePath %>/js/jquery-easyui-1.7.0/themes/default/easyui.css">
<link rel="stylesheet" href="<%=basePath %>/js/jquery-easyui-1.7.0/themes/icon.css">
<link rel="stylesheet" href="<%=basePath %>/css/common.css">
<script type="text/javascript" src="<%=basePath %>/js/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="<%=basePath %>/js/jquery-easyui-1.7.0/jquery.easyui.min.js"></script>
<script type="text/javascript" src="<%=basePath %>/js/jquery-easyui-1.7.0/locale/easyui-lang-zh_CN.js"></script>
<!-- layer 弹出层 -->
<script type="text/javascript" src="<%=basePath %>/js/layer/layer.js"></script>
<link rel="stylesheet" href="<%=basePath %>/js/layui-v2.5.6/css/layui.css"><style>.m1{margin-left: 10px;                     }.s1{font-weight: bold;color:#1e9fff;}.m2{margin-bottom: 10px;}</style>
</head>
<body  class="easyui-layout"><h1 class="m1">OSS文件上传、下载:</h1><div class="easyui-tabs"style="width:700px;height:250px;margin: 10px 0 0 10px;"><div title="图片上传"style="padding:10px"><div class="m2"style="margin-top: 30px;font-weight: bold;">图片上传</div><div class="m2"style="margin-top: 30px;"><img alt="" src="" id="img_url"><input type="hidden" id="img_url2" value=""></div><a href="#" class="easyui-linkbutton" id="imgButton">图片上传</a></div><div title="普通文件上传"style="padding:10px"><div class="m2"style="margin-top: 30px;font-weight: bold;">普通文件上传</div><div class="m2"style="margin-top: 30px;">文件地址:<input type="text" id="url" class="easyui-textbox"style="width:600px;" value="" readonly="readonly"></div><a href="#" class="easyui-linkbutton"  id="fileButton">文件上传</a></div><div title="文件下载"style="padding:10px"><div class="m2"style="margin-top: 30px;font-weight: bold;">oss文件下载</div><div class="m2"style="margin-top: 30px;">文件地址:<input type="text" id="url3" class="easyui-textbox"style="width:600px;" value="http://xiaofang-001.oss-cn-shanghai.aliyuncs.com/file/20201216/093915477_23.docx" readonly="readonly"></div><a href="#" class="easyui-linkbutton"  onclick="downloadFile()">文件下载</a></div></div><script src="<%=basePath %>/js/layui-v2.5.6/layui.js" charset="utf-8"></script><script type="text/javascript">layui.use('upload', function(){var $ = layui.jquery,upload = layui.upload;var loading;//图片上传upload.render({elem: '#imgButton',url: '/oss_file/upload_img' //改成您自己的上传接口,accept: 'file' //普通文件 //指定允许上传时校验的文件类型,可选值有:images(图片)、file(所有文件)、video(视频)、audio(音频),acceptMime:'image/*'//规定打开文件选择框时,筛选出的文件类型,值为用逗号隔开的 MIME 类型列表。如:acceptMime: 'image/*'(只显示图片文件)acceptMime: 'image/jpg, image/png'(只显示 jpg 和 png 文件),before: function(obj){loading = layer.msg("上传中", {icon: 16,time: 10000000,shade: 0.3});},done: function(res){if (res.code == "success"){layer.close(loading);layer.msg('上传成功');console.log(res.url);$("#img_url").attr("src",res.url)$("#img_url2").val(res.url)}},error: function(res){//请求异常回调console.log(res)}});//普通文件上传upload.render({elem: '#fileButton',url: '/oss_file/upload_attachment' //改成您自己的上传接口,accept: 'file' //普通文件 //指定允许上传时校验的文件类型,可选值有:images(图片)、file(所有文件)、video(视频)、audio(音频)//,exts: 'docx|doc' //只允许上传word文件,before: function(obj){loading = layer.msg("上传中", {icon: 16,time: 10000000,shade: 0.3});},done: function(res){if (res.code == "success"){layer.close(loading);layer.msg('上传成功');console.log(res.url);$("#url").val(res.url)}}});});function downloadFile(){window.location.href='/oss_file/downFile?oss_url='+$("#url3").val();}</script>
</body>
</html>

2.实现Word生成

需求:将Word模板上传到oss中并入库,之后根据这个模板生成相应的销售合同Word,并把这个Word上传到oss,返回相应的oss文件地址并入库,以便之后使用(下载)。

2.1 编写word模板,并上传

Word模板上传见上面。 入库省略不讲。
word模板:

讲解下如何根据 oss文件生成Word并上传oss。

2.2添加导出word业务接口ExportWordService

package com.example.ossdemo.service;import java.util.Map;import javax.servlet.http.HttpServletRequest;public interface ExportWordService {/*** 导出订单合同信息* @param request*/public Map<String,Object> exportOrderContractInfo(HttpServletRequest request) throws Exception;
}

2.3 ExportWordService的实现类

package com.example.ossdemo.service.impl;import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.text.DecimalFormat;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Random;
import javax.servlet.http.HttpServletRequest;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.util.ClassUtils;
import com.deepoove.poi.XWPFTemplate;
import com.deepoove.poi.config.Configure;
import com.deepoove.poi.policy.HackLoopTableRenderPolicy;
import com.example.ossdemo.common.OSSConstant;
import com.example.ossdemo.service.ExportWordService;
import com.example.ossdemo.service.OssService;
import com.example.ossdemo.utils.MoneyUtils;@Service("ExportWordService")
public class ExportWordServiceImpl implements ExportWordService{@Autowiredprivate OssService ossService;@Autowiredprivate OssServiceImpl ossServiceImpl;/*** 导出订单合同word*/@Overridepublic Map<String, Object> exportOrderContractInfo(HttpServletRequest request) throws Exception {Map<String, Object> result = new HashMap<String, Object>();//模拟数据请求,组装必要数据Map<String, Object> data = new HashMap<String, Object>();//导出wordexportWordOrderInfo(data);result.put("code", "success");return result;}/*** 生成订单合同word* @param request* @param response* @throws IOException*/public void exportWordOrderInfo(Map<String,Object> data){Map<String, Object> params = new HashMap<>();DecimalFormat df = new DecimalFormat("######0.00");   Calendar now = Calendar.getInstance(); double money = 0;//总金额//模拟数据请求,组装表格列表数据List<Map<String,Object>> detailList=new ArrayList<Map<String,Object>>();for (int i = 0; i < 6; i++) {Map<String,Object> detailMap = new HashMap<String, Object>();detailMap.put("index", i+1);//序号detailMap.put("title", "商品"+i);//商品名称detailMap.put("product_description", "套");//商品规格detailMap.put("buy_num", 3+i);//销售数量detailMap.put("saleprice", 100+i);//销售价格double saleprice=Double.valueOf(String.valueOf(100+i));Integer buy_num=Integer.valueOf(String.valueOf(3+i));String buy_price=df.format(saleprice*buy_num);detailMap.put("buy_price", buy_price);//单个商品总价格money=money+Double.valueOf(buy_price);detailList.add(detailMap);}//总金额String order_money=String.valueOf(money);//金额中文大写String money_total = MoneyUtils.change(money);// 渲染文本{{}}//渲染表格HackLoopTableRenderPolicy  policy = new HackLoopTableRenderPolicy();Configure config = Configure.newBuilder().bind("detailList", policy).build();String basePath=ClassUtils.getDefaultClassLoader().getResource("").getPath()+"static/template/";//本地String oss_url="http://xiaofang-001.oss-cn-shanghai.aliyuncs.com/file/20201217/130652163_419.docx";//模板地址(存储在oss上的模板文件地址)//word模板地址String resource=getResourceName(oss_url, basePath);//将oss上传文件地址 还原成 目标文件XWPFTemplate template = XWPFTemplate.compile(resource, config).render(new HashMap<String, Object>() {{put("detailList", detailList);put("order_number", "2356346346645");put("y", now.get(Calendar.YEAR));//当前年put("m", (now.get(Calendar.MONTH) + 1));//当前月put("d", now.get(Calendar.DAY_OF_MONTH));//当前日put("order_money",order_money);//总金额put("money_total",money_total);//金额中文大写}});String newFilePath=basePath+getNewFilePath1(resource);try {File newFile = new File(basePath, getNewFilePath1(resource));FileOutputStream out = new FileOutputStream(newFile);template.write(out);out.flush();out.close();template.close();String url = getOssFilePath(newFilePath,newFile);System.out.println(url);if(StringUtils.isNotEmpty(url)){newFile.delete();}} catch (IOException e) {e.printStackTrace();}}/*** 将oss上传文件地址 还原成 目标文件* @param oss_url:Word文档的oos url全地址*/public String getResourceName(String oss_url,String basePath){String resource="";//域名String oss_domain = OSSConstant.OSS_PIC_URL;String file_name = oss_url.replace(oss_domain, "");//获取oss文件byte[]byte[] oss_byte = ossServiceImpl.getOssFileByteArray(file_name);String targetPath=basePath+getNewFilePath1(oss_url);//将字节数组还原为文件ossServiceImpl.byteArrayToFile(oss_byte, targetPath);resource=targetPath;return  resource;}/*** 新的文件名* @param fileName* @return*/public String getNewFilePath1(String fileName){String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();//生成新的文件名String newfilename = "";Date now = new Date();SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");newfilename += date.format(now) + "";SimpleDateFormat time = new SimpleDateFormat("HHmmssSSS");newfilename += time.format(now);newfilename += "-" + new Random().nextInt(1000) + "." + fileExt;return newfilename;}/*** 生成的Word上传oos* @param fileName* @param newFile* @return*/public String getOssFilePath(String fileName,File newFile){String url = "";try {String fileExt = fileName.substring(fileName.lastIndexOf(".") + 1).toLowerCase();//生成新的文件名String newfilename = "file/";Date now = new Date();SimpleDateFormat date = new SimpleDateFormat("yyyyMMdd");newfilename += date.format(now) + "/";SimpleDateFormat time = new SimpleDateFormat("HHmmssSSS");newfilename += time.format(now);newfilename += "_" + new Random().nextInt(1000) + "." + fileExt;FileInputStream fis = new FileInputStream(newFile);//文件上传到ossossService.uploadFile(newfilename, fis);url = OSSConstant.OSS_PIC_URL + newfilename;}catch (Exception e) {e.printStackTrace();}return url;}}

2.4 添加ExportWordController

package com.example.ossdemo.controller;import java.util.Map;import javax.servlet.http.HttpServletRequest;import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;
import org.springframework.web.servlet.ModelAndView;import com.example.ossdemo.service.ExportWordService;/*** 导出word* @author Administrator**/
@RequestMapping("/exportWord")
@RestController
public class ExportWordController {@Autowiredprivate ExportWordService exportWordService;/*** 进入导出页面* @return*/@RequestMapping("/index")public ModelAndView toIndex(){return new ModelAndView("export/index");}/*** 导出销售订单信息(导出word)* @param request* @return* @throws Exception*/@RequestMapping(value="/exportOrderContractInfo",method = RequestMethod.POST)public Map<String, Object> exportOrderContractInfo(HttpServletRequest request) throws Exception{return exportWordService.exportOrderContractInfo(request);}
}

2.5 创建jsp文件,调用导出方法

<%@ page language="java" contentType="text/html; charset=UTF-8" pageEncoding="UTF-8"%>
<%
String path = request.getContextPath();
String basePath = request.getScheme()+"://"+request.getServerName()+":"+request.getServerPort()+path+"/";
%><!DOCTYPE html>
<html>
<head>
<!-- easyUI -->
<link rel="stylesheet" href="<%=basePath %>/js/jquery-easyui-1.7.0/themes/default/easyui.css">
<link rel="stylesheet" href="<%=basePath %>/js/jquery-easyui-1.7.0/themes/icon.css">
<link rel="stylesheet" href="<%=basePath %>/css/common.css">
<script type="text/javascript" src="<%=basePath %>/js/jquery-3.4.1.min.js"></script>
<script type="text/javascript" src="<%=basePath %>/js/jquery-easyui-1.7.0/jquery.easyui.min.js"></script>
<script type="text/javascript" src="<%=basePath %>/js/jquery-easyui-1.7.0/locale/easyui-lang-zh_CN.js"></script>
<!-- layer 弹出层 -->
<script type="text/javascript" src="<%=basePath %>/js/layer/layer.js"></script>
<link rel="stylesheet" href="<%=basePath %>/js/layui-v2.5.6/css/layui.css"><style>.m1{margin-left: 10px;}.s1{font-weight: bold;color:#1e9fff;}.m2{margin-bottom: 10px;}</style>
</head>
<body  class="easyui-layout"><h1 class="m1">SpringBoot+oss实现根据word模板生成Word,文件地址保存在oss中:</h1><div class="easyui-tabs"style="width:700px;height:350px;margin: 10px 0 0 10px;"><div title="导出销售订单"style="padding:10px"><div class="m2"style="margin-top: 30px;">需求:将word模板上传到oss中,然后根据返回的模板地址进行生成word,最后把生成的Word再上传到oss并返回oss文件地址。</div><div class="m2"style="margin-top: 30px;">word模板地址(oss):<input type="text" id="url3" class="easyui-textbox"style="width:600px;" value="http://xiaofang-001.oss-cn-shanghai.aliyuncs.com/file/20201216/093915477_23.docx" readonly="readonly"></div><a href="#" class="easyui-linkbutton" onclick="downloadTemplate();" >下载word模板地址</a><div class="m2"style="margin-top: 30px;">使用<span class="s1">POI-tl</span>根据word模板动态生成word(包含动态表格)</div><a href="#" class="easyui-linkbutton" onclick="doExportWord3();" data-options="iconCls:'icon-save'">导出销售订单</a></div></div><script type="text/javascript">//下载word模板function downloadTemplate(){console.log($("#url3").val())window.location.href=$("#url3").val();}//导出word(包含动态表格)function doExportWord3(){window.location.href="<%=basePath%>/exportWord/exportOrderContractInfo";}</script>
</body>
</html>

源码点击此处下载

参考资料:
地表最细阿里云OSS开通和文件上传demo,巨细 yeah
开通阿里云服务存储OSS
springboot整合阿里云OSS实现文件上传

SpringBoot实现oss文件的上传与下载相关推荐

  1. SpringBoot+Vue3实现文件的上传和下载

    目录 前言 上传前端页面 上传后端代码 下载后端代码 下载前端代码 总结 参考文献 前言 上传文件和下载文件是我们平时经常用到的功能,接下来就让我们用SpringBoot,Vue3和ElementPl ...

  2. SpringBoot 项目将文件图片资源上传到本地静态资源文件夹下(指定文件夹下)

    1.SpringBoot 项目将文件图片资源上传到本地静态资源文件夹下(指定文件夹下) 最终效果: 前端浏览本地文件,点击上传至本地resources/static/images/imgWall下 2 ...

  3. iOS阿里云对象存储 OSS文件的上传/下载的实现

    之前的项目中,图片语音等资源文件都是直接上传数据流给服务器,然后服务器进行处理和存储.最近的这个项目,服务器直接开的OSS,然后客户端直接使用阿里云提供的上传下载功能来上传和下载资源. 阿里云对图片的 ...

  4. 使用阿里云OSS实现文件的上传、下载、删除及修改功能

    一.配置OSS相关配置信息 1.要配置 OSS 相关配置信息,您可以按照以下步骤操作: 登录阿里云控制台,进入 OSS 控制台,创建一个新的 OSS Bucket,并记录下以下信息:Bucket 名称 ...

  5. (十六)admin-boot项目之文件存储上传与下载minio

    (十六)文件存储上传与下载 项目地址:https://gitee.com/springzb/admin-boot 如果觉得不错,给个 star 简介: 这是一个基础的企业级基础后端脚手架项目,主要由s ...

  6. SpringMVC实现文件的上传和下载

    SpringMVC实现文件的上传和下载http://www.bieryun.com/1120.html 前些天一位江苏经贸的学弟跟我留言问了我这样一个问题:"用什么技术来实现一般网页上文件的 ...

  7. Akka实战:HTTP大文件断点上传、下载,秒传

    2019独角兽企业重金招聘Python工程师标准>>> 访问:https://github.com/yangbajing/scala-applications/tree/master ...

  8. 初学Java Web(7)——文件的上传和下载

    文件上传 文件上传前的准备 在表单中必须有一个上传的控件 <input type="file" name="testImg"/> 因为 GET 方式 ...

  9. SpringMVC实现文件的上传与下载

    文件的上传与下载可以说是工作中经常使用的功能,现在整理一下,希望能够给大家一个参考.这里以 Maven 的形式来创建项目,相关的配置文件会把主要的内容列出来,其他头文件信息不再一一全部的列出.最后会把 ...

最新文章

  1. 皮一皮:这是要红啊...
  2. git checkout -b dev origin/dev详解
  3. eclipse中maven的user settings和global settings的区别
  4. 回文数python_回文数 python
  5. 2018年中国C++大会详细日程+报名
  6. unity中怎么在InspectorI面板加LOGO
  7. python可以测试java的代码吗_使用python做你自己的自动化测试--对Java代码做单元测试 (2)-导入第三方jar包裹...
  8. 输出一个数的二进制序列中1的个数(三种方法)
  9. python链表定长翻转_python实现单链表翻转
  10. 蓝牙精确定位技术下的化工厂安全管理系统,蓝牙定位标签-新导智能
  11. python抓取qq群消息,python 爬取qq群员信息
  12. 激光雷达产业深度研究报告:三大思考
  13. Android对话框控件读写,Android 对话框控件
  14. CITA v0.15 Release
  15. 一顿操作猛如虎,3000 行代码重构成 15 !
  16. UI界面视觉设计之色彩要素
  17. 过程计算机系统 pcs,科学网-对PCS(等离子体控制系统)软件基本结构的理解-章勇的博文...
  18. vue 打包后 components 组件 样式丢失问题
  19. 第50章 读写内部FLASH—零死角玩转STM32-F429系列
  20. 毕业设计记录-yolov5的wandb报错,原因和解决方法(非屏蔽wandb)

热门文章

  1. 计算机ps基础考试题,2017年计算机一级PS考试模拟题及答案
  2. Android媒体播放器设计,基于Android系统多媒体播放器的设计与实现
  3. 有关天线与波的极化方向的小结:线极化,圆极化,椭圆极化
  4. vue admin template开启顶部导航
  5. [附源码]Python计算机毕业设计毕业设计管理系统
  6. 接地电阻测试的5种方法
  7. vm15 版本win mac 的unlocker 安装失败 unlocker不能使用
  8. cuda安装失败问题2:install of driver component failed
  9. Python 多线程下载图片
  10. lol最克制诺手的英雄_LOL:62个上单英雄,竟只有3个能克制诺手?第1名很多人想不到!...