背景
最近做一个项目,需要本地上传图片到第三方API,获得其返回的图片链接,将图片链接存入数据库。
理想很美好:找一个第三方图床,了解接口文档 -> Ajax异步发送请求,将图片文件传到第三方图床API -> 接收第三方API返回的数据,从中拿到图片外链 -> 将外链存入数据库
现实很骨感:在我认为最简单的一步,发送Ajax请求时,由于跨域报错了…
已拦截跨源请求:同源策略禁止读取位于 *** 的远程资源。(原因:CORS 头缺少 'Access-Control-Allow-Origin')
需求
有一个第三方接口,我要用ajax请求,出现跨域。在不修改第三方服务器且只能用post提交的前提下,有什么方法能解决跨域问题。(实例:图片文件上传到第三方图床获得外链)
资料
1,第三方api接口文档
由于用到第三方图床,这边把对应图片上传API文档信息共享一下(详情可以看官网:https://sm.ms/)
//接口url
https://sm.ms/api/v2//upload
请求方式 | post |
请求头 | |
Content-Type (String类型) | 默认值: multipart/form-data |
Authorization (String类型) | Secret Token(登录后才有,在个人中心查看。有点像用户ID,让对应图片上传成功后到你的账号下) |
请求参数 | |
smfile (file类型) | 待上传的图片文件 |
format (String) | 返回类型: json 或 xml, 默认是 json |
返回信息参考 | -见截图- |
2,本地的Ajax请求代码
$.ajax({
url:"https://sm.ms/api/v2/upload", //第三方接口url
data:{ //待上传的数据
"smfile" : $('#file')[0].files[0] //$('#file')[0].files[0]是我要上传的图片文件
},
type:"post", //请求方式
headers: { //设置请求头
"Content-Type":"multipart/form-data",
"Authorization":"14ac5******************d079bf" //替换成自己的token
},
success:function(r){ //尝试打印结果
alert(r);
},
error:function(){ //错误提示
alert("操作失败,请稍后再试...");
}
})
实现
尝试方法1:ajax请求的数据类型设置成"JSONP"
dataType:"JSONP"
添加完成后,Ajax请求如下:
$.ajax({
url:"https://sm.ms/api/v2/upload",
data:{
"smfile" : $('#file')[0].files[0]
},
type:"post",
dataType:"JSONP", //设置数据类型
headers: {
"Content-Type":"multipart/form-data",
"Authorization":"14ac5******************d079bf"
},
success:function(r){
alert(r);
},
error:function(){ //错误提示
alert("操作失败,请稍后再试...");
}
})
尝试发送后报错,错误提示如图:
好家伙,改了数据类型后,确实没有报跨域错误了,但是我的请求方式被强制改成了GET;但是我要访问的第三方接口只支持POST方式的请求,对于GET方式的请求,直接就拒绝了…
所以方法1只能解决用GET方式发送的跨域请求;对于非GET请求的跨域问题,并不能解决失败。所以得试试其他办法。
尝试方法2:CORS
这时候有其他小伙伴给我推荐了CORS。
CORS定义一种跨域访问的机制,可以让AJAX实现跨域访问。CORS 允许一个域上的网络应用向另一个域提交跨域 AJAX。
请求。实现此功能非常简单,只需由服务器发送一个响应标头【response.setHeader(“Access-Control-Allow-Origin”, “"); 】。
如果访问的是对方的API接口,而且允许跨域的话,加上【response.setHeader(“Access-Control-Allow-Origin”, "”); 】这个标头,就能实现跨域。
看完介绍后我就果断放弃这个方法,因为需要对服务器端进行操作;而我想访问的第三方API,我可没有操作权限…
尝试方法3:利用spring boot(后端)中转转发请求
说实话,这个问题当时困扰了我两天,因为一开始就单纯想通过纯前端发送请求,但是各种方法尝试后均不能如愿。
所以我最后决定借助后端,通过将请求发送给后端,由后端进行访问获得数据,再将数据从后端返回前端来间接实现我的需求。
下面是具体操作和相关代码:
1,前端Ajax请求代码(请求参数带上待上传的图片即可,其他的就按正常的请求来写就好,目标API的请求头等在后端实现)
//数据封装
var formData = new FormData();
formData.append("smfile", $('#file')[0].files[0]);
$.ajax({
type : "post",
url : "api/imgGetUrl", //请求的后端接口url
data : formData, // ajax传文件时 一定要指定两个关键性的参数
contentType:false, // 1 不用任何编码 因为formdata对象自带编码
processData:false, // 2 告诉浏览器不要处理数据 直接发就行
success : function(r) {
alert(r); //输出测试
})
2,工具类代码(参考的是网上某位大佬的【参考链接:https://www.cnblogs.com/dreammyone/p/6994685.html】,我就加了需要的请求头)
package com.guet.ccbishe.common;import java.io.BufferedReader;
import java.io.DataOutputStream;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.Iterator;
import java.util.Map;
import org.springframework.web.multipart.MultipartFile;
import com.alibaba.fastjson.JSONObject;
public class InterfaceUtil {
/**
* 表单形式上传数据
*
* @param urlStr
* @param textMap
* @param fileMap
* @param contentType 没有传入文件类型默认采用application/octet-stream
* contentType非空采用filename匹配默认的图片类型
* @return 返回response数据
*/
@SuppressWarnings("rawtypes")
public static JSONObject doPost(String urlStr, Map<String, String> textMap, Map<String, MultipartFile> fileMap) {
JSONObject jsonObject = null;
HttpURLConnection conn = null;
// boundary就是request头和上传文件内容的分隔符
String BOUNDARY = "---------------------------123821742118716";
try {
URL url = new URL(urlStr);
conn = (HttpURLConnection) url.openConnection();
conn.setConnectTimeout(5000);
conn.setReadTimeout(30000);
conn.setDoOutput(true);
conn.setDoInput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Connection", "Keep-Alive");
conn.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows; U; Windows NT 6.1; zh-CN; rv:1.9.2.6)");
conn.setRequestProperty("Content-Type", "multipart/form-data; boundary=" + BOUNDARY);
conn.setRequestProperty("Authorization","DBN****87fQ");//添加的请求头
OutputStream out = new DataOutputStream(conn.getOutputStream());
// text
if (textMap != null) {
StringBuffer strBuf = new StringBuffer();
Iterator iter = textMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
String inputValue = (String) entry.getValue();
if (inputValue == null) {
continue;
}
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
strBuf.append("Content-Disposition:form-data;name=\"" + inputName + "\"\r\n\r\n");
strBuf.append(inputValue);
}
out.write(strBuf.toString().getBytes());
}
// file
if (fileMap != null) {
Iterator iter = fileMap.entrySet().iterator();
while (iter.hasNext()) {
Map.Entry entry = (Map.Entry) iter.next();
String inputName = (String) entry.getKey();
MultipartFile inputValue = (MultipartFile) entry.getValue();
if (inputValue == null) {
continue;
}
String filename = inputValue.getOriginalFilename();
// 没有传入文件类型,同时根据文件获取不到类型,默认采用application/octet-stream
String contentType = inputValue.getContentType();
StringBuffer strBuf = new StringBuffer();
strBuf.append("\r\n").append("--").append(BOUNDARY).append("\r\n");
strBuf.append("Content-Disposition:form-data;name=\"" + inputName + "\";filename=\"" + filename
+ "\"\r\n");
strBuf.append("Content-Type:" + contentType + "\r\n\r\n");
out.write(strBuf.toString().getBytes());
InputStream in = inputValue.getInputStream();
int bytes = 0;
byte[] bufferOut = new byte[1024];
while ((bytes = in.read(bufferOut)) != -1) {
out.write(bufferOut, 0, bytes);
}
in.close();
}
}
byte[] endData = ("\r\n--" + BOUNDARY + "--\r\n").getBytes();
out.write(endData);
out.flush();
out.close();
InputStream inputStream = null;
if (conn.getResponseCode() != HttpURLConnection.HTTP_OK ) {
inputStream = conn.getErrorStream();//主要就是这里
} else {
inputStream = conn.getInputStream();//主要就是这里
}
BufferedReader br = new BufferedReader(new InputStreamReader(inputStream, "UTF-8"));
StringBuffer buffer = new StringBuffer();
String line = "";
while ((line = br.readLine()) != null) {
buffer.append(line);
}
String result = new String(buffer);
jsonObject = JSONObject.parseObject(result);
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
} finally {
if (conn != null) {
conn.disconnect();
conn = null;
}
}
return jsonObject;
}
}
3,后端Java代码(控制层)
@RequestMapping("api/imgGetUrl")
@ResponseBody
public String imgAdd( @RequestParam(value = "smfile", required = false) MultipartFile smfile) throws IllegalStateException, IOException {
//一、转发地址
String url = "https://sm.ms/api/v2/upload";
//二、参数处理
Map<String, MultipartFile> params1 = new HashMap<>();//1 文件类型参数
params1.put("smfile", smfile);
Map<String, String> params2 = new HashMap<>();//2 字符串类型参数
params2.put("format", "json");
//三、发送请求-接收返回的数据
JSONObject result = InterfaceUtil.doPost(url, params2, params1);
//结果返回,这里由于我只要图片地址,所以做了如下处理
return result.getJSONObject("data").getString("url");
}
4,测试