在实际项目中,文件上传是很多项目必不可少的一个功能。那么在 Spring Boot 项目中又是如何来实现文件上传功能的呢?一般来说,上传的文件可以保存到项目根目录下的某一文件夹中,但这样做显然是不太合适的。因此我们选择将文件上传到专门的文件服务器中。很多云计算厂商都提供文件存储服务。这里我选择的是阿里云的对象存储(OSS)。
一、配置OSS 1. 导入SDK 首先,你需要注册阿里云的账号并开通对象存储服务。在准备工作完成之后,需要导入 JAVA 版本的 SDK,这里使用 maven 进行导入
1 2 3 4 5 6 <dependency > 	<groupId > com.aliyun.oss</groupId >  	<artifactId > aliyun-sdk-oss</artifactId >  	<version > 3.8.0</version >  </dependency > 
2. 修改配置文件 导入完成后在 application.properties 配置文件中添加以下内容
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 # 节点域名 aliyun.oss.endpoint=oss-cn-xxxxxxx.aliyuncs.com # 账户id aliyun.oss.accessKeyId=xxxxxxxxxxxxx # 账户密码 aliyun.oss.accessKeySecret=xxxxxxxxxxxxx # bucket名称 aliyun.oss.bucketName=xxxxxxxxxxx # 签名过期时间 aliyun.oss.policy.expire=300 # 上传文件的最大尺寸 aliyun.oss.maxSize=10 # 上传地址的前缀 aliyun.oss.dir.prefix=xxx # 回调参数的请求地址 aliyun.oss.callback=http://www.xxxxxx.com/api/aliyun/oss/callback 
以上内容在开通服务后均可获取到,请根据实际情况进行修改
3. 初始化 
OSSClient是OSS的Java客户端,用于管理存储空间和文件等OSS资源。使用Java SDK发起OSS请求,您需要初始化一个OSSClient实例,并根据需要修改ClientConfiguration的默认配置项。
 
