根据 接入方式及流程 中的说明,可知,搭建过程中,我们需要自己整一个 OAuth 的授权平台,具体说明可以参考蟋蟀大哥的文章  ASP.NET WebApi OWIN 实现 OAuth 2.0 ,我的实际代码也是基于文章给出的源码修改的。

第一步

认真研究一次文档:

(1)AliGenie在开发商开放平台或者其他第三方平台注册一个应用,获取到相应的Client id 和Client secret

(2)AliGenie 应用向开发商OAuth2.0服务发起一个授权请求

(3)开发商OAuth2.0服务向用户展示一个授权页面,用户可进行登陆授权

(4)用户授权AliGenie客户端应用后,进行回跳到AliGenie 的回调地址上并带上code相关参数

(5)AilGenie回调地址上根据code会去合作方Oauth 的服务上换取 access_token

(6)通过access_token,天猫精灵设备控制时通过该access_token进行访问合作方的服务

关键字已经用颜色标明,也就是说,我们需要一个授权平台,授权页,code 换取 access_token 的功能,分配给 天猫的 clien_id,client_secret.

最主要的还是平台,具体的平台怎么搭建就不说了,参考一下上面给出的链接,照着做或者改就行了。

因为我们对接天猫精灵主要用的是授权码模式,所以我们主看授权码的方式,授权码模式 的流程请查看 ASP.NET WebApi OWIN 实现 OAuth 2.0

这儿顺便说一下,如果你用的是 .net 4.5 的情况下,安装 OWIN ,用NuGet安装时会报错,提示不兼容对应的框架,所以用控制台安装旧版的,

  • Owin 对应:  Install-Package Microsoft.Owin -Version 3.1.0
  • Microsoft.Owin.Host.SystemWeb 对应:Install-Package Microsoft.Owin.Host.SystemWeb -Version 3.1.0
  • Microsoft.Owin.Security.OAuth 对应:Install-Package Microsoft.Owin.Security.OAuth -Version 3.1.0
  • Microsoft.Owin.Security.Cookies 对应:Install-Package Microsoft.Owin.Security.Cookies -Version 3.1.0
  • Microsoft.AspNet.Identity.Owin 对应:Install-Package Microsoft.AspNet.Identity.Owin -Version 2.1.0

授权页面

我们直接以对接天猫精灵的流程来简单说一下代码应该怎么改吧。

根据官方文档提示,我们要给他天猫精灵配置一个 client_id ,client_secrete,检查源码知道,它已经默认写死了,client_id=xishuai,client_secrete=123  .我们先记下,等会再配置。

按着天猫精灵的流程,我们还需要一个 适配手机端访问的授权的 H5 页,那我们添加一个控制器(Home),加个 Index 方法用来返回视图,再加一个 Index2 准备用来接收它提交过来的数据,再给它加个视图,名字就定为 Index ,在里面放上几个 input 标签,准备用来存放天猫传过来的参数的(后续再隐藏起来),样式就先不处理了,晚点再说。

控制器:

        public ActionResult Index()
{
return View();
} [HttpPost]
public ActionResult Index2()
{
return View();
}

视图:

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
<form id="form1" method="post" action="@Url.Action("Index2")">
<input type="text" name="clientId" value="xishuai" />
<br />
<input type="text" name="redirect_uri" value="" />
<br /> <input type="text" name="response_type" value="" />
<br />
<input type="text" name="state" value="" />
<br />
<input type="submit" value="提交" />
</form> </div>
</body>
</html>

看一下 官方文档-接入方式及流程 ,在第5点得知,请求的链接如下:

原文档链接:https://xxx.com/auth/authorize?redirect_uri=https%3A%2F%2Fopen.bot.tmall.com%2Foauth%2Fcallback%3FskillId%3D11111111%26token%3DXXXXXXXXXX&client_id=XXXXXXXXX&response_type=code&state=111
对应到我们这儿的链接将会是:
https://xxx.com/Home/Index?redirect_uri=https%3A%2F%2Fopen.bot.tmall.com%2Foauth%2Fcallback%3FskillId%3D11111111%26token%3DXXXXXXXXXX&client_id=XXXXXXXXX&response_type=code&state=111

redirect_uri: 处理完成的返回地址,这个我们不应该去动它.
client_id: 在合作方上注册的应用Id(也就是我们刚才说到的准备给他配置的那个 client_id 值:xishuai )

state: 在我代码中,我是直接无视了这个参数的。项目目前没有用到它。

既然知道有这么多参数,那我们整理一下,在控制器接收一下,返回到视图界面对应的地方吧.代码如下:

Home 控制器中的 Index 方法:

        public ActionResult Index()
{
string clientId = Request.QueryString["client_id"] + "";
string redirect_uri = Request.QueryString["redirect_uri"] + "";
string response_type = Request.QueryString["response_type"] + "";
string state = Request.QueryString["state"] + ""; ViewBag.clientId = clientId;
ViewBag.redirect_uri = redirect_uri;
ViewBag.response_type = response_type;
ViewBag.state = state; return View();
}

Index 视图:

<!DOCTYPE html>

<html>
<head>
<meta name="viewport" content="width=device-width" />
<title>Index</title>
</head>
<body>
<div>
<form id="form1" method="post" action="@Url.Action("Index2")">
<input type="text" name="clientId" value="@ViewBag.clientId" />
<br />
<input type="text" name="redirect_uri" value="@ViewBag.redirect_uri" />
<br />
<input type="text" name="response_type" value="@ViewBag.response_type" />
<br />
<input type="text" name="state" value="@ViewBag.state" />
<br />
<input type="submit" value="提交" />
</form> </div>
</body>
</html>

我们看一下测试用例的代码,其中 OAuth_AuthorizationCode_Test 测试用例对应着授权码模式,也就是说,我们需要看下它原本是怎么请求的,依葫芦画瓢,简单过一篇代码之后,发现 GetAuthorizationCode() 这个方法很可疑,跟进去,可以看到,它确实是发起了请求授权服务。我们简单分析一下,它利用 HttpClient 发起了一个 /authorize 请求,传递了grant_type,response_type,client_id,redirect_uri 这几个参数,也就是说,我们也可以模仿他这样操作,那么在  Index2 方法中这样写:

        public async Task<ActionResult> Index2()
{
string clientId = Request.Form["clientId"] + "";
string redirect_uri = Request.Form["redirect_uri"] + "";
string state = Request.Form["state"] + "";
string urlEncode = HttpUtility.UrlEncode(redirect_uri);
return Redirect("~/authorize?grant_type=authorization_code&response_type=code&client_id="+clientId+"&redirect_uri="+urlEncode);
}

因为我们是在同一个服务器,所以我们不再用 HttpClient 去发起请求了,直接上跳转。

直接更新上服务器,并上 天猫的控制台 填写 信息,

账户授权链接填入: https://YourWebSite/Home/Index

ClientID填入:xishuai

Client Secret 填入:123

Access Token URL 填入:https://YourWebSite/NoAddress (随便写一个,只是为了先测试第一步是不是正常的先)

厂商登出 URL 留空。

更新上服务器,进入 天猫控制台 中的测试验证,然后点击 账户授权 ,会发现报错!!!提示缺少参数。那我们检查一下代码吧,看下是哪里出的问题,缺少参数,也就是说,我们返回给天猫的URL,参数写少了,或者写错了。我们检查一下。

打开 OpenAuthorizationServerProvider 找到 AuthorizeEndpoint 方法(它是对应着 authorize 请求的处理),可以看到里面有个 redirectUri 的数据,用日志的方式输出来看下(其实不用看了,刚才我们在测试的时候,你直接把 <input type="text" name="redirect_uri" /> 的值复制出来就知道问题出在哪了),redirectUri 在此处的值为:https://open.bot.tmall.com/oauth/callback?skillId=11111111&token=XXXXXXXXXX ,看到这儿,我想结合代码就知道问题出在哪了吧,源码如下:

