引子
近日接到了一个需求,给项目的个人资料设置加上上传头像的功能。我心想这不是简简单单,项目里已经配置了阿里云的oss,我做毕设的时候用过七牛云的对象存储,这需求不是一模一样,直接去调用官方提供的sdk就好了,光速搞完就可以愉快地自我提升了(指换个方式摸鱼)。
上传の初体验
把官网提供的sdk封装成工具类,相关的参数写在配置文件里,接着写个接口,把参数配置注入进去,在接口里提供参数然后直接调用工具类,将上传后图片的路径返回给前端即可。
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
// 调用ossClient.getObject返回一个OSSObject实例,该实例包含文件内容及文件元信息。
OSSObject ossObject = ossClient.getObject(bucketName, objectName);
// 调用ossObject.getObjectContent获取文件输入流,可读取此输入流获取其内容。
InputStream content = ossObject.getObjectContent();
if (content != null) {
BufferedReader reader = new BufferedReader(new InputStreamReader(content));
while (true) {
String line = reader.readLine();
if (line == null) break;
System.out.println("\n" + line);
}
// 数据读取完成后,获取的流必须关闭,否则会造成连接泄漏,导致请求无连接可用,程序无法正常工作。
content.close();
}
} 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();
}
}
自己测试了一下,前端调用也正常返回图片url和正常显示了,于是开心地找到mentor去“报喜”。
我:简简单单,一个小时就搞定这个需求了,直接review吧!
mentor:可以呀,年轻人效率就是高,我来看看。
几分钟后,mentor:你小子快是快,可是没考虑好安全性啊,现在你把key直接回传前端也不做限制,别人拿到不就可以白嫖我们的oss服务了,去把这里优化一下!
上传の再优化
痛定思痛后,我重新认真去看了阿里云提供的相关文档,发现它有提供STS临时授权,最终我这样做图片上传,前端发起请求后,后端根据调用临时授权方法,返回给前端一个临时的token令牌,再在前端拿着令牌进行上传操作。
// 以华东1(杭州)的外网Endpoint为例,其它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完整路径,例如exampleobject.txt。Object完整路径中不能包含Bucket名称。
//String objectName = "exampleobject.txt";
//String pathName = "D:\localpath\examplefile.txt";
// 从STS服务获取临时访问凭证后,您可以通过临时访问密钥和安全令牌生成OSSClient。
// 创建OSSClient实例。
OSS ossClient = new OSSClientBuilder().build(endpoint, credentialsProvider);
try {
// 执行OSS相关操作,例如上传、下载文件等。
// 上传文件,此处以上传本地文件为例介绍。
// 填写本地文件的完整路径。如果未指定本地路径,则默认从示例程序所属项目对应本地路径中上传文件。
//PutObjectRequest putObjectRequest = new PutObjectRequest(bucketName, objectName, new File(pathName));
//ossClient.putObject(putObjectRequest);
// 下载OSS文件到本地文件。如果指定的本地文件存在则覆盖,不存在则新建。
// 如果未指定本地路径,则下载后的文件默认保存到示例程序所属项目对应本地路径中。
//ossClient.getObject(new GetObjectRequest(bucketName, objectName), new File(pathName));
} 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();
}
}
优化以后,自己也进行了本地测试没问题,于是再次找到mentor来review代码,这次得到了表扬,自己美美地打了包,发到了线上测试环境。然后,反转来了,线上测试环境上传这里有问题,报了跨域问题。
问题の排查
首先,跨域问题的出现我们都知道必然是域名、端口、协议这三者至少有一个出了问题,于是我光速定位报错的请求,把它和本地的url进行比对,并最终发现问题,我本地请求使用的是HTTP请求,而线上使用的是HTTPS请求,在前端代码里发这个请求我也是写死的HTTP请求。
问题の解决
由于我是在前端发起上传的,因此最终我决定使用window.location.protocol属性,这个属性可以获取当前页面的协议(例如 HTTP 或 HTTPS)。这样就可以确保上传请求的协议与当前页面的协议一致,避免跨域问题。
// 获取当前页面的协议
var protocol = window.location.protocol;
// 构建上传请求的 URL
var uploadUrl = protocol + '//your-domain.com/upload';