异步任务接口回调模式使用方法

1. 联系管理员配置回调信息

由于云手机侧需要开通用户侧的网路策略,使用回调必须联系管理员添加回调接口信息。

2. 构造回调接口

2.1 接口描述
用于接收云手机侧发送的异步任务的结果信息(成功或者失败)。

2.2 请求方法和URI
请求方式:POST
数据类型:JSON
响应类型:JSON
接口路径:https://xxx.com/chinamobile/cloudphone/async-task/callback

2.3 请求头参数

序号 名称 类型 描述 ​必选​​\​​可选
1 callbackSignature String 请求体签名值,用于鉴权 可选

2.4 请求体参数

名称 类型 描述 ​必选​​\​​可选
1 phoneId String 云手机ID 必选
2 jobId String 任务id 必选
3 beginTime String 任务处理开始时间, 时间格式为UTC,YYYY- MM-DDTHH:MM:SSZ 必选
4 endTime String 任务处理结束时间, 时间格式为UTC,YYYY- MM-DDTHH:MM:SSZ 必选
5 status String 任务状态。2: 成功 -1 :失败 必选
6 errorCode String 任务错误码 可选
7 errorMsg String 任务错误码说明 可选
8 executeMsg String 任务执行返回内容,最长1024个字节 可选
9 content String 任务类容 可选
10 type Integer 任务类型(更新中):1:开机、2:关机、3:重启、4:初始化、5:更换镜像、6:安装应用、7:卸载应用、8:执行shell命令、9:执行shell异步命令、10:推送文件、11:备份、12:恢复、13:绑定云硬盘、14:解绑云硬盘、15:更新云手机属性、16:三合一重置、17:三合一重启、18:重启并重置、19:备份,无pod、20:修改机型设备属性、21:截图、22:推送文件到指定位置、23:更换机型、24:异步应用列表、25:订购、26:退订、27:冻结、28:恢复、29:移入回收站、30:从回收站恢复、31:租户回收分配出去的云手机、32:设置云机互踢属性、33:云机拷贝、34:设置云机应用黑名单 可选

2.5 返回结果
暂不要求具体格式,云手机侧调用用户提供的回调接口以接口响应的HTTP状态码为200,即视为回调成功。
响应状态码不为200,或是响应时间超过5秒判定为超时,都视为回调失败。若回调失败(客户回调url配置错误、客户回调服务异常、网络波动等)云手机侧会重试回调2次,即最多回调3次。

3. 鉴权校验

云手机侧通过RSA 3072bits非对称加密算法对回调接口的请求体进行私钥加密,生成签名放到请求头中,客户根据公钥对签名值解密,防止第三方篡改回调请求体;

公钥:MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAg77tZpqWyKhaWyDe+Xw+75nJ29hDlisj1+EHoQCMZzrr2pAKMuGPg5mi1pxHlzIjqcM+8iK+E7+qQi8w5F5qzatxgjjkfoe81muCcNSxrfE58YlLHAG32x3LMTLTTd9iMHaiQLXbPQVthLdhu/lN21LRKEXB8inBmb4QXd0YhdwjSNh7TaBHKCfW3trcQq3usEgJM7A2XY8LrpLOBI59VpNFpHJ4w0R/3N5/142eSjHMOFPvw7ZS6V0SyIScCc2Ftam0Ikv9PNzDwxd7C3FrJLYuWdKcwUd/Vq1rhEuLxol1JFyp6WnFfxBoBxxOJSvh/glnaNQLSMLzfG8yoXXUgqr8yVFENN94pfubHxfyOD/nQRxsQ4jrJYfPnzF7xA0f6j4Qz7qLc3d4DZDqeugqDUz695BwN1dOp2egF74GVTGb9JgLqpbaSUJQdkZ80tcOaoawXd4KHBGURqceUovHlDgrbnwu1sFDvoUCsDCaygRtsO/IWlpoyWlCxbRnjiMjAgMBAAE=

回调接口样例:

package com.cmss.ecloud.resource.controller.openapi;

import com.cmss.ecloud.resource.dto.base.openapi.OpenapiBaseResp;
import com.cmss.ecloud.resource.dto.req.openapi.CustomerCallback.OpenApiCallbackMockReq;
import com.cmss.ecloud.resource.service.OpenapiCustomerCallbackExampleService;
import io.swagger.annotations.Api;
import lombok.extern.slf4j.Slf4j;
import org.springframework.web.bind.annotation.*;
import javax.annotation.Resource;
import javax.validation.Valid;

@Slf4j
@Api(tags = "OpenAPI-客户回调管理")
@RestController
@RequestMapping("/api/v1/zycloudphone/resource/zh/phones/callback")
public class OpenapiCustomerCallbackController extends OpenapiBaseController {
    @Resource
    private OpenapiCustomerCallbackExampleService openapiCustomerCallbackExampleService;

    @PostMapping("/callback/example")
    public OpenapiBaseResp<String> callbackExample(@RequestBody @Valid final OpenApiCallbackMockReq req, @RequestHeader(value = "callbackSignature", required = false) @Valid final String callbackSignature) {
        return success(openapiCustomerCallbackExampleService.callbackExample(req,callbackSignature));
    }
}
​
package com.cmss.ecloud.resource.service;

import com.cmss.ecloud.resource.dto.req.openapi.CustomerCallback.OpenApiCallbackMockReq;

public interface OpenapiCustomerCallbackExampleService {
    String callbackExample(OpenApiCallbackMockReq req,String callbackSignature);
}
​
package com.cmss.ecloud.resource.service.impl;

import com.alibaba.fastjson.JSON;
import com.cmss.ecloud.resource.dto.req.openapi.CustomerCallback.*;
import com.cmss.ecloud.resource.service.OpenapiCustomerCallbackExampleService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.BeanUtils;
import org.springframework.stereotype.Service;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.Signature;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;

@Slf4j
@Service
public class OpenapiCustomerCallbackExampleServiceImpl implements OpenapiCustomerCallbackExampleService {

    private String callbackPublicKey = "MIIBojANBgkqhkiG9w0BAQEFAAOCAY8AMIIBigKCAYEAg77tZpqWyKhaWyDe+Xw+75nJ29hDlisj1+EHoQCMZzrr2pAKMuGPg5mi1pxHlzIjqcM+8iK+E7+qQi8w5F5qzatxgjjkfoe81muCcNSxrfE58YlLHAG32x3LMTLTTd9iMHaiQLXbPQVthLdhu/lN21LRKEXB8inBmb4QXd0YhdwjSNh7TaBHKCfW3trcQq3usEgJM7A2XY8LrpLOBI59VpNFpHJ4w0R/3N5/142eSjHMOFPvw7ZS6V0SyIScCc2Ftam0Ikv9PNzDwxd7C3FrJLYuWdKcwUd/Vq1rhEuLxol1JFyp6WnFfxBoBxxOJSvh/glnaNQLSMLzfG8yoXXUgqr8yVFENN94pfubHxfyOD/nQRxsQ4jrJYfPnzF7xA0f6j4Qz7qLc3d4DZDqeugqDUz695BwN1dOp2egF74GVTGb9JgLqpbaSUJQdkZ80tcOaoawXd4KHBGURqceUovHlDgrbnwu1sFDvoUCsDCaygRtsO/IWlpoyWlCxbRnjiMjAgMBAAE=";

    /**
     * 将公钥字符串转化为 公钥对象
     * */
    private PublicKey loadPublicKeyFromString(String keyString) throws Exception {
        byte[] keyBytes = Base64.getDecoder().decode(keyString);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory kf = KeyFactory.getInstance("RSA");
        return kf.generatePublic(spec);
    }