context.Response.Redirect(redirectUri + "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token));

修改如下:

        /// <summary>
/// 生成 authorization_code(authorization code 授权方式)、生成 access_token (implicit 授权模式)
/// </summary>
public override async Task AuthorizeEndpoint(OAuthAuthorizeEndpointContext context)
{
if (context.AuthorizeRequest.IsImplicitGrantType)
{
//implicit 授权方式
var identity = new ClaimsIdentity("Bearer");
context.OwinContext.Authentication.SignIn(identity);
context.RequestCompleted();
}
else if (context.AuthorizeRequest.IsAuthorizationCodeGrantType)
{
//authorization code 授权方式
var redirectUri = context.Request.Query["redirect_uri"];
var clientId = context.Request.Query["client_id"]; var identity = new ClaimsIdentity(new GenericIdentity(
clientId, OAuthDefaults.AuthenticationType)); var authorizeCodeContext = new AuthenticationTokenCreateContext(
context.OwinContext,
context.Options.AuthorizationCodeFormat,
new AuthenticationTicket(
identity,
new AuthenticationProperties(new Dictionary<string, string>
{
{"client_id", clientId},
{"redirect_uri", redirectUri}
})
{
IssuedUtc = DateTimeOffset.UtcNow,
ExpiresUtc = DateTimeOffset.UtcNow.Add(context.Options.AuthorizationCodeExpireTimeSpan)
})); await context.Options.AuthorizationCodeProvider.CreateAsync(authorizeCodeContext); var ResUrl = redirectUri;
if (redirectUri.Contains("?"))
{
ResUrl += "&code=" + Uri.EscapeDataString(authorizeCodeContext.Token);
}
else
{
ResUrl += "?code=" + Uri.EscapeDataString(authorizeCodeContext.Token);
} //writeLog("ResUrl", ResUrl,"TestToken");
// writeLog("Token", authorizeCodeContext.Token, "TestToken"); context.Response.Redirect(ResUrl);
context.RequestCompleted();
}
}

这样,就能正常的处理跳转的参数了。更新上服务器,测试,通过!没有提示缺少参数了。但现在提示取不到 access_token 了。也就是说我们到了处理 获取 access_token 这一步了。

获取 access_token

我们回头继续看下那个测试用例(OAuth_AuthorizationCode_Test),

        [Fact]
public async Task OAuth_AuthorizationCode_Test()
{
var authorizationCode = GetAuthorizationCode().Result; //获取 authorization_code var tokenResponse = GetToken("authorization_code", null, null, null, authorizationCode).Result; //根据 authorization_code 获取 access_token
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponse.AccessToken); var response = await _httpClient.GetAsync($"/api/values");
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(response.StatusCode);
Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);
}
Console.WriteLine(await response.Content.ReadAsStringAsync());
Assert.Equal(HttpStatusCode.OK, response.StatusCode); Thread.Sleep(10000); var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; //根据 refresh_token 获取 access_token
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken);
var responseTwo = await _httpClient.GetAsync($"/api/values");
Assert.Equal(HttpStatusCode.OK, responseTwo.StatusCode);
}

在获取了 authorization_code 之后,调用了 GetToken 方法,查看 GetToken 方法

        private static async Task<TokenResponse> GetToken(string grantType, string refreshToken = null, string userName = null, string password = null, string authorizationCode = null)
{//"authorization_code", null, null, null, authorizationCode
var clientId = "xishuai";
var clientSecret = "123";
var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", grantType); if (!string.IsNullOrEmpty(userName) && !string.IsNullOrEmpty(password))
{
parameters.Add("username", userName);
parameters.Add("password", password);
}
if (!string.IsNullOrEmpty(authorizationCode))
{
parameters.Add("code", authorizationCode);
parameters.Add("redirect_uri", "http://localhost:8333/api/authorization_code"); //和获取 authorization_code 的 redirect_uri 必须一致,不然会报错
}
if (!string.IsNullOrEmpty(refreshToken))
{
parameters.Add("refresh_token", refreshToken);
} _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(clientId + ":" + clientSecret))); var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
var responseValue = await response.Content.ReadAsStringAsync();
if (response.StatusCode != HttpStatusCode.OK)
{
Console.WriteLine(response.StatusCode);
Console.WriteLine((await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage);
return null;
}
return await response.Content.ReadAsAsync<TokenResponse>();
}

