You need to enable JavaScript to run this app.
文档中心
云手机

云手机

复制全文
下载 pdf
最佳实践
边缘直连功能
复制全文
下载 pdf
边缘直连功能
相比经由中心服务转发的传统链路,边缘直连将高频数据面请求从“客户端 → 中心 → 边缘”优化为“客户端 → 边缘”。 通过边缘直连,云手机实例能够在保证安全和可控的基础上,提供更低时延、更高响应效率的访问体验。本文将介绍如何通过预签名链接,直接访问云手机边缘实例,完成截图、命令执行、文件上传和文件下载等高频交互操作。​
背景​
在传统云手机链路中,客户端(即用户终端)通常需要先接入中心服务,再由中心服务完成实例调度、链路分发和请求转发。这种方式具备统一管理和通用兼容的优势,但在截图、命令执行、文件传输、网络代理等实时交互场景下,中心中转会增加链路长度和额外时延,影响整体访问体验。​
随着云手机逐步承载更多低时延业务,尤其是面向自动化控制和 AI Agent 接入的场景,系统需要一种在保留中心统一治理能力的同时,进一步缩短数据链路的访问方式。​
使用场景​
在保留中心统一鉴权、调度和治理能力的前提下,该方案可降低网络跳数和中转开销,适合对时延和响应效率要求较高的场景。建议在以下场景优先使用边缘直连方案:​
  • 对时延敏感的远程控制场景。​
  • 高频截图、命令执行和文件传输场景。​
  • 需要稳定长连接的实时交互场景。​
  • AI Agent 驱动的云手机操作场景。​
前提条件​
  • 该接口仅对部分机房开放。如需使用,请 提交工单 联系技术支持团队。​
  • 请使用发布日期为 2026 年 7 月 14 日之后的 AOSP10-AOSP13 镜像文件。​
  • 使用 SDK 之前,请参考如下说明,初始化 SDK。​
方案架构​
边缘实例直连方案采用 “中心控制面 + 边缘数据面” 的设计,说明如下:​
  • 客户端(即用户终端)调用 GetPreSignedEdgeURL 接口,向云手机中心服务申请目标实例的边缘直连预签名链接。​
  • 云手机中心服务完成用户鉴权与资源调度,下发实例连接策略与路由,生成边缘实例直连预签名链接并返回给用户终端。​
  • 用户终端通过预签名链接直连边缘服务网关。​
  • 边缘服务网关校验直连链接签名,解析请求并完成底层实例路由映射,建立 HTTP/WebSocket 安全直连通道,与安卓实例进行通信。​
​​​
步骤一、获取预签名链接​
调用控制面接口 GetPreSignedEdgeURL ,为指定的边缘接口请求生成一个可重复使用的预签名链接(URL)。客户端使用该链接直接访问边缘网关,无需再自行拼接鉴权信息。边缘接口支持以下四种类型:​
​
接口能力​
APIType​
HTTP 方法​
说明​
截图​
TakeScreenshot​
GET​
支持单次截图和基于 SSE 的连续截图。​
同步命令执行​
RunSyncCommand​
POST​
在云手机实例内执行命令,并同步返回执行结果。​
文件上传​
UploadFile​
PUT​
将本地文件上传到云手机实例指定路径。​
文件下载​
DownloadFile​
GET​
从云手机实例下载文件,支持单段 Range 续传。​
​
边缘接口请求参数与 GetPreSignedEdgeURL 入参的对应关系如下:​
Go SDK 兼容性提示​
部分版本的 Go SDK 在调用 GetPreSignedEdgeURL 时,因入参结构体存在字段缺失,可能导致 APIPayload 无法正常传入。如遇此问题,建议改用 SDK 的 GetPreSignedEdgeURLCommon 调用方式(以 map 类型传参),或直接按 OpenAPI 协议发起 HTTP 请求,并从响应中解析预签名链接。详细内容可参考各功能示例代码及 helpers.go 文件。​
​
边缘接口参数位置​
GetPreSignedEdgeURL 字段​
说明​
Query​
APIPayload​
将边缘接口的 Query 参数按键值对传入。系统会把这些参数拼入预签名链接,并纳入签名范围;链接返回后不可修改。​
Body​
APIBodyHash​
可选。对边缘接口的 HTTP Body 计算 SHA256 后传入,用于校验并将链接与请求体绑定。建议在 RunSyncCommand 等带 Body 的场景启用。​
Header​
无​
获取预签名链接时不需要额外映射 Header。业务接口本身要求的 Header(如 Range)仍需在实际请求中按接口说明携带。​
​
步骤二、通过预签名链接直连边缘​
获取预签名链接后,客户端可以直接向边缘网关发起 HTTP 请求。不同接口的请求方法、参数位置和响应处理方式不同,详见如下说明。​
TakeScreenshot 截图​
截图能力用于从云手机实例获取当前画面,支持以下两种模式:​
  • 单次截图:请求一次预签名链接,获取一张截图。​
  • 连续截图:请求一次预签名链接后,云手机按指定周期持续返回截图帧,适合 MUA、CUA 等连续观察场景。​
单次截图​
单次截图为默认模式。不传 Mode=sse 时,接口按单次截图处理。​
  • Query 参数​
  • ​
    参数​
    数据类型​
    是否必选​
    示例值​
    描述​
    Width​
    Integer​
    否​
    720​
    截图目标宽度,参考客户端屏幕比例,默认值为 0,取值范围为 [200, 2560]。​
    Height​
    Integer​
    否​
    1280​
    截图目标高度,参考客户端屏幕比例,默认值为 0,取值范围为 [200, 2560]。​
    MimeType​
    String​
    否​
    jpg​
    截图格式,支持的类型如下:​
    • png​
    • jpg (默认值)​
    IsSavedOnPod​
    Boolean​
    否​
    true​
    是否将截图同步保存至实例中。​
    • true:是。​
    • false:(默认值)否。​
    IsBroadcasted​
    Boolean​
    ​
    否​
    true​
    是否广播截图事件。​
    • true:(默认值)广播。​
    • false:非广播。​
    Rotation​
    ​
    Integer​
    ​
    否​
    1​
    截图画面横竖屏的旋转方向。​
    • 0:(默认值)截图方向不做处理。​
    • 1:截图画面旋转为竖屏。​
    • 手机竖屏的截图,不做处理。​
    • 手机横屏的截图,截图顺时针旋转 90 度。​
    ResizeMode​
    ​
    Integer​
    否​
    1​
    截图图片缩放模式 。​
    • 0:(默认值)等比缩放,缩略图限制在指定 Width 与 Height 的矩形内的最大图片。​
    • 1:等比缩放,缩略图为延伸出指定 Width 与 Height 的矩形框外的最小图片。​
    • 2:等比缩放,缩略图为延伸出指定 Width 与 Height 的矩形框外的最小图片,之后按照固定宽高进行裁剪。​
    • 3:等比缩放,缩略图限制在指定 Width 与 Height 的矩形内的最大图片再按照固定宽高进行颜色填充。​
    • 4:按照固定宽高强制缩放,若宽高与原图宽高比例不同,则会导致图片变形。​
    RoundId​
    ​
    String​
    ​
    否​
    2027703615530995712_17xxx​
    每次截图的标识 ID,传入后将作为截图文件名的一部分。默认为空。​
    Quality​
    Integer​
    否​
    100​
    截图质量,取值范围为 [1, 100],默认值为 100。值越小图片越模糊、文件体积越小,建议设置为 80 以平衡画质与体积。​
    Scale​
    Double​
    否​
    1.0​
    截图同比缩放的比例,取值范围为 [0.1, 1.0],默认值为 1.0(即原尺寸输出,不缩放)。​
    ​
  • 单次截图 HTTP 响应状态码​
  • ​
    HTTP 状态码​
    message​
    含义​
    建议​
    200​
    success​
    单次截图成功,返回图片二进制流。​
    按图片二进制流处理响应体。​
    400​
    Invalid timestamp format​
    请求中的 timestamp 时间戳格式不合法。​
    检查 timestamp 是否为合法的数字字符串,并按接口签名规范重新发起请求。​
    401​
    Missing auth headers / Missing signature headers​
    请求缺少必要的鉴权信息。​
    ​
    补全鉴权相关请求头后重试。​
    403​
    Replay detected / Invalid signature​
    请求鉴权失败,可能为重复请求或签名不正确。​
    使用新的 nonce / timestamp 重新签名;检查签名算法、密钥、请求路径及参数是否一致。​
    408​
    Screenshot timeout​
    截图请求处理超时。​
    建议稍后重试;如持续出现,请检查设备状态或适当降低请求频率。​
    500​
    Auth secret not ready / Screenshot interrupted / Screenshot failed: ... / Screenshot image path is null / Screenshot file not found​
    服务端暂时无法完成截图请求。​
    建议稍后重试;如持续出现,请联系服务端或设备侧排查。​
    ​
  • 单次截图示例代码​
  • 以下为使用 Go 语言调用单次截图接口的示例。​
  • ​
    func TakeScreenshot() {​
    ​
    input := map[string]any{​
    "ProductId": "18450134671xxxxxx",​
    "PodId": "76395775887xxxxxx",​
    "APIType": "TakeScreenshot",​
    "APIPayload": map[string]any{​
    "RoundId": "123456",​
    },​
    }​
    rawOutput, err := GetACEPClient().GetPreSignedEdgeURLCommon(&input)​
    if err != nil {​
    log.Fatal(err.Error())​
    }​
    preSignedEdgeURL, err := getPreSignedEdgeURLFromOutput(rawOutput)​
    if err != nil {​
    log.Fatal(err.Error())​
    }​
    log.Printf("get pre-signed edge url success: %s", preSignedEdgeURL)​
    }​
    ​
