.net core 美团闪购对接---授权
.net 难道真的不行了吗?真无奈呀,上次做支付对接,官方示例没有C#语言案例,这次做美团和饿了吗对接也没有C#示例的影子,大多都是java和php示例。做支付的时候,可以在网上搜一些案例,也有开源的人家封装好的案例,做起来有样板容易很多。这次做美团接口对接。哎,让我无语了,C#案例的我一个都没搜到。在网上一根毛都找不到。突然没有案例,这让我这个程序小裁缝怎末办?没办法只能自己根据api文档慢慢调试了,还好做了好几年开发,老脸还是保住了,经过一番折腾,终于接通了。哈哈哈哈。。。
要做美团接口对接,自己要注册一个开发者账号,不过要提交审核资质,注意:这个必须通过真正的企业去认证,过程中要上传营业执照的。这样审核才能通过。哎,真是搞不懂,你就不能给开发者留条路,设了这个门槛,要测试对接就没法做了。如果要测试只能审核通过才能申请一个测试门店。
至于如何注册这里就不多说了,自己根据官方文档一步一步来就行了。注册好后,申请一个测试门店,通过后如下可以查看门店信息:
这里我们需要几个重要字段: 1,appid:应用Id 2app screte 应用密钥 3,三方门店ID
有了这两个重要参数,接下来就是如何调用接口了,根据官方文档介绍,如果要调用接口,必须要获得ISV安全认证,也就是应用授权。如何授权,请看:https://open-shangou.meituan.com/home/guide/market/10686
这个流程看起来很详细,但是操作起来就麻烦了,我大致说一下啊流程:
OAuth认证要两种方式,一种是官方推荐使用的,需要两步才能获取access_token,一种是快捷方式,只需要一步就能获取access_token值。
第一种方式:先调用https://waimaiopen.meituan.com/api/v1/oauth/authorize 获取Code值,然后在把Code值作为参数传入接口:https://waimaiopen.meituan.com/api/v1/oauth/token 获取acces_token值,后续调用其它接口都需要携带这个access_token值才能访问。
第二种方式:直接调用https://waimaiopen.meituan.com/api/v1/oauth/authorize 就可以获取access_token值。
这两种方式唯一区别就是authorize 接口参数response_type的值不行同,如何response_type= code 获取的就是code值,需要两部才能获取到access_token. 如果response_type=token 就会直接获取access_token值。不要以为这样就可以了,这个过程你还需要对传入的参数尽心加密,也就是生签,对应参数sig,这里的加密方式是用MD5 32位加密方式对url和参数一起进行加密。具体加密方式请看:(https://opendj.meituan.com/home/guide/market/10683 )注意这里坑很多,不要粗略的看,要仔细,参数拼接是按照参数字母拍寻拼接的,不是随意拼接参数。我这里举个例子。就以生成code为例:
需要参数:
注意这里面有坑,需要一个timestamp时间戳参数。而这里面并没有给。所以记得加上。
第一步:拼接参数:注意:拼接的时候要按照参数字母排序,至于怎末排序,后续我会给方法。
排序后后拼接参数:app_Id=123&app_poi_code=34223&response_type=code×tamp=124324324
第二步:url+"?"+拼接后的参数+app secret(应用密钥)
url+app_Id=123&app_poi_code=34223&response_type=code×tamp=124324324+app secret(注意:这里直接把密钥值放在后面,不要加参数)
第三步:对整个url路径进行MD5加密;加密后获取sig值;
sig=md5(url+app_Id=123&app_poi_code=34223&response_type=code×tamp=124324324+app secret)
第四步:在将sig作为参数放在url路径中:
url+app_Id=123&app_poi_code=34223&response_type=code×tamp=124324324&sig=sig值(注意:这里不要在app secret密钥了)
这里流程大概就是这样了。
下面直接上代码:
首先是Post和Get请求,已经封装好了,直接调用就行了。注意(美团开放接口用的https,所以请求的时候要忽略认证。代码我已经做了忽略认证了,可以直接用)
public class HttpHelper { /// <summary> /// /// </summary> /// <typeparam name="T"></typeparam> /// <param name="requestUrl"></param> /// <returns></returns> public async static Task<T> DoGet<T>(string requestUrl, object paraModel) where T : new() { var res = new T(); using (var httpClientHandler = new HttpClientHandler()) { httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; using (var client = new HttpClient(httpClientHandler)) { string url = GetUrl(requestUrl, paraModel); client.BaseAddress = new Uri(url); var result = await client.GetStringAsync(url);//.GetAwaiter().GetResult(); res = (T)JsonConvert.DeserializeObject<T>(result); } } return res; } public static async Task<T> DoPost<T>(string requestUrl, object paraModel, string accessToken = "") where T : new() { var res = new T(); using (var httpClientHandler = new HttpClientHandler()) { httpClientHandler.ServerCertificateCustomValidationCallback = (message, cert, chain, errors) => { return true; }; using (var client = new HttpClient(httpClientHandler)) { try { string url = GetUrl(requestUrl, paraModel); HttpRequestMessage request = new HttpRequestMessage(HttpMethod.Post,url); request.Headers.Add("Accept", "application/x-www-form-urlencoded"); //if (!string.IsNullOrEmpty(accessToken)) //{ // string value =$"Bearer {accessToken}"; // request.Headers.Add("Authorization", value); //} HttpResponseMessage response = client.SendAsync(request).Result; var respStr = await response.Content.ReadAsStringAsync(); res = respStr.ToObject<T>(); } catch (Exception ex) { throw; } } } return res; } /// <summary> /// 拼接Url请求路径(生成sig签名) /// </summary> /// <typeparam name="T"></typeparam> /// <param name="url"></param> /// <param name="TModel"></param> /// <returns></returns> public static string GetUrl<T>(string url, T TModel) { Hashtable hash = new Hashtable(); var timestamp = DateTimeOffset.Now.ToUnixTimeSeconds(); hash.Add("app_id", ConfigHelper.appid); hash.Add("timestamp", timestamp); if (TModel != null) { //取得m的Type实例 Type t = TModel.GetType(); //获取所有属性 PropertyInfo[] properties = t.GetProperties(); //取得类的属性名并获取属性值 foreach (PropertyInfo s in t.GetProperties()) { hash.Add(s.Name, TModel.GetType().GetProperty(s.Name).GetValue(TModel, null).ToString()); } } ArrayList keys = new ArrayList(hash.Keys); keys.Sort(); //按字母顺序进行排序 string strSig = ""; foreach (string key in keys) { if (key != null && key != "" && key != "sign") { strSig += "&" + key + "=" + hash[key]; } } strSig = strSig.TrimStart('&'); string strUrl = url + "?" + strSig ; string createSig = MD5Encrypt.Get32MD5(strUrl + ConfigHelper.appsecret).ToLower(); string requestUrl = strUrl + "&sig=" + createSig; return requestUrl; } }
model 用来反序化返回值:
public class AuthorizeResult { public int status { get; set; } public string code { get; set; } public string state { get; set; } public string message { get; set; } }
md5 32位加密:
/// <summary> /// 此代码示例通过创建哈希字符串适用于任何 MD5 哈希函数 (在任何平台) 上创建 32 个字符的十六进制格式哈希字符串 /// 官网案例改编 /// </summary> /// <param name="source"></param> /// <returns></returns> public static string Get32MD5(string source) { using (MD5 md5Hash = MD5.Create()) { byte[] data = md5Hash.ComputeHash(Encoding.UTF8.GetBytes(source)); StringBuilder sBuilder = new StringBuilder(); for (int i = 0; i < data.Length; i++) { sBuilder.Append(data[i].ToString("x2")); } string hash = sBuilder.ToString(); return hash.ToUpper(); } }
业务处理:
/// <summary> /// 获取商家授权码code的接口 (Get) /// </summary> public async static Task<AuthorizeResult> Authorize() { var model = new { app_poi_code = ConfigHelper.GoodId, //门店代码。 response_type = "code" //授权方式,此处response_type=code。 }; var result = await HttpHelper.DoGet<AuthorizeResult>(ConfigHelper.GetAuthorize, model); return result; }
调用:
var result = OauthAPI.Authorize().Result;
这里已经成功了,之所以报这个问题,是因为我已经获取的授权码了,如果授权码不过期,或者自己不调用刷新接口刷新。调用用就会出现已经发放的问题。
有一个案例后续其实都一样的。我对接了一笑部分,基本都差不多,封装好后就是直接调用了。
对接美团接口的过程中,我发现美团的官方api是真的乱。
1,参数部分没有明确说明:比如每次请求必带有appid和timestamp,但是api接口没有给。
2,参数返回值的类型,根本不统一,很乱。
3,出现层级参数,好多都需要转换成json字符串传入,不能传入对象。