构建了一个 HttpClient ,组好参数 post 给 /token  .那我们还是依葫芦画瓢,照抄过来用看下。在 Home 控制器下,定义一个 TestToken 方法,再看下官方文档

(B) 通过code换取合作方访问令牌
例:
https://XXXXX/token?grant_type=authorization_code&client_id=XXXXX&client_secret=XXXXXX&code=XXXXXXXX&redirect_uri=https%3A%2F%2Fopen.bot.tmall.com%2Foauth%2Fcallback
请求方法: POST

参数说明:
client_id: 在合厂商平台上注册的应用Id
grant_type: 授权类型 authorization_code
client_secret:在厂商平台上注册应用的secret
code: 授权登陆后回调AliGenie的地址返回的code
redirect_uri: AliGenie回调地址

也就是说,它会发这些参数给我们,那么我们就用 Request.Form 一一接收,然后模仿着GetToken 的方法写。

TestToken 方法如下:

        public async Task<ActionResult> TestToken()
{
string grant_type = Request.Form["grant_type"] + "";
string client_id = Request.Form["client_id"] + "";
string client_secret = Request.Form["client_secret"] + "";
string code = Request.Form["code"] + "";
string redirect_uri = Request.Form["redirect_uri"] + ""; var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", grant_type);
parameters.Add("code", code);
parameters.Add("redirect_uri", redirect_uri); ServicePointManager.SecurityProtocol = SecurityProtocolType.Tls12 | SecurityProtocolType.Tls11 | SecurityProtocolType.Tls; HttpClient _httpClient = new HttpClient();
_httpClient.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["serverUrl"]);//从配置文件中读取,服务器的域名
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(client_id + ":" + client_secret))); var response = await _httpClient.PostAsync("/token", new FormUrlEncodedContent(parameters));
var responseValue = await response.Content.ReadAsStringAsync();        writeLog("responseValue",resp,"responseValue"); if (response.StatusCode != HttpStatusCode.OK)
{
return Json(new TaskResponseError
{
error = response.StatusCode + "",
error_description = (await response.Content.ReadAsAsync<HttpError>()).ExceptionMessage
});
}
var TokenResponse = await response.Content.ReadAsAsync<TokenResponseRight>(); TokenResponse.expires_in = Convert.ToInt64(System.Configuration.ConfigurationManager.AppSettings["tokenExpiresTime"]);//过期时间也要返回
writeLog("TokenResponse:", JsonConvert.SerializeObject(TokenResponse) + "", "TestToken");
return Content(JsonConvert.SerializeObject(TokenResponse), "application/json");//强行指定类型,官方文档特别提示要用 application/json ,之前试过用return Json(xxx); 返回 之后提示参数错误之类的。
}

OK,更新上服务器,再上 天猫控制台 ,把 Access Token URL 修改一下,修改为:https://YourWebSite/Home/TestToken ,测试跑起来。测试应该是通过的,如果还有问题可以问一下我,2018年6月1日11:47:55 昨天到现在 真机测试的功能 AliGenie 还没修复好,所以我也不好截图给你们看,回头再来补充吧。

refalsh Token 的功能

查看 官方文档

(C) 通过refresh_token刷新access_token(该功能已上线,请确保厂商自己的刷新功能是完善的)
例:
https://XXXXX/token?grant_type=refresh_token&client_id=XXXXX&client_secret=XXXXXX&refresh_token=XXXXXX

请求方法: POST

 
参数说明:
  grant_type:更新access_token的授权方式为refresh_token
  client_id: 在厂商平台上注册的应用Id
  client_secret:在厂商平台上注册应用的secret
  refresh_token: 上一次授权获取的refresh_token
 
注:示例中的链接API( https://XXXXX/token) 为开放平台中的Access Token Url 字段。

得知,刷新 token 的功能是和我们的在 控制台中填写的 Access Token Url 是同一个入口,也就是说,我们需要对 TestToken 方法进行改造,让它也可以处理 刷新 token 的操作。

再查看那个测试用例(OAuth_AuthorizationCode_Test),可以看到以下代码

 Assert.Equal(HttpStatusCode.OK, response.StatusCode);

            Thread.Sleep(10000);

            var tokenResponseTwo = GetToken("refresh_token", tokenResponse.RefreshToken).Result; //根据 refresh_token 获取 access_token
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", tokenResponseTwo.AccessToken);

测试代码中休眠了10秒之后,去刷新 token 的操作,GetToken 方法我们之前已经看过了,知道它是做什么操作的,那么我们就直接在 TestToken 里面改造一下:

        public async Task<ActionResult> TestToken()
{
string grant_type = Request.Form["grant_type"] + "";
string client_id = Request.Form["client_id"] + "";
string client_secret = Request.Form["client_secret"] + "";
string code = Request.Form["code"] + "";
string redirect_uri = Request.Form["redirect_uri"] + "";
string refreshToken = Request.Form["refresh_token"] + "";//取refreshToken 参数 之前这儿写错了,如有人使用此段代码,麻烦改正一下
//https://XXXXX/token?grant_type=refresh_token&client_id=XXXXX&client_secret=XXXXXX&refresh_token=XXXXXX var parameters = new Dictionary<string, string>();
parameters.Add("grant_type", grant_type); parameters.Add("code", code); parameters.Add("redirect_uri", redirect_uri); if (!string.IsNullOrEmpty(refreshToken))
{//加入判断,当有此字段时,则为刷新 token 的操作
parameters.Add("refresh_token", refreshToken);
} System.Net.ServicePointManager.SecurityProtocol = System.Net.SecurityProtocolType.Tls12 | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls; HttpClient _httpClient = new HttpClient(); _httpClient.BaseAddress = new Uri(System.Configuration.ConfigurationManager.AppSettings["serverUrl"]);
_httpClient.DefaultRequestHeaders.Authorization = new System.Net.Http.Headers.AuthenticationHeaderValue(
"Basic",
Convert.ToBase64String(Encoding.ASCII.GetBytes(client_id + ":" + client_secret))); var response = await _httpClient.PostAsync("/" + System.Configuration.ConfigurationManager.AppSettings["serverExtUrlParam"] + "/token", new FormUrlEncodedContent(parameters));
var responseValue = await response.Content.ReadAsStringAsync(); if (response.StatusCode != System.Net.HttpStatusCode.OK)
{
return Json(new TaskResponseError
{
error = response.StatusCode + "",
error_description = (await response.Content.ReadAsAsync<System.Web.Http.HttpError>()).ExceptionMessage
});
}
var TokenResponse = await response.Content.ReadAsAsync<TokenResponseRight>(); TokenResponse.expires_in = Convert.ToInt64(System.Configuration.ConfigurationManager.AppSettings["tokenExpiresTime"]); if (grant_type == "authorization_code")
{
string hostId = CloudApi.Providers.OpenAuthorizationCodeProvider.GetHostIDByAuthenticationCode(code);
if (!string.IsNullOrEmpty(hostId))
{
CloudApi.Providers.OpenAuthorizationCodeProvider.SetHostIDAndTicket(hostId, TokenResponse.AccessToken);
}
} ToolHelper.FuntionHelper.writeLog("TokenResponse:", JsonConvert.SerializeObject(TokenResponse) + "", "TestToken");
return Content(JsonConvert.SerializeObject(TokenResponse), "application/json");
}

OAuth 的授权平台,就这样简单搭建好了,剩下的就是开发者自己去完善实际的功能,比如授权用的 ClientID,Client_Secrete 应该从数据库里取、还是直接从配置文件中读取出来。授权成功了,你怎么判断是谁,

现在时间是:2018年6月1日14:36:53 ,AliGenie 的真机测试还是用不了,所以也是没办法给你们展示实际的效果。这个也是蛋蛋的忧伤。

Q & A:

Q1.我这个流程走是没问题,但我不知道实际操作的时候应该取哪个账户的设备数据啊?

A1:这个问题在你授权登录那 H5 页面,自行解决,加个账户密码,让用户自己登录,具体逻辑自己处理下就可以了,其他要修改的地方,可能是在 OpenAuthorizationCodeProvider 这个类里面,你要加一个静态的 ConcurrentDictionary ,用来存放是哪个用户授权的,对应的 token 是什么。

代码如下:

    public class OpenAuthorizationCodeProvider : AuthenticationTokenProvider
{
private readonly ConcurrentDictionary<string, string> _authenticationCodes = new ConcurrentDictionary<string, string>(StringComparer.Ordinal); //主机ID 对应着你的用户名
public static ConcurrentDictionary<string, string> HostIdAndAuthenticationCodeCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal);
//我这儿记录了主机ID和授权码 public static ConcurrentDictionary<string, string> HostIdAndTicketCache = new ConcurrentDictionary<string, string>(StringComparer.Ordinal); //这儿记录了主机ID和 token public static string GetHostIDByAuthenticationCode(string authenticationCode)
{
var tempItem = HostIdAndAuthenticationCodeCache.FirstOrDefault(c => c.Value.Equals(authenticationCode, StringComparison.OrdinalIgnoreCase));
if (tempItem.Key != null)
{
return tempItem.Key;
}
else
{
return "";
}
} public static void SetHostIDAndTicket(string hostId, string ticket)
{
HostIdAndTicketCache.AddOrUpdate(hostId, ticket, (k, ov) => ov = ticket);
} public static string GetHostIdByTicket(string ticket)
{
var tempItem = HostIdAndTicketCache.FirstOrDefault(c => c.Value.Equals(ticket, StringComparison.OrdinalIgnoreCase));
if (tempItem.Key != null)
{
return tempItem.Key;
}
else
{
return "";
}
} /// <summary>
/// 生成 authorization_code
/// </summary>
public override void Create(AuthenticationTokenCreateContext context)
{
context.SetToken(Guid.NewGuid().ToString("n") + Guid.NewGuid().ToString("n")); string TempTicket = context.SerializeTicket();
_authenticationCodes.AddOrUpdate(context.Token, TempTicket, (k, ov) => ov = TempTicket);
//_authenticationCodes[context.Token] = context.SerializeTicket(); string hostId = context.Request.Query["hostId"]; HostIdAndAuthenticationCodeCache.AddOrUpdate(hostId, context.Token, (k, ov) => ov = context.Token);//将 主机ID 保存起来
} /// <summary>
/// 由 authorization_code 解析成 access_token
/// </summary>
public override void Receive(AuthenticationTokenReceiveContext context)
{
string value;
if (_authenticationCodes.TryRemove(context.Token, out value))
{
context.DeserializeTicket(value);
}
} }

