## 背景 在使用 \*\*巨量引擎\*\* 时,我们可能会通过 API 获取到视频的 URL。但是,获取到的视频 URL 通常是 \*\*前端临时链接\*\*,不能直接用于下载。为了能够下载视频并上传到自己的存储系统,我们需要绕过这个临时链接的限制。 通过分析浏览器的开发者工具(F12),我们发现,虽然视频 URL 是临时的,但它背后存在一个 \*\*302 重定向\*\*,通过该重定向可以获取到一个有效的、可以直接下载的视频地址。接下来,我们将介绍如何利用这一点来下载视频,并将其上传到 \*\*连山\*\* 的对象存储。 ## 获取视频的临时链接 首先,我们使用 \*\*巨量引擎 API\*\* 中的接口,来请求获取视频的 URL。巨量引擎提供了一个接口,能够返回广告视频的相关信息,其中包括视频的 URL。具体接口如下: 该接口返回的数据中,包含了一个临时的 \*\*video\\_url\*\*,该链接指向的视频是临时的,因此不能直接下载。 !\[\](https://vip.123pan.cn/1851591614/blog/%E6%96%87%E7%AB%A0%E9%85%8D%E5%9B%BE/%E9%80%9A%E8%BF%87%E5%B7%A8%E9%87%8F%E5%BC%95%E6%93%8EAPI%E8%8E%B7%E5%8F%96%E8%A7%86%E9%A2%91URL%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0%E8%BF%9E%E5%B1%B1%E4%BA%91%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/Snipaste_2025-11-01_10-52-26.png) 返回的链接类似于: \`\`\` https://cc.oceanengine.com/anm/CsoDcnZJc1URoiNJCoy7D5Md3NhcNhzAR2gSeMz1-fVCMuR16p1n0gbn5dYUIeWkgXE1zT75mX7Ianl0f0d2msKxKeJH5oACQM2Hn1ixIt2vhQXp0qYQQfSDb9796kZ2TE47Q_RRlpzsb8trRgW2TO1dO6FeD7tk6YdNlD1RXUZ9dX5xTxUkdAaJbB7TCJwHQw2ziUi8p5CR7rPLqZuqVLmtpYSUpVK5M0z3oLqKuU23zRKbrJfa179cUvaZllyZauzlmWUZoJR8CCvRDMreWJkAOte2rDjBUuQBVMTUMiDJLfb1wEVznDnkZk3AYebXG9aQo2sDwf-AJCpIS7I2zRjqXwrLlMbdYNs2Mw9HNY8xKG2wo7v_4oUAo1QPvWUI-nXfF_oh4c9XJRnOnps2rFdm-2oWak43CeeVH66eYGtBtgC0iO6XPFRR0j_zn6V5xgskuKZlNDfdOB-Fju36-L5PdMTydbJbp97SR09Adjnqz35m-qhSP6yOMsg-jglnT9phjY8hU0pBMVnFuuaPIAAoHlkPFa0uaBYe9CjLmQ1yl2T6s7bBKmlAasSVkX7NIWPGJyE7bQUD8B4fdzIyJ93jM2FEwKP567BQLzMaSwo8AAAAAAAAAAAAAE-oNyT7JAzSaiEZbzi1UJv_nlcIyiTUjexlRbi_pBsLgx4aQa9DDhULVrie-fa66ynlEMesgA4Y7fL_4QEgASIBA5x2FaU= \`\`\` 是一个前端地址,而且还是加密的,临时的,每次请求都不一样 ## 分析 F12 重定向过程 访问此链接,使用浏览器的开发者工具(F12),我们发现,当我们请求这个临时视频 URL 时,会首先返回一个 \*\*302 Redirect\*\* 响应。 !\[\](https://vip.123pan.cn/1851591614/blog/%E6%96%87%E7%AB%A0%E9%85%8D%E5%9B%BE/%E9%80%9A%E8%BF%87%E5%B7%A8%E9%87%8F%E5%BC%95%E6%93%8EAPI%E8%8E%B7%E5%8F%96%E8%A7%86%E9%A2%91URL%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0%E8%BF%9E%E5%B1%B1%E4%BA%91%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/Snipaste_2025-11-01_10-48-08.png) 在响应中,我们看到以下关键内容: 从这个过程我们可以了解到,虽然初始的 URL 是临时的,但它会通过重定向获取到一个永久的视频下载地址。 !\[\](https://vip.123pan.cn/1851591614/blog/%E6%96%87%E7%AB%A0%E9%85%8D%E5%9B%BE/%E9%80%9A%E8%BF%87%E5%B7%A8%E9%87%8F%E5%BC%95%E6%93%8EAPI%E8%8E%B7%E5%8F%96%E8%A7%86%E9%A2%91URL%E5%B9%B6%E4%B8%8A%E4%BC%A0%E5%88%B0%E8%BF%9E%E5%B1%B1%E4%BA%91%E5%AF%B9%E8%B1%A1%E5%AD%98%E5%82%A8/Snipaste_2025-11-01_10-57-21.png) 于是我便怀疑这个set-cookie是访问视频的关键信息,因为最开始时访问会被直接返回一个403。 ## 解决方案:解析重定向并下载视频 为了下载视频,我们需要模拟这个过程,首先发送请求到临时的 URL,获取重定向信息(特别是 \`Set-Cookie\` 和 \`Location\`),然后通过这些信息发送请求到最终的视频 URL,成功获取视频文件。 ## 关键代码实现 以下是使用 \*\*OkHttp\*\* 发送请求并解析视频下载地址的代码: \`\`\`java import okhttp3.\*; import java.io.IOException; import java.util.Arrays; import java.util.concurrent.TimeUnit; public class VideoDownloader { private final OkHttpClient sharedClient = new OkHttpClient.Builder() .connectionPool(new ConnectionPool(150, 30, TimeUnit.MINUTES)) .connectTimeout(30, TimeUnit.SECONDS) .readTimeout(30, TimeUnit.SECONDS) .writeTimeout(30, TimeUnit.SECONDS) .protocols(Arrays.asList(Protocol.HTTP_2, Protocol.HTTP_1_1)) //优先 HTTP/2 .followRedirects(false) // 禁用自动重定向 .build(); public InputStream downloadFromRealUrl(String initialUrl) throws IOException { // 第一步:发送请求获取 302 重定向 Request initialRequest = new Request.Builder() .url(initialUrl) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36") .header("Accept", "video/webm,video/ogg,video/\*;q=0.9") .header("Referer", "https://cc.oceanengine.com/") .header("Origin", "https://cc.oceanengine.com/") .build(); // 执行请求 Response initialResponse = sharedClient.newCall(initialRequest).execute(); // 处理 302 响应 if (initialResponse.code() == 302) { // 提取 Set-Cookie 和 Location String setCookie = initialResponse.header("Set-Cookie"); String location = initialResponse.header("Location"); if (setCookie == null \|\| location == null) { throw new RuntimeException("响应缺少必要的 Set-Cookie 或 Location"); } // 第三步:提取到的 cookies 在后续请求中使用 // 创建一个新的请求到重定向的 URL Request downloadRequest = new Request.Builder() .url(location) .header("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/128.0.0.0 Safari/537.36") .header("Accept", "video/webm,video/ogg,video/\*;q=0.9") .header("Referer", "https://cc.oceanengine.com/") .header("Origin", "https://cc.oceanengine.com/") .header("Cookie", setCookie) // 设置从 302 响应中获取的 cookies .build(); // 执行下载请求 Response downloadResponse = sharedClient.newCall(downloadRequest).execute(); if (!downloadResponse.isSuccessful()) { throw new RuntimeException("视频下载失败: HTTP " + downloadResponse.code()); } // 返回视频流 return downloadResponse.body().byteStream(); } else { throw new RuntimeException("初始请求失败: HTTP " + initialResponse.code()); } } } \`\`\` ## 代码解析 ## 上传到连山云 通过这种方式,我们成功使用Java解析了视频文件,并可以将其\[上传到 \*\*连山\*\* 对象存储桶\](https://blog.nanshengwx.cn/linkJump?target=https%3A%2F%2Fwww.volcengine.com%2Fdocs%2F6349%2F79898%23%25E4%25B8%258A%25E4%25BC%25A0%25E7%25BD%2591%25E7%25BB%259C%25E6%25B5%2581),因为巨量引擎的上传视频素材只接受连山内网视频地址,因此我们可以直接上传视频流到指定的存储桶,并获取到内网地址,\[上传视频素材\](https://blog.nanshengwx.cn/linkJump?target=https%3A%2F%2Fopen.oceanengine.com%2Flabels%2F12%2Fdocs%2F1697466676592651%3Forigin%3Dleft_nav)到指定账户上。 \`\`\`java // 对象名,在连山云 TOS 中保存的路径(建议使用英文/数字,避免中文) String objectKey = "video/" + newFileName; try ( TOSV2 tos = new TOSV2ClientBuilder().build(REGION, ENDPOINT, ACCESS_KEY, SECRET_KEY); InputStream inputStream = downloadFromUrl(videoDownUrl) // 使用增强方法 ) { PutObjectInput putObjectInput = new PutObjectInput() .setBucket(BUCKET_NAME) .setKey(objectKey) .setContent(inputStream); PutObjectOutput output = tos.putObject(putObjectInput); // 构造 TOS 公网访问地址 tosUrl = String.format( "https://%s.tos-%s.ivolces.com/%s", BUCKET_NAME, REGION, objectKey ); log.info("视频上传成功!TOS URL: {}", tosUrl); log.info("ETag: {}, CRC64: {}", output.getEtag(), output.getHashCrc64ecma()); } catch (IOException e) { log.error("读取视频URL失败,无法下载文件。URL={}", videoDownUrl, e); record.setResult("读取视频URL失败,无法下载文件:" + e.getMessage()); return record; } catch (TosClientException e) { log.error("TOS客户端异常,请求未发送。Bucket={}, Key={}", BUCKET_NAME, objectKey, e); record.setResult("TOS客户端异常,请求未发送:" + e.getMessage()); return record; } catch (TosServerException e) { log.error("TOS服务端返回错误。StatusCode={}, Code={}, Message={}", e.getStatusCode(), e.getCode(), e.getMessage(), e); record.setResult("TOS服务端返回错误:状态码=" + e.getStatusCode() + ", 错误码=" + e.getCode() + ", 消息=" + e.getMessage()); return record; } catch (Throwable t) { log.error("未知异常发生", t); record.setResult("未知异常发生:" + t.getMessage()); return record; } \`\`\` 其中的REGION,ENDPOINT,ACCESS\\_KEY,SECRET\\_KEY,BUCKET\\_NAME是全局变量,根据自身情况自行设置