​
连续截图​
连续截图模式下,需将请求参数 Mode 设置为 sse,服务端将以 text/event-stream 格式返回响应。​
  • Query 参数​
  • ​
    参数​
    数据类型​
    是否必选​
    示例值​
    描述​
    Mode​
    String​
    是​
    sse​
    连续截图的推送模式。​
    • sse:启用 SSE(Server-Sent Events)模式推送截图结果。​
    说明​
    不填则使用单次截图模式。​
    MimeType​
    String​
    否​
    jpg​
    截图格式,支持的类型如下:​
    • png​
    • jpg (默认值)​
    Interval​
    ​
    Integer​
    否​
    1000​
    截图时间间隔,单位为毫秒。每次会在上一轮截图完成后,等待指定时长再执行下一轮截图。默认值为 500,最小值为 200,最大值为 Long.MAX_VALUE(实际可以视为无有效业务上限)。当您填写的值小于 200、超过最大值或格式非法时,系统会自动回退为默认值。​
    DurationLimit​
    Integer​
    否​
    ​
    100​
    连续截图操作的最大持续时长,单位为毫秒,超时后自动结束截图。取值范围:[100, 43200000],默认值为 1000(即持续 1 秒)。​
    Rotation​
    ​
    Integer​
    ​
    否​
    1​
    截图画面横竖屏的旋转方向。​
    • 0:(默认值)截图方向不做处理。​
    • 1:截图画面旋转为竖屏。​
    • 手机竖屏的截图,不做处理。​
    • 手机横屏的截图,截图顺时针旋转 90 度。​
    Quality​
    Integer​
    否​
    100​
    截图质量,取值范围为 [1, 100],默认值为 100。值越小图片越模糊、文件体积越小,建议设置为 80 以平衡画质与体积。​
    Scale​
    Double​
    否​
    1.0​
    截图同比缩放的比率,取值范围为 [0.1, 1.0],默认值为 1.0(即原尺寸输出,不缩放)。​
    ​
  • 连续截图 HTTP 响应状态码​
  • ​
    HTTP 状态码​
    message​
    含义​
    建议​
    200​
    success​
    SSE 连接建立成功,后续通过事件流持续接收截图帧。​
    按 text/event-stream 协议处理响应,持续监听 frame、error(具体错误信息参见下表 连续截图时 SSE 建连后的错误事件) 等事件。​
    400​
    Invalid timestamp format​
    请求中的 timestamp 格式不合法。​
    检查 timestamp 是否为合法的数字字符串,并按接口签名规范重新发起请求。​
    401​
    Missing auth headers / Missing signature headers​
    请求缺少必要的鉴权信息。​
    补全鉴权相关请求头后重试。​
    403​
    Replay detected / Invalid signature​
    请求鉴权失败,可能为重复请求或签名不正确。​
    使用新的 nonce/timestamp 重新签名;检查签名算法、密钥、请求路径及参数是否一致。​
    429​
    Too many SSE connections​
    当前 SSE 截图连接数已达上限。​
    关闭不再使用的连接,复用已有连接,或采用退避策略后重试。​
    500​
    Auth secret not ready / Internal server error​
    服务端暂时无法建立 SSE 截图连接。​
    建议稍后重试;如持续出现,请联系服务端或设备侧排查。​
    ​
  • 连续截图时 SSE 建连后的错误事件​
  • SSE 连接建立成功后(HTTP 状态码为 200),后续错误不再通过 HTTP 状态码返回,而是以 SSE event: error 事件的形式下发。​
  • ​
    SSE error data​
    含义​
    建议​
    frame_error​
    单帧截图失败。​
    可继续监听后续帧;如连续出现,建议重建 SSE 连接。​
    timeout duration=<milliseconds>​
    SSE 会话已达持续时间上限。​
    按需重新建立 SSE 连接。​
    internal_error <message>​
    SSE 会话发生内部错误。​
    关闭当前连接,退避后重连。​
    ​
  • 连续截图示例代码​
  • 以下为使用 Go 语言调用连续截图接口的示例。​
  • ​
    func TakeScreenshot() {​
    ​
    input := map[string]any{​
    "ProductId": "1845013467xxxxxx",​
    "PodId": "7639577588xxxxxx",​
    "APIType": "TakeScreenshot",​
    "APIPayload": map[string]any{​
    "Mode": "sse",​
    },​
    }​
    rawOutput, err := GetACEPClient().GetPreSignedEdgeURLCommon(&input)​
    if err != nil {​
    log.Fatal(err.Error())​
    }​
    preSignedEdgeURL, err := getPreSignedEdgeURLFromOutput(rawOutput)​
    if err != nil {​
    log.Fatal(err.Error())​
    }​
    log.Printf("get pre-signed edge url success: %s", preSignedEdgeURL)​
    }​
    ​
