1. 文件上传到本地
需求分析
在用户更换头像或发布文章时,需要携带一个图片的 url 地址,该 url 地址是当用户访问文件上传接口,将图片上传成功后,服务器返回的地址。所以,后台需要提供一个文件上传接口,用来接收前端提交的文件数据,并且返回文件的访问地址。
接口文档
文件上传知识回忆
前端页面文件上传三要素:
- 请求方式必须是 post;
- 表单的编码类型必须是 multipart/form-data;
- 文件表单项对应的类型必须是 file。
这三个要素满足后,用户就可以选择要上传的文件,点击提交按钮,最终把文件内容提交给服务器。当服务器接收到该请求后,如果服务器中的代码是使用 SpringMVC 框架编写的,我们就可以在方法上声明一个 MultipartFile 类型的参数,用来接收上传的文件内容。MultipartFile 提供了很多方法:
接口实现思路
Controller 中声明用于上传文件的 upload()方法,方法内部需要借助 MultipartFile 提供的 api,把文件内容写入本地磁盘文件,因此方法上要声明一个 MultipartFile 类型的参数。
现在还没有把文件内容上传到阿里云服务器,我们先把文件存储到服务器本地,保证接口访问是正常的,接口没问题后,再把文件上传到服务器测试。
@RestController
public class FileUploadController {@PostMapping("/upload")public Result<String> upload(MultipartFile file) throws IOException {//把文件内容存储到本地磁盘//获取文件原始的名称String originalFilename = file.getOriginalFilename();//file.transferTo的异常直接抛出去//再次存储的时候使用原来的文件名file.transferTo(new File("C:\\Users\\xxx\\Desktop\\files\\"+originalFilename));return Result.success("url访问地址...");}
}
postman 测试:
点击 select file 选择要上传的文件:
打开文件:
上传成功:
但是,现在的代码中还存在bug。当我们在 postman 中针对刚刚选择的图片再次点击 send 时,files 文件夹中并没有出现两张图片。原因是后上传的图片与之前上传的图片重名,导致之前的图片被覆盖了。在现实中,很有可能出现需要上传两张同名图片的需求,比如两个用户上传的图片同名。因此这个问题需要解决一下。
解决方式就是保证上传时的文件名唯一:在文件后缀名前面拼接UUID:
再次上传:
现在只是通过将文件上传到本地的方式模拟了将文件上传到服务器的过程。下面来介绍将文件上传到服务器的实现方式。
2. 文件上传到云服务器
云服务器会提供很多服务,当计算机连接上云服务器并开通某个服务后后就可以使用该服务了。
在本节,我们使用阿里云的对象存储服务。
使用云对象存储 OSS(Object Storage Service),可以通过网络随时存储和调用包括文本、图片、音频和视频等在内的各种文件。
使用第三方服务的通用思路
- 准备工作:在服务提供商网站上注册用户,开通对应服务
- 参照官方 SDK 编写入门程序:引入阿里云 OSS 相关的 jar 包,并参照提供的示例代码编写程序
- 参照入门程序,将功能集成到自己的代码中使用
SDK:Software Development Kit
的缩写,软件开发工具包,包括辅助软件开发的依赖(jar包)、代码示例等,都可以叫做SDK。
2.1 准备工作
在服务提供商网站上注册用户,开通对应服务
① 登录之后,点击控制台
② 查看阿里云提供的所有服务
③ 搜索对象存储 OSS
④ 首次使用 OSS 需要开通
⑤ 按照提示开通后,开始创建 bucket
⑥ 填写必要信息,创建 bucket
⑦ 创建成功后,进入 bucket
⑧ 概览中展示了 bucket 基本信息和访问地址
⑨ 获取 AccessKey:
根据提示一步一步操作就能获取到 AccessKey。至此,准备工作完成。
2.2 参照官方 SDK 编写入门程序
导入阿里云 OSS 相关的依赖坐标,并参照提供的示例代码编写程序。
(1) 导入 OSS 相关依赖坐标
① 左侧点击概览:
② 滑动到底部,点击 SDK 下载
③ 找到 Java 版本,点击查看文档,会弹出帮助文档。点击“文档中心打开”
④ 点击”安装“
⑤ 将所需依赖坐标导入我们的 pom.xml 文件:
(2) 参照提供的示例代码编写程序
① 因为是要上传文件,所以点击:对象/文件➡上传文件➡简单上传
② 此处给出了一个示例代码,copy 一下
copy 的示例代码如下:
public class Demo {public static void main(String[] args) throws Exception {// Endpoint以华东1(杭州)为例,其它Region请按实际情况填写。String endpoint = "https://oss-cn-hangzhou.aliyuncs.com";// 从环境变量中获取访问凭证。运行本代码示例之前,请确保已设置环境变量OSS_ACCESS_KEY_ID和OSS_ACCESS_KEY_SECRET。EnvironmentVariableCredentialsProvider credentialsProvider = CredentialsProviderFactory.newEnvironmentVariableCredentialsProvider();// 填写Bucket名称,例如examplebucket。String bucketName = "examplebucket";// 填写Object完整路径,完整路径中不能包含Bucket名称,例如exampledir/exampleobject.txt。String objectName = "exampledir/exampleobject.txt";// 创建OSSClient实例。OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);try {// 填写字符串。String content = "Hello OSS,你好世界";// 创建PutObjectRequest对象。PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new ByteArrayInputStream(content.getBytes()));// 如果需要上传时设置存储类型和访问权限,请参考以下示例代码。// ObjectMetadata metadata = new ObjectMetadata();// metadata.setHeader(OSSHeaders.OSS_STORAGE_CLASS, StorageClass.Standard.toString());// metadata.setObjectAcl(CannedAccessControlList.Private);// putObjectRequest.setMetadata(metadata);// 上传字符串。PutObjectResult result = ossClient.putObject(putObjectRequest); } catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}}
}
③ 修改示例代码
public class Demo {@Testpublic void test() throws Exception {// Endpoint:bucket概览中的访问端口->外网访问的地域节点String endpoint = "https://oss-cn-beijing.aliyuncs.com";// 阿里云为了防止AccessKeyId和AccessKeySecret泄露,建议以配置环境变量的方式来使用// 这里是测试代码,就不再配置环境变量了,直接定义两个变量来模拟,值都改成自己的String ACCESS_KEY_ID="**********";String ACCESS_KEY_SECRET="**********";// Bucket名称String bucketName = "big-event-bucket1";// 要存储的对象的名称,这里的对象可以是字符串、图片、视频等String objectName = "cat.jpg";// 创建OSSClient实例OSS ossClient = new OSSClientBuilder().build(endpoint, ACCESS_KEY_ID, ACCESS_KEY_SECRET);try {// 借助图片输入流对象构建出PutObjectRequest对象PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new FileInputStream("C:\\Users\\xxx\\Desktop\\cat.jpg"));// 上传到服务器PutObjectResult result = ossClient.putObject(putObjectRequest);} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}}
}
(3) 运行代码
上传成功:
点击图片名称,弹出图片具体信息。复制图片在服务器上的 url。
在浏览器中输入图片在服务器上的 url,搜索。此时已从服务器上下载了图片。
虽然上面相对于阿里云提供的示例代码该类很多,但只要完成这个 Demo 的改造,今后在一般情况下需要改的地方就只有下面两处:要存储的对象的名称、输入流中要存储的对象在本地的位置。
2.3 案例集成 OSS
参照入门程序,将功能集成到自己的代码中使用。
为了使用方便,需要把上面改造成功的代码封装成一个工具类。将来哪里需要使用,直接调用工具类的方法即可。
public class AliOssUtil {//这四个一般不变,所以抽取到成员处,声明为静态常量public static final String ENDPOINT = "https://oss-cn-beijing.aliyuncs.com";//区域节点public static final String ACCESS_KEY_ID="LTAI5t9NJbszDF431vYUkzbh";public static final String ACCESS_KEY_SECRET="d82arCTGqH3Turz4HY3NA4a9RufHn0";public static final String BUCKET_NAME = "big-event-bucket1";// Bucket名称// objectName: 要存储的对象的名称,这里的对象可以是字符串、图片、视频等// 方法内部可能发生变化的是objectName和输入流,因此以参数的方式暴露出去// 方法要返回图片在服务器上的访问地址,因此返回值是String类型public static String upLoadFile(String objectName, InputStream inputStream) throws Exception {// 创建OSSClient实例OSS ossClient = new OSSClientBuilder().build(ENDPOINT, ACCESS_KEY_ID, ACCESS_KEY_SECRET);String url = ""; //为了返回url,先声明一个空字符串try {PutObjectRequest putObjectRequest = new PutObjectRequest(BUCKET_NAME, objectName, inputStream);PutObjectResult result = ossClient.putObject(putObjectRequest);//上传到服务器//如果下面的代码不报错,就该给url赋值了//url组成:https://bucket名称.区域节点(去掉https://)/objectName//ENDPOINT.lastIndexOf("/")+1: 在ENDPOINT中,从最后一个"/"的下一个位置开始截取字符串url="https://"+BUCKET_NAME+"."+ENDPOINT.substring(ENDPOINT.lastIndexOf("/")+1)+"/"+objectName;} catch (OSSException oe) {System.out.println("Caught an OSSException, which means your request made it to OSS, "+ "but was rejected with an error response for some reason.");System.out.println("Error Message:" + oe.getErrorMessage());System.out.println("Error Code:" + oe.getErrorCode());System.out.println("Request ID:" + oe.getRequestId());System.out.println("Host ID:" + oe.getHostId());} catch (ClientException ce) {System.out.println("Caught an ClientException, which means the client encountered "+ "a serious internal problem while trying to communicate with OSS, "+ "such as not being able to access the network.");System.out.println("Error Message:" + ce.getMessage());} finally {if (ossClient != null) {ossClient.shutdown();}}return url;}
}
现在工具类准备好了,以后需要将文件上传阿里云只需调用 upLoadFile() 方法即可。
在 FileUploadController 中,之前我们将文件存储到了本地磁盘,现在不需要了,直接调用 upLoadFile() 方法将文件上传服务器。upLoadFile() 方法需要传递两个参数,filename 可以作为传入的第一个参数 objectName;第二个参数是文件输入流,MultipartFile 提供的 getInputStream() 方法可以提供该参数。最后将 upLoadFile() 方法返回的 url 响应给浏览器。
@RestController
public class FileUploadController {@PostMapping("/upload")public Result<String> upload(MultipartFile file) throws Exception {//把文件内容上传到阿里云服务器//获取原始文件名String originalFilename = file.getOriginalFilename();//使用UUID生成文件名,保证文件名唯一,从而防止文件被覆盖//originalFilename.lastIndexOf("."): 从文件名的最后一个"."处开始截取字符串(包括"."),即获取原始文件后缀名String filename = UUID.randomUUID().toString()+originalFilename.substring(originalFilename.lastIndexOf("."));// file.transferTo(new File("C:\\Users\\xxx\\Desktop\\files\\"+filename));//upLoadFile()方法上的异常直接抛出去即可String url = AliOssUtil.upLoadFile(filename, file.getInputStream());return Result.success(url);}
}
postman 测试:
在浏览器中输入文件访问地址,可以下载: