Youmi video callback protocol

来自有米广告开发者WIKI
跳转至: 导航搜索



有米视频广告服务器回调协议

接口说明

  • 接口统一使用的编码为:UTF-8
  • 适合于贵方使用自己的服务器来接受视频播放情况的回调
  • 当我们确认该次播放有效时,我们会实时反馈给贵方服务器
  • 需要贵方提供一个接口来接收数据(有米开发者后台提供设置页面),接口接收数据的方式:GET


使用流程

  1. 贵方在有米的开发者后台设置自己应用的回调地址,设置地址:https://www.youmi.net/apps/setting
  2. 当有米的服务器接收到一个有效的效果记录时,就会按贵方提供的反馈地址,加上相关的参数以 GET 方式一起反馈给贵方的服务器(具体参数见下面的表格)


回调参数说明

Key 类型 说明 备注
order 字符串 订单号 订单ID:该值是唯一的,如果开发者接收到相同的订单号,那说明该订单已经存在
app 字符串 应用 ID 开发者应用 ID
ad 字符串 广告名 广告名,如果是应用类型的广告则是应用名
注:参数经过urlencode,请使用urldecode获取原始参数
adid 整型 广告 ID
user 字符串 贵方传入的用户 ID 贵方没有传入则返回空
points 整型 (已弃用)固定奖励,贵方在有米开发者后台开启奖励模式后设置的奖励分数。已弃用无法设置,但是字段保留返回,贵方可以忽略本字段。
time 整型 回调发送时间
device 字符串 设备 ID,Android 平台返回 imei,iOS 平台返回 ifa
storeid 整型 应用商店 ID,iOS 平台返回 某些广告类型该字段可能为空
trade_type 整型 回调类型 1是播放完成回调,2是分享成功回调
sign 字符串 参数串签名


sign 签名参数生成方法

生成方法

将【参数】列表中除“sign”外的所有参数作为 key 求 MD5 值。

假设参与参数签名计算的请求参数分别是“k1”、“k2”、“k3”,它们的值分别是“v1”、“v2”、“v3”,则参数签名计算方法如下:

  • 将请求的参数格式化为“key=value”格式,即“k1=v1”、“k2=v2”、“k3=v3”;
  • 将格式化好的参数键值对以字典序升序排列后,拼接在一起,即“k1=v1k2=v2k3=v3”;
  • 在拼接好的字符串末尾追加上 $secret;
  • 上述字符串的 MD5 值即为签名的值。

注意事项

  • 计算签名时的请求参数中不要包含 sign(签名)参数,因为 sign 参数的值此时还不知道,有待计算。
  • 签名过程中的各个参数并未经过 urlencode 处理
  • 为保证回调参数变更时,签名不会出现异常,请确保务必以我们提供的签名函数作为验证手段(或以相同算法实现),以避免因为参数变更而导致签名验证失效影响数据的获取。

签名说明

  • 本协议的所有参数均采用 UTF-8。
  • 部分字段可能包含有特殊字符,在接收到请求时请自行使用 urldecode。
  • $secret:签名加密密钥,由有米单独提供给贵方。 请 登 录 后 进 入“https://www.youmi.net/apps/setting” 进行服务器地址设置,设置成功后就会分配一个密钥给您的应用,切记,服务器秘钥,并非应用秘钥,请做好区分。

Video Settings.png

返回值

  1. 有米会根据开发者服务器返回的 http 状态码(http_code)来判断该进行什么样的操作,正常情况收到的 http 状态码应该是200或者403。
  2. 如果是200,表示开发者正常接收到信息并且正常处理了。
  3. 如果是403,表示开发者拒绝了该次请求,此时中间层服务器不会再重复请求开发者服务器。
  4. 如果超时、或收到的不是2xx、301、302、303、307、400、403,有米的服务器就会放在下一次循环请求开发者服务器。
  5. 下一次循环请求开发者服务器会有一定的延迟,延迟分别为:5s, 10s, 60s, 300s, 600s, 3600s(距离上一次发送)。即最差环境下,有米最多将发送7次请求,若7次请求全部失败,则该链接将被丢弃。
  6. 因为网络等问题,开发者服务器可能会接收到订单号完全相同的多条记录,这时开发者服务器需要把重复的丢失,并以 http 状态码(http_code)403输出。也就是说:开发者服务器需要对接收到的信息进行去重


附录 签名算法范例

PHP 实现

 <?php
 $url = 'http://api.youmi.com/callback/youmiios?order=YM140927--uPMAL-c7&app=9076333dcfc7f490&ad=去哪儿攻略&adid=4188&user=1067748&chn=0&points=979&price=1.96&time=1411751092&device=0AD80C3C-D320-AC2B-5FD3-994E2FA7A153&storeid=555610791&sig=8ef41e70';
 $dev_server_secret = '1234567890';
 $url .= '&sign=' . getUrlSignature($url, $dev_server_secret);
 echo $url, "\n"; 
 
 function getUrlSignature($url, $secret){
   $params = array();
   $url_parse = parse_url($url);
   if (isset($url_parse['query'])){
     $query_arr = explode('&', $url_parse['query']);
     if (!empty($query_arr)){
       foreach($query_arr as $p){
         if (strpos($p, '=') !== false){
           list($k, $v) = explode('=', $p);
           $params[$k] = urldecode($v);
         }
       }
     }
   }
   return getSignature($params, $secret);
 }
 
 function getSignature($params, $secret){
   $str = '';
   ksort($params);
   foreach ($params as $k => $v) {
     $str .= "{$k}={$v}";
   }
   $str .= $secret;
   return md5($str);
 }


Java 实现

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLDecoder;
import java.util.TreeMap;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.security.MessageDigest; 
import java.security.GeneralSecurityException; 
 