RunSyncCommand 同步命令执行​
在云手机实例上执行指定命令,并以 JSON 格式同步返回执行结果。​
Body 参数​
​
参数​
原始数据类型​
是否必选​
示例值​
描述​
Command​
String​
是​
pm list packages​
待执行的命令,不可为空。​
注意​
若 Command 命令中包含中文字符,需在请求 Header 中添加以下字段,否则可能导致乱码。​
"Content-Type": "application/json; charset=utf-8"​
PermissionType​
String​
否​
root​
命令执行的权限类型,支持以下类型:​
  • root:(默认值)以 root 权限执行命令,可操作系统级资源。​
  • shell:以普通权限执行命令,适用于常规操作。​
TimeoutSecond​
Integer​
否​
10​
命令执行的超时时间(秒),超时后命令将被终止并返回超时状态码。取值范围为 [1, 3600],默认值为 3600。​
ResultLength​
Integer​
否​
1024​
命令执行结果(stdout / stderr)的最大返回长度,单位为字节,取值范围为 [1, 10485760],默认值为 10485760,即 10 MiB。​
​
HTTP 响应状态码​
​
HTTP 状态码​
message​
含义​
建议​
200​
success​
请求已处理完成,但命令本身不一定执行成功。​
以响应体为准判断命令结果,还需检查响应体中的 Success、ExitCode、TimedOut、Stdout 和 Stderr 等字段。​
400​
Invalid timestamp format / bad request​
请求格式或参数不合法。​
​
校验请求体 JSON 格式,并确认命令、权限类型、超时时间、结果长度等参数符合接口要求。​
401​
Missing auth headers / Missing signature headers​
请求缺少必要的鉴权信息。​
补齐签名相关请求头后重新发起请求。​
403​
Replay detected / Invalid signature​
请求鉴权失败,可能是重复请求或签名不正确。​
使用新的 nonce 和 timestamp 重新生成签名,并确认签名方法、请求路径和参数与实际请求一致。​
500​
Auth secret not ready / internal error​
服务端暂时无法完成命令执行请求。​
建议进行有限重试;若持续失败,记录请求参数、HTTP 状态码和响应体,并提交服务端或设备侧排查。​
​
示例代码​
以下为使用 Go 语言调用同步命令执行接口的示例。​
​
type RunSyncCommandRequest struct {​
Command string `json:"Command"`​
PermissionType string `json:"PermissionType"`​
TimeoutSecond int `json:"TimeoutSecond"`​
ResultLength int `json:"ResultLength"`​
}​
​
func RunSyncCommand() {​
​
req := RunSyncCommandRequest{​
Command: "ls -al",​
PermissionType: "root",​
TimeoutSecond: 10,​
ResultLength: 10240,​
}​
rawReq, err := json.Marshal(req)​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("req: %s\n", string(rawReq))​
​
reqReader := bytes.NewReader(rawReq)​
hash, err := GeneratePayloadHash(reqReader)​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("hash: %s\n", hash)​
​
input := map[string]any{​
"ProductId": "184501346712xxxxxx",​
"PodId": "763957758872xxxxxx",​
"APIType": "RunSyncCommand",​
"APIBodyHash": hash,​
}​
rawOutput, err := GetACEPClient().GetPreSignedEdgeURLCommon(&input)​
if err != nil {​
log.Fatal(err.Error())​
}​
preSignedEdgeURL, err := getPreSignedEdgeURLFromOutput(rawOutput)​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("get pre-signed edge url success: %s", preSignedEdgeURL)​
​
request, err := http.NewRequest(http.MethodPost, preSignedEdgeURL, reqReader)​
if err != nil {​
log.Fatal(err.Error())​
}​
request.Header.Set("Content-Type", "application/json; charset=utf-8")​
​
httpClient := &http.Client{}​
response, err := httpClient.Do(request)​
if err != nil {​
log.Fatal(err.Error())​
}​
defer response.Body.Close()​
​
body, err := io.ReadAll(response.Body)​
if err != nil {​
log.Fatal(err.Error())​
}​
if response.StatusCode != http.StatusOK {​
log.Fatalf("http response status code %d, body: %s", response.StatusCode, string(body))​
}​
log.Printf("RunSyncCommand response: %s\n", string(body))​
}​
​
UploadFile 文件上传​
将本地文件写入云手机实例指定路径。​
说明​
若上传的文件为多媒体格式(如 .mp4、.jpg、.png 等),系统将自动刷新媒体库,相册等应用可直接显示已上传的文件。​
Query 参数​
​
参数​
原始数据类型​
是否必选​
示例值​
描述​
Path​
String​
是​
/sdcard/Download/test.zip​
目标文件的完整路径,不可为目录。​
AutoUnzip​
String​
否​
false​
是否自动解压,仅对 .zip 文件有效。​
  • true:表示上传完成后自动解压。​
  • false:(默认值)表示不自动解压。​