Q2:为什么我明明返回给天猫精灵 authorize code 了,它第二次来请求 token 的时候,就是死活没找到?

A2: 检查一下,第一步返回时的参数 redirect_url 是什么,他第二次请求过来的参数 return_url 是什么。(我就因为把state 在第一步的时候也返回回去了,导致第二步死活取不到 token)

2018-7-16 14:47:12 补充:

实际用的过程中,你需要保存用户ID、refresh_token 这些数据,请自行处理,在我实际代码中,用的是 Redis。用来保存了refresh_token和用户标识。

上面代码中,有一段 用到的 refresh_token 接收的时候写错了,写成了 refreshtoken。导致刷新 token 功能有问题。请之前用的人注意一下。还是太相信自己的代码,导致出bug ,测试的时候就看着自己写的代码测试.

天猫精灵对接2(OAuth 搭建)的更多相关文章

  1. 天猫精灵对接1:outh对接

    公司的智能家居产品需要接入语音控制,目前在对接阿里语音的天猫精灵 对接天猫精灵的第一步是完成outh鉴权 https://doc-bot.tmall.com/docs/doc.htm?spm=0.76 ...

  2. (零 ) 天猫精灵接入Home Assistant-总说明

    天猫精灵设备管理 https://bbs.hassbian.com/tmall 自己的hass访问地址 http://[自己的IP或域名]:8123/states 自己的MQTT服务器访问 http: ...

  3. (一) 天猫精灵接入Home Assistant- hass对接天猫精灵

    1如何利用论坛的认证服务器对接天猫精灵 说起天猫精灵的接入,最早是由c1pher(25989406)大神通过开发自定义技能接入,后面qebabe大神进行了改进,可以直接通过HASS API读取hass ...

  4. 对接天猫精灵X1 (https 的申请)

    1 起因 公司是做智能家居的,最近公司要求对接天猫精灵的智能家居功能,所以就来对接天猫精灵X1 了. 新产品,大家都懂的,坑是有不少的,正常事. 1 首先,语言是 c#,不要和我讲 php 是世界最好 ...

  5. (二 -3-1) 天猫精灵接入Home Assistant-自动发现Mqtt设备--灯系列 实战

    #本片教程介绍了具体如何实现天猫精灵控制一个灯. 前提: HASS平台 你已经搭建一个可以在公网IP访问到的HASS平台--- 我用的是租了阿里云服务器,买了个域名,ubuntu1604系统 你已经搭 ...

  6. 【阿里云IoT+YF3300】16.云端一体化,天猫精灵操控YF3300

    “你好天猫精灵”,“主人有什么吩咐”,“打开灯”,“好的,灯已打开”.对于这样的对话应该大多数人都很熟悉,这就是智能家居的缩影.对于现在市面上层出不穷的智能家居系统,功能越来越繁杂,可是因为开发难度高 ...

  7. Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端

    Microsoft.Owin.Security.OAuth搭建OAuth2.0授权服务端 目录 前言 OAuth2.0简介 授权模式 (SimpleSSO示例) 使用Microsoft.Owin.Se ...

  8. 天猫精灵X1智能音箱使用感想

    11.22音箱到手,等了刚好一个月. 主要是测评语音交互功能. 测试条件:正宗普通话. 1)问天气.温度:表现良好.2)找手机功能:试了多次,每次都说手机号码格式不对3)小孩听故事:正常.但是开头会有 ...

  9. 接入天猫精灵auth2授权页面https发送ajax请求

    已存在一个应用A,采用的是http交互, 在接入天猫精灵时,要求请求类型是https,所以在应用服务前加了个nginx转发https请求.在绑定授权页面,会发送ajax请求验证用户名和密码,采用htt ...