public class YoumiSign {
    /**
     * 签名生成算法
     *
     * @param HashMap<String,String> params 请求参数集,所有参数必须已转换为字符串类型
     * @param String                 dev_server_secret 开发者在有米后台设置的密钥
     * @return String
     * @throws IOException
     */
    public static String getSignature(HashMap<String, String> params, String dev_server_secret) throws IOException {
        // 先将参数以其参数名的字典序升序进行排序
        Map<String, String> sortedParams = new TreeMap<String, String>(params);
 
        Set<Map.Entry<String, String>> entrys = sortedParams.entrySet();
        // 遍历排序后的字典,将所有参数按"key=value"格式拼接在一起
        StringBuilder basestring = new StringBuilder();
        for (Map.Entry<String, String> param : entrys) {
            basestring.append(param.getKey()).append("=").append(param.getValue());
        }
        basestring.append(dev_server_secret);
        //System.out.println(basestring.toString());
        // 使用MD5对待签名串求签
        byte[] bytes = null;
        try {
            MessageDigest md5 = MessageDigest.getInstance("MD5");
            bytes = md5.digest(basestring.toString().getBytes("UTF-8"));
        } catch (GeneralSecurityException ex) {
            throw new IOException(ex);
        }
        // 将MD5输出的二进制结果转换为小写的十六进制
        StringBuilder sign = new StringBuilder();
        for (int i = 0; i < bytes.length; i++) {
            String hex = Integer.toHexString(bytes[i] & 0xFF);
            if (hex.length() == 1) {
                sign.append("0");
            }
            sign.append(hex);
        }
        return sign.toString();
    }
 
    /**
     * 对一条完整的未签名的URL做签名,并将签名结果添加到URL的末尾
     * 
     * @param String url 未做签名的完整URL
     * @param String dev_server_secret 签名秘钥
     * @return String
     * @throws IOException, MalformedURLException
     */
    public static String getUrlSignature(String url, String dev_server_secret) throws IOException, MalformedURLException {
        try {
            URL urlObj = new URL(url);
            String query = urlObj.getQuery();
            String[] params = query.split("&");
            Map<String, String> map = new HashMap<String, String>();
            for (String each : params) {
                String name = each.split("=")[0];
                String value;
                try {
                    value = URLDecoder.decode(each.split("=")[1], "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    value = "";
                }
                map.put(name, value);
            }
            String signature = getSignature((HashMap<String, String>) map, dev_server_secret);
            return url + "&sign=" + signature;
        } catch (MalformedURLException e) {
            throw e;
        } catch (IOException e) {
            throw e;
        }
    }
 
    /**
     * 检查一条完整的包含签名参数的URL,其签名是否正确
     * 
     * @param String url 已经签名的完整URL
     * @param String dev_server_secret 签名秘钥
     * @return boolean
     */
    public static boolean checkUrlSignature(String signedUrl, String dev_server_secret) {
        String urlSign = "";
        try {
            URL urlObj = new URL(signedUrl);
            String query = urlObj.getQuery();
            String[] params = query.split("&");
            Map<String, String> map = new HashMap<String, String>();
            for (String each : params) {
                String name = each.split("=")[0];
                String value;
                try {
                    value = URLDecoder.decode(each.split("=")[1], "UTF-8");
                } catch (UnsupportedEncodingException e) {
                    value = "";
                }
                if ("sign".equals(name)) {
                    urlSign = value;
                } else {
                    map.put(name, value);
                }
            }
            if ("".equals(urlSign)) {
                return false;
            } else {
                String signature = getSignature((HashMap<String, String>) map, dev_server_secret);
                return urlSign.equals(signature);
            }
        } catch (MalformedURLException e) {
            return false;
        } catch (IOException e) {
            return false;
        }
    }
}


Objective-C 实现

//
//  AppDelegate.m
//  sign
//
//  Created by Enzo Yang on 11/7/14.
//  Copyright (c) 2014 YouMi. All rights reserved.
//
 
#import "AppDelegate.h"
#import <CommonCrypto/CommonHMAC.h>
 
 
@interface URLSigner : NSObject
 
// 根据查询参数得到URL签名
- (NSString *)generateSignWithDict:(NSDictionary *)params serverSecret:(NSString *)serverSecret;
 
@end
 
@implementation URLSigner
 
- (NSString *)generateSignWithDict:(NSDictionary *)params serverSecret:(NSString *)serverSecret {
    NSArray *keys = [params allKeys];
 
    // key 按字母顺序排序
    NSArray *sortedKeys = [keys sortedArrayUsingComparator:^NSComparisonResult(id obj1, id obj2) {
        return [obj1 compare:obj2 options:NSLiteralSearch];
    }];
 
    // 把查询字符串按顺序按 k1=v1k2=v2 的格式拼起来
    NSMutableString *str = [[NSMutableString alloc]init];
    for (NSString *key in sortedKeys) {
        [str appendString:[NSString stringWithFormat:@"%@=%@", key, [params objectForKey:key]]];
    }
 
    // 拼上密钥
    [str appendString:serverSecret];
 
    // MD5 后得到签名
    return [self md5HexDigest:str];
}
 
- (NSString*)md5HexDigest:(NSString*)input {
    const char* str = [input UTF8String];
    unsigned char result[CC_MD5_DIGEST_LENGTH];
    CC_MD5(str, (uint32_t)(strlen(str)), result);
 
    // 32位小写的MD5
    NSMutableString *ret = [NSMutableString stringWithCapacity:CC_MD5_DIGEST_LENGTH*2];
    for(int i = 0; i < CC_MD5_DIGEST_LENGTH; i++) {
        [ret appendFormat:@"%02x",result[i]];
    }
 
    return ret;
}
@end