    /**
     * 对字符串plainText进行SHA-256哈希处理生成哈希值B
     * 用RSA-3072bits的公钥对加密过的哈希值A进行解密处理,得到哈希值A
     * 判断哈希值A哈希值B与是否一致,一致则证明数据没被篡改
     * */
    private boolean verify(String plainText, String signature, PublicKey publicKey) throws Exception {
        Signature publicSignature = Signature.getInstance("SHA256withRSA");
        publicSignature.initVerify(publicKey);
        publicSignature.update(plainText.getBytes());
        return publicSignature.verify(Base64.getDecoder().decode(signature));
    }

    @Override
    public String callbackExample(OpenApiCallbackMockReq req, String callbackSignature) {
        boolean isVerified = false;
        if (callbackSignature != null) {
            OpenapiCallbackJobInfoDto openapiCallbackJobInfoDto = new OpenapiCallbackJobInfoDto();
            //对请求体进行规范处理,便于校验
            BeanUtils.copyProperties(req, openapiCallbackJobInfoDto);
            String requestBody = JSON.toJSONString(openapiCallbackJobInfoDto);
            try {
                PublicKey publicKey = loadPublicKeyFromString(callbackPublicKey);
                isVerified = verify(requestBody, callbackSignature, publicKey);
            }catch (Exception e){
                log.error(e.getMessage());
            }
            if (!isVerified){
                return "mock failed";
            }
        }
        return "mock success";
    }
}
​
package com.cmss.ecloud.resource.dto.req.openapi.CustomerCallback;

import com.fasterxml.jackson.annotation.JsonFormat;
import io.swagger.annotations.ApiModel;
import io.swagger.annotations.ApiModelProperty;
import lombok.Data;
import java.util.Date;

@Data
@ApiModel(value = "回调规范化请求体")
public class OpenapiCallbackJobInfoDto {

    @ApiModelProperty("云手机ID")
    private String phoneId;

    @ApiModelProperty("任务ID")
    private String jobId;

    @ApiModelProperty("任务处理开始时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date beginTime;

    @ApiModelProperty("任务处理结束时间")
    @JsonFormat(pattern = "yyyy-MM-dd HH:mm:ss", timezone = "GMT+8")
    private Date endTime;

    @ApiModelProperty("状态:1 :运行中, 2 : 成功,-1 :失败")
    private Integer status;

    @ApiModelProperty("任务错误码")
    private String errorCode;

    @ApiModelProperty("任务错误码说明")
    private String errorMsg;

    @ApiModelProperty("任务执行返回内容,最长1024个字节")
    private String executeMsg;

    @ApiModelProperty("任务类容")
    private String content;

    @ApiModelProperty("任务类型(更新中):1:开机、2:关机、3:重启、4:初始化、5:更换镜像、6:安装应用、7:卸载应用、8:执行shell命令、9:执行shell异步命令、10:推送文件、11:备份、12:恢复、13:绑定云硬盘、14:解绑云硬盘、15:更新云手机属性、16:三合一重置、17:三合一重启、18:重启并重置、19:备份,无pod、20:修改机型设备属性、21:截图、22:推送文件到指定位置、23:更换机型、24:异步应用列表、25:订购、26:退订、27:冻结、28:恢复、29:移入回收站、30:从回收站恢复、31:租户回收分配出去的云手机、32:设置云机互踢属性、33:云机拷贝、34:设置云机应用黑名单")
    private Integer type;

}
​

4. 支持回调模式的异步任务OpenAPI

  • 云手机开机
  • 云手机关机
  • 云手机初始化
  • 云手机重启
  • 异步执行shell命令
  • 云手机更新属性
  • 恢复云手机数据
  • 导出云手机数据
  • 推送并解压tar文件
  • 云手机卸载应用
  • 云手机安装应用
    (更新中)
本文档内容整理自移动云官方帮助中心(查看原文),仅供参考。