OverWrite​
String​
否​
true​
是否覆盖已存在的目标文件。​
  • true:(默认值)允许覆盖。​
  • false:目标文件已存在,不允许覆盖。​
​
Header 参数​
​
参数名​
数据类型​
是否必选​
示例值​
说明​
Content-Length​
Long​
是​
1048576​
上传内容的字节数,不能为空、非法值或 0,否则请求将被拒绝。​
Content-MD5​
String​
否​
1B2M2Y8AsgTpgAmY7PhCfg==​
文件内容的 MD5 值,以 Base64 编码传入,默认不校验。传入后服务端将在上传完成后验证内容一致性。​
​
Body 参数​
​
参数名​
数据类型​
是否必选​
示例值​
说明​
request body​
Binary​
是​
文件二进制内容​
直接传入文件的二进制数据,非 JSON 格式。服务端将依据请求头中的 Content-Length 读取 Body 内容。​
​
HTTP 响应状态码​
​
HTTP 状态码​
message​
含义​
建议​
200​
success​
文件上传成功。​
解析响应体,确认文件路径、文件大小、MD5 校验结果及自动解压结果。​
400​
Invalid timestamp format / Invalid target path / Missing or invalid Content-Length / Empty upload body / Upload file too large / Upload body missing file content / Upload body truncated / Content-MD5 mismatch / Invalid Content-MD5​
请求参数、请求头或请求体不符合接口要求。​
校验 timestamp、目标路径、Content-Length、请求体完整性、文件大小及 Content-MD5 格式与内容一致性。​
401​
Missing auth headers / Missing signature headers​
请求缺少必要的鉴权信息。​
补齐签名相关请求头后重新发起请求。​
403​
Replay detected / Invalid signature / Path not allowed​
请求鉴权失败,或上传目标路径不被允许。​
使用新的 nonce/timestamp 重新签名,并确认签名路径、请求参数及上传路径权限配置正确。​
408​
Upload timeout​
文件上传处理超时。​
重新发起上传;如文件较大,建议拆分文件或优化网络稳定性后重试。​
409​
File already exists​
目标文件已存在,且当前请求不允许覆盖。​
若业务允许覆盖,请在设置覆盖参数后重试;否则更换目标文件名。​
500​
Auth secret not ready / Permission denied for target path / Failed to create target directory / Failed to prepare temp upload file / Failed to finalize uploaded file / Failed to unzip file / Upload failed​
服务端暂时无法完成上传或后处理。​
可进行有限重试;若持续失败,检查目标路径、文件权限、存储状态或压缩包内容。​
​
示例代码​
以下为使用 Go 语言调用文件上传接口的示例。​
​
func UploadFile() {​
​
input := map[string]any{​
"ProductId": "18450134671xxxxxx",​
"PodId": "76395775887xxxxxx",​
"APIType": "UploadFile",​
"APIBodyHash": "UNSIGNED-PAYLOAD",​
"APIPayload": map[string]any{​
"Path": "/sdcard/Download/superpowers-5.1.0.zip",​
"OverWrite": "true",​
"AutoUnzip": "false",​
},​
}​
rawOutput, err := GetACEPClient().GetPreSignedEdgeURLCommon(&input)​
if err != nil {​
log.Fatal(err.Error())​
}​
preSignedEdgeURL, err := getPreSignedEdgeURLFromOutput(rawOutput)​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("get pre-signed edge url success: %s", preSignedEdgeURL)​
​
fileReader, err := os.Open("./superpowers-5.1.0.zip")​
if err != nil {​
log.Fatal(err.Error())​
}​
defer fileReader.Close()​
​
stat, err := fileReader.Stat()​
if err != nil {​
log.Fatal(err.Error())​
}​
request, err := http.NewRequest(http.MethodPut, preSignedEdgeURL, fileReader)​
if err != nil {​
log.Fatal(err.Error())​
}​
request.ContentLength = stat.Size()​
request.Header.Set("Content-Type", "application/octet-stream")​
​
httpClient := &http.Client{}​
response, err := httpClient.Do(request)​
if err != nil {​
log.Fatal(err.Error())​
}​
defer response.Body.Close()​
​
body, err := io.ReadAll(response.Body)​
if err != nil {​
log.Fatal(err.Error())​
}​
if response.StatusCode != http.StatusOK {​
log.Fatalf("http response status code %d, body: %s", response.StatusCode, string(body))​
}​
log.Printf("UploadFile success, written %d bytes\n", stat.Size())​
}​
​
DownloadFile 文件下载​
从云手机实例拉取文件,支持单段 Range 续传。​
Query 参数​
​
参数​
原始数据类型​
是否必选​
示例值​
描述​
Path​
String​
是​
/data/misc/logd/logcat​
待下载文件在实例中的完整路径,指向具体文件(不可为目录),且不得包含 “..”。/logcat、/logcat.txt、/data/misc/logd/logcat 等特殊路径将作为日志文件处理。​
​
Header 参数​
​
参数名​
数据类型​
是否必选​
示例值​
说明​
Range​
String​
否​
bytes=10-1024​
​
指定文件的下载字节范围,用于支持断点续传。格式为 bytes=start-end 或 bytes=start-:​
  • bytes=0-1000:下载文件第 0 至第 1000 个字节。​
  • bytes=10-:从第 10 个字节下载至文件末尾。​
