根据 接入方式及流程 中的说明,可知,搭建过程中,我们需要自己整一个 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. 【深入理解Linux内核架构】6.6 资源分配

    一段摘自<Linux设备驱动程序>的话: 每种外设都通过读写寄存器进行控制.大部分外设都有多个寄存器,不管是内存地址空间还是I/O地址空间,这些寄存器的访问地址都是连续的. 在硬件层,内存 ...

  2. php中用面向对象的思想编写mysql数据库操作类

    最近刚入门完mysql,正好学了一阵子php就想着如何把mysql的表信息用php打印页面上.现在就把代码贴出来,以便小伙伴们参考. 先是建立mysql连接: /*建立连接*/ class datab ...

  3. C语言的运算符

    操作数 :参与运算的变量 表达式:有意义有结果的语句 运算符的分类:1.按照功能运算符划分:算术运算符 关系运算符 逻辑运算符 位运算 2.按照参与运算的操作数个数划分:单目运算(只有一个操作数) 双 ...

  4. 使用vue-cli(vue脚手架)快速搭建项目-2

    接上一篇文章,这篇文章对如何使用IDEA打开并运行项目做教程 1.将在窗口模式启动的Vue关闭 只需要按住Ctrl+C,输入Y就可以了 2.打开idea 3.复制你项目所在地址,然后点击OK 4.下面 ...

  5. 【原创】K8S使用ceph-csi持久化存储之RBD

    一.集群和组件版本 K8S集群:1.17.3+Ceph集群:Nautilus(stables)Ceph-CSI:release-v3.1snapshotter-controller:release-2 ...

  6. burp suite 之 Scanner(漏洞扫描)

    Scanner选项:是一个进行自动发现 web 应用程序的安全漏洞的工具. 将抓取的包 通过选项卡发送至 Scanner下的Scan queue 首先来介绍 Scanner 下的 lssue acti ...

  7. spark textfile rdd 日记

    批量处理模板方法, 核心处理方法为内部方法 def batchProces(sc: SparkContext, locationFlag: Int, minid: Int, maxid: Int, n ...

  8. npm包的发布和管理

    npm包管理 npm其实是Node.js的包管理工具(node package manager). 为啥我们需要一个包管理工具呢?因为我们在Node.js上开发时,会用到很多别人写的JavaScrip ...

  9. 0921 LCA练习

    1.poj 1330 数据结构中的树,在计算机科学中是非常重要的,例如我们来看看下面这棵树: 在图中我们对每个节点都有编号了. 8号节点是这棵树的根.我们定义,一个子节点向它的根节点的路径上,任意一个 ...

  10. vue+elementUI实现 分页表格的单选或者多选、及禁止部分选择

    一.vue+elementUI实现 分页表格前的多选 多选效果图: 代码如下: <el-table ref="multipleTable" :data="listD ...