根据官方文档的描述,需要初始化一个ossClient实例并将其注入到Spring容器中,因此可以编写一个配置类OssConfig
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 @Configuration @PropertySource(value = {"classpath:application.properties"}, encoding = "utf-8") public  class  OssConfig      @Value("${aliyun.oss.endpoint}")      private  String endpoint;     @Value("${aliyun.oss.accessKeyId}")      private  String accessKeyId;     @Value("${aliyun.oss.accessKeySecret}")      private  String secretAccessKey;     @Bean      public  OSS ossClient ()          return  new  OSSClientBuilder().build(endpoint, accessKeyId, secretAccessKey);     } } 
更多详细的配置,请参考官方文档:初始化 
二、文件上传 1. 流程分析 我们以典型的表单上传为例,在使用对象存储OSS后,表单上传分为以下几个流程:
注:Policy表单域用于验证请求的合法性。例如可以指定上传的大小,可以指定上传的Object名称等,上传成功后客户端跳转到的URL,上传成功后客户端收到的状态码。
1 2 3 4 5 6 7 8 9 PolicyConditions policyConds = new  PolicyConditions(); policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0 , maxSize); policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, DIR_PREFIX); String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); byte [] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);String policy = BinaryUtil.toBase64String(binaryData); String signature = ossClient.calculatePostSignature(postPolicy); 
前端向OSS服务器上传文件时要上传Policy表单域,OSS服务器将对Policy表单域的内容进行验证。关于 Post Policy 的详细内容,请参考官方文档:Post Policy 
当文件上传成功后,OSS服务器会向应用服务器发起回调请求,具体流程如下:
用户只需要在发送给 OSS 的请求中携带相应的 Callback 参数,即能实现回调。
Callback 参数是由一段经过 base64 编码的 JSON 字符串(字段)。构建 callback 参数的关键是指定请求回调的服务器 URL(callbackUrl)以及回调的内容(callbackBody)。
1 2 3 4 5 6 7 8 9 10 11 12 Callback callback = new  Callback(); callback.setCallbackUrl(CALLBACK); callback.setCallbackBody("{\\\"filename\\\":${object},\\\"mineType\\\":${mimeType}}" ); callback.setCalbackBodyType(Callback.CalbackBodyType.JSON); 
更详细的内容请阅读官方文档:Callback 
2. 功能实现 首先编写 Post Policy 封装对象OssPolicyResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 @Data public  class  OssPolicyResult  	@ApiModelProperty("用户id")  	private  String accessKeyId; 	 	@ApiModelProperty("Post Policy经过base64编码过的字符串")      private  String policy; 	 	@ApiModelProperty("对policy签名后的字符串")      private  String signature; 	 	 	 	@ApiModelProperty("上传文件夹路径前缀")      private  String dir; 	 	@ApiModelProperty("oss对外服务的访问域名")      private  String host; 	@ApiModelProperty("上传成功后的回调设置")  	private  String callback; } 
然后需自定义一个回调结果对象OssCallBackResult
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 @Data public  class  OssCallBackResult      @ApiModelProperty("文件的链接")      private  String url;     @ApiModelProperty("文件名称")      private  String filename;     @ApiModelProperty("文件大小")      private  String size;     @ApiModelProperty("文件的mimeType")      private  String  mimeType;     @ApiModelProperty("图片文件的宽")      private  String width;     @ApiModelProperty("图片文件的高")      private  String height; } 
注:以上内容可根据实际需要进行修改
之后编写 Service 接口及实现类
Service 接口:
1 2 3 4 5 6 7 8 public  interface  OssService  	      	OssPolicyResult policy ()  ; 	      	OssCallBackResult callback (Map<String, Object> requestBody)  ; } 
Service 实现类:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 @Slf4j @Service @PropertySource(value = {"classpath:application.properties"}, encoding = "utf-8") public  class  OssServiceImpl  implements  OssService  	@Value("${aliyun.oss.endpoint}")  	private  String ENDPOINT; 	@Value("${aliyun.oss.accessKeyId}")  	private  String ACCESS_KEY_ID; 	@Value("${aliyun.oss.accessKeySecret}")  	private  String SECRET_ACCESS_KEY; 	@Value("${aliyun.oss.bucketName}")  	private  String BUCKET_NAME; 	@Value("${aliyun.oss.policy.expire}")  	private  int  EXPIRE; 	@Value("${aliyun.oss.maxSize}")  	private  int  MAX_SIZE; 	@Value("${aliyun.oss.dir.prefix}")  	private  String DIR_PREFIX; 	@Value("${aliyun.oss.callback}")  	private  String CALLBACK; 	@Autowired  	private  OSS ossClient; 	@Override  	public  OssPolicyResult policy ()   		OssPolicyResult result = new  OssPolicyResult(); 		 		long  expireEndTime = System.currentTimeMillis() + EXPIRE * 1000 ; 		Date expiration = new  Date(expireEndTime); 		 		 		 		long  maxSize = MAX_SIZE * 1024  * 1024 ; 		 		String action = "http://"  + BUCKET_NAME + "."  + ENDPOINT; 		 		Callback callback = new  Callback(); 		 		callback.setCallbackUrl(CALLBACK); 		 		 		 		callback.setCallbackBody("{\\\"filename\\\":${object}}" ); 		 		callback.setCalbackBodyType(Callback.CalbackBodyType.JSON); 		 		 		try  { 			PolicyConditions policyConds = new  PolicyConditions(); 			policyConds.addConditionItem(PolicyConditions.COND_CONTENT_LENGTH_RANGE, 0 , maxSize); 			policyConds.addConditionItem(MatchMode.StartWith, PolicyConditions.COND_KEY, DIR_PREFIX); 			String postPolicy = ossClient.generatePostPolicy(expiration, policyConds); 			byte [] binaryData = postPolicy.getBytes(StandardCharsets.UTF_8);              			String policy = BinaryUtil.toBase64String(binaryData);              			String signature = ossClient.calculatePostSignature(postPolicy);              			String callbackData = BinaryUtil.toBase64String(OSSUtils.jsonizeCallback(callback).getBytes()); 			 			result.setAccessKeyId(ACCESS_KEY_ID); 			result.setPolicy(policy); 			result.setSignature(signature); 			 			result.setDir(dir); 			result.setHost(action); 			result.setCallback(callbackData); 		} catch  (Exception e) { 			log.error("签名生成失败" , e); 		} 		return  result; 	} 	@Override  	public  OssCallBackResult callback (Map<String, Object> requestBody)   		OssCallBackResult ossCallbackResult = new  OssCallBackResult(); 		 		String filename = requestBody.get("filename" ).toString(); 		 		String url = "https://"  + BUCKET_NAME + "."  + ENDPOINT + "/"  + DIR_PREFIX + "/"  + filename; 		ossCallbackResult.setUrl(url); 		return  ossCallbackResult; 	} } 
添加 Controller 层:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 @Api(tags = "阿里云对象存储接口") @RequestMapping("/api") @RestController public  class  OssController  	 	@Autowired  	private  OssService ossService; 	 	@ApiOperation(value = "OSS上传签名生成")  	@GetMapping("/aliyun/oss/policy")  	public  Object policy ()   		return  ossService.policy(); 	} 	@ApiOperation(value = "OSS上传成功回调")  	@PostMapping("/aliyun/oss/callback")  	public  Object callback (@RequestBody  Map<String, Object> requestBody)   		return  ossService.callback(requestBody); 	} }