随机推荐

  1. 6.Exchanger-交换机

  2. use-default-filters的用法

    <context:component-scan base-package="com.atguigu.atcrowdfunding.*" > <context:ex ...

  3. 需要完成PAT作业和微博作业的具体方法

    http://www.cnblogs.com/c-programing-language/p/6703508.html

  4. 嘿!Mybatis

    简介 什么是Mybatis MyBatis 是一款优秀的持久层框架 它支持自定义 SQL.存储过程以及高级映射. MyBatis 免除了几乎所有的 JDBC 代码以及设置参数和获取结果集的工作. My ...

  5. Android Widget开发过程中的一些问题汇总

    一.基本实现要点 布局文件 配置文件 控制文件 AndroidManifest.xml

  6. 关于Xilinx PCIE DMA的问答

    关于Xilinx PCIE DMA的问答 很久没上博客园了,但由于之前在博客园写了几篇关于PCIE DMA的文章,很多同学给我发消息询问相关知识点,之前有空的时候都是语音一小时跟人细讲,最近由于工作繁 ...

  7. Centos-bzip2压缩文件-bzip2 bunzip2

    bzip2 buzip2 对文件进行压缩与解压缩,类似 gzip gunzip命令,只能压缩文件,对目录则压缩目录下文件,生成以 .bz2为扩展名的文件 相关选项 -d 解压 -v 压缩或解压显示详细 ...

  8. Android Handler MessageQueue Looper 消息机制原理

    提到Android里的消息机制,便会提到Message.Handler.Looper.MessageQueue这四个类,我先简单介绍以下这4个类 之间的爱恨情仇. Message 消息的封装类,里边存 ...

  9. Python练习题 015:一颗自由落地的球

    [Python练习题 015] 一球从100米高度自由落下,每次落地后反跳回原高度的一半,再落下.求它在第10次落地时,共经过多少米?第10次反弹多高? ----------------------- ...

  10. spring-boot-route(四)全局异常处理

    在开发中,我们经常会使用try/catch块来捕获异常进行处理,如果有些代码中忘记捕获异常或者不可见的一些异常出现,就会响应给前端一些不友好的提示,这时候我们可以使用全局异常处理.这样就不用在代码中写 ...