不传此参数时,默认下载完整文件。当前仅支持单段 Range,不支持多段范围请求。​
​
HTTP 响应状态码​
​
HTTP 状态码​
message​
含义​
建议​
200​
success​
文件完整下载成功。​
按响应头接收并保存文件;如需校验完整性,可结合文件大小或业务侧校验信息进行确认。​
206​
success​
Range 下载成功。​
按 Content-Range 写入对应文件片段,确保本地续传偏移与服务端返回范围一致。​
400​
Invalid timestamp format / File path is required / Path is not a file / Invalid range format​
请求参数或 Range 请求格式不合法。​
检查 timestamp、Path 参数和 Range 头格式;Path 必须指向具体文件,Range 建议使用单段范围格式。​
401​
Missing auth headers / Missing signature headers​
请求缺少必要的鉴权信息。​
补齐签名相关请求头后重新发起请求。​
403​
Replay detected / Invalid signature / Path not allowed​
请求鉴权失败,或下载路径不被允许。​
使用新的 nonce 和 timestamp 重新生成签名;同时确认下载路径是否在允许访问范围内。​
404​
File not found​
请求下载的文件不存在。​
确认文件路径是否正确,并检查目标文件是否已生成或上传完成。​
416​
Range not satisfiable​
请求的下载范围无效。​
重新获取远端文件大小并修正本地续传偏移;必要时清理本地缓存后重新下载。​
500​
Auth secret not ready / Failed to access logcat / Permission denied for target path / Failed to read file​
服务端暂时无法读取目标文件。​
稍后重试;如持续失败,检查服务端密钥初始化、文件访问权限、目标路径配置及文件读取状态。​
​
​
示例代码​
以下为使用 Go 语言调用文件下载的示例。​
​
func DownloadFile() {​
​
input := map[string]any{​
"ProductId": "18450134671xxxxxx",​
"PodId": "76395775887xxxxxx",​
"APIType": "DownloadFile",​
"APIPayload": map[string]any{​
"Path": "/data/misc/logd/logcat",​
},​
}​
rawOutput, err := GetACEPClient().GetPreSignedEdgeURLCommon(&input)​
if err != nil {​
log.Fatal(err.Error())​
}​
preSignedEdgeURL, err := getPreSignedEdgeURLFromOutput(rawOutput)​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("get pre-signed edge url success: %s", preSignedEdgeURL)​
​
fileWriter, err := os.Create("./logcat")​
if err != nil {​
log.Fatal(err.Error())​
}​
defer fileWriter.Close()​
​
request, err := http.NewRequest(http.MethodGet, preSignedEdgeURL, nil)​
if err != nil {​
log.Fatal(err.Error())​
}​
request.Header.Set("Content-Type", "application/octet-stream")​
// request.Header.Set("Range", "bytes=0-1000") // 可选参数​
​
httpClient := &http.Client{}​
response, err := httpClient.Do(request)​
if err != nil {​
log.Fatal(err.Error())​
}​
defer response.Body.Close()​
​
body, err := io.ReadAll(response.Body)​
if err != nil {​
log.Fatal(err.Error())​
}​
if response.StatusCode != http.StatusOK {​
log.Fatalf("http response status code %d, body: %s", response.StatusCode, string(body))​
}​
writtenBytes, err := io.Copy(fileWriter, bytes.NewReader(body))​
if err != nil {​
log.Fatal(err.Error())​
}​
log.Printf("DownloadFile success, written %d bytes\n", writtenBytes)​
}​
​
参考​
helpers.go 文件​
​
package main​
​
import (​
"errors"​
"fmt"​
​
"github.com/volcengine/volcengine-go-sdk/volcengine/response"​
)​
​
type GetPreSignedEdgeURLOutput struct {​
Metadata *response.ResponseMetadata​
PreSignedEdgeURL *string `type:"string" json:",omitempty"`​
}​
​
func getPreSignedEdgeURLFromOutput(rawOutput *map[string]any) (string, error) {​
output, err := decodeGetPreSignedEdgeURLCommonOutput(rawOutput)​
if err != nil {​
return "", err​
}​
if output.Metadata != nil && output.Metadata.Error != nil && output.Metadata.Error.CodeN != 0 {​
return "", fmt.Errorf("output metadata error: %+v", output.Metadata.Error)​
}​
if output.PreSignedEdgeURL == nil || *output.PreSignedEdgeURL == "" {​
return "", errors.New("output pre-signed edge url is nil or empty")​
}​
return *output.PreSignedEdgeURL, nil​
}​
​
func decodeGetPreSignedEdgeURLCommonOutput(raw *map[string]any) (*GetPreSignedEdgeURLOutput, error) {​
if raw == nil {​
return nil, fmt.Errorf("raw output is nil")​
}​
​
root := *raw​
​
metadataMap, err := getObjectField(root, "ResponseMetadata")​
if err != nil {​
return nil, err​
}​
​
resultMap, err := getObjectField(root, "Result")​
if err != nil {​
return nil, err​
}​
​
preSignedEdgeURLValue, ok := resultMap["PreSignedEdgeURL"]​
if !ok {​
return nil, fmt.Errorf("missing Result.PreSignedEdgeURL")​
}​
​
preSignedEdgeURL, ok := preSignedEdgeURLValue.(string)​
if !ok {​
return nil, fmt.Errorf("invalid type for Result.PreSignedEdgeURL")​
}​
​
errorMap, err := getOptionalObjectField(metadataMap, "Error")​
if err != nil {​
return nil, err​
}​
​
var metadataError *response.Error​
if errorMap != nil {​
metadataError = &response.Error{​
CodeN: getInt(errorMap, "CodeN"),​
Code: getString(errorMap, "Code"),​
Message: getString(errorMap, "Message"),​
}​
}​
​
output := &GetPreSignedEdgeURLOutput{​
Metadata: &response.ResponseMetadata{​
RequestId: getString(metadataMap, "RequestId"),​
Action: getString(metadataMap, "Action"),​
Version: getString(metadataMap, "Version"),​
Service: getString(metadataMap, "Service"),​
Region: getString(metadataMap, "Region"),​
Error: metadataError,​
},​
PreSignedEdgeURL: &preSignedEdgeURL,​
}​
​
return output, nil​
}​
​
func getObjectField(obj map[string]any, key string) (map[string]any, error) {​
value, ok := obj[key]​
if !ok {​
return nil, fmt.Errorf("missing %s", key)​
}​
​
field, ok := value.(map[string]any)​
if !ok {​
return nil, fmt.Errorf("invalid type for %s", key)​
}​
​
return field, nil​
}​
​
func getOptionalObjectField(obj map[string]any, key string) (map[string]any, error) {​
value, ok := obj[key]​
if !ok || value == nil {​
return nil, nil​
}​
​
field, ok := value.(map[string]any)​
if !ok {​
return nil, fmt.Errorf("invalid type for %s", key)​
}​
​
return field, nil​
}​
​
func getString(obj map[string]any, key string) string {​
value, ok := obj[key]​
if !ok {​
return ""​
}​
​
result, ok := value.(string)​
if !ok {​
return ""​
}​
​
return result​
}​
​
func getInt(obj map[string]any, key string) int {​
value, ok := obj[key]​
if !ok {​
return 0​
}​
​
switch result := value.(type) {​
case int:​
return result​
case int32:​
return int(result)​
case int64:​
return int(result)​
case float64:​
return int(result)​
default:​
return 0​
}​
}​
​
最近更新时间:2026.11.02 16:06:11
这个页面对您有帮助吗?
有用
有用
无用
无用