天猫精灵对接2(OAuth 搭建)

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

第一步

认真研究一次文档:

(1)AliGenie在开发商开放平台或者其他第三方平台注册一个应用,获取到相应的Client idClient 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_codevar 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, authorizationCodevar 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=XXXXXXvar 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和 tokenpublic 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);}}}
View Code

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 ,测试的时候就看着自己写的代码测试.

 

转载于:https://www.cnblogs.com/Frank-Jan/p/9118105.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/49379.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

天猫精灵对接智能设备

why to do&#xff1a;   我之前一直很喜欢智能家居&#xff0c;可惜的是现在市场上成品的智能家居实在是太贵了&#xff0c;屌丝的码农是在背不起每月高额的房贷和装修费用的基础上&#xff0c;再买成品的智能设备&#xff08;像某米那样一个智能开关&#xff0c;竟然卖那么…

从零玩转系列之SpringBoot3-核心原理

一、简介 1.前置知识 ● Java17 ● Spring、SpringMVC、MyBatis ● Maven、IDEA 2.环境要求 环境&工具版本(or later)SpringBoot3.1.xIDEA2023.xJava17Maven3.5Tomcat10.0Servlet5.0GraalVM Community22.3Native Build Tools0.9.19 二、SpringBoot3-核心原理 1.事件和监听器…

SpringBoot3【⑤ 核心原理】

1. 事件和监听器 1. 生命周期监听 场景&#xff1a;监听应用的生命周期 1. 监听器-SpringApplicationRunListener 自定义SpringApplicationRunListener来监听事件&#xff1b; 1.1. 编写SpringApplicationRunListener 这个接口的实现类 1.2. 在 META-INF/spring.factories …

开发必备,开源 or 免费的 AI 编程助手

AI 大模型的火热&#xff0c;让开发圈近来如虎添翼&#xff0c;各种各样基于 AI 技术的开发者工具和新范式不断涌现&#xff0c;尤其是 Github 和 OpenAI 共同推出的 Copilot X &#xff0c;更是一骑绝尘。本文推荐一些开源 or 免费的 AI 编程工具&#xff0c;不妨试着用起来。…

超过5000人的2年研究表明,这一活动破坏你的身心健康

Tips 原文作者&#xff1a;Minda Zetlin 原文出处&#xff1a;A 2-Year Study of More Than 5,000 People Shows This 1 Activity Destroys Your Emotional and Physical Health 阅读时&#xff0c;把文中的 Fackbook 换成微信。 国外主要用 Facebook&#xff1b; 国内主要是微…

申请阿里云服务器并搭建公网可支持数据上传下载的HTTP服务器

1. 前言 拥有一台自己的云服务器可以做很多事情。阿里云服务器毫无疑问是国内最好的。 阿里云服务器可以用于各种互联网应用的搭建和运行&#xff0c;提供稳定、高性能的服务。 阿里云服务器的用途&#xff0c;包括但不限于以下几个方面&#xff1a; 网站托管&#xff1a;可以将…

谷歌眼镜秀出时尚风采:对面的女孩看过来

摘要&#xff1a;在近日举办的纽约时尚周上&#xff0c;让身材火辣的模特带上谷歌的眼镜&#xff0c;行走在T台之上。主打时尚牌&#xff0c;进一步加固谷歌眼镜在大众消费阶层的印象&#xff0c;尤其是女性消费者。谷歌眼镜创始人Sebastian Thrun指出&#xff1a;谷歌眼镜特别…

学生台灯什么牌子好对眼睛好?专业护眼灯的学生台灯分享

据报告统计&#xff0c;2022年我国儿童青少年总体近视率为52.7%&#xff0c;其中6岁儿童为14.3%&#xff0c;小学生为35.6%&#xff0c;初中生为71.1%&#xff0c;高中生为80.5%&#xff0c;这些数据让人不寒而栗&#xff01; 专家表示&#xff0c;导致儿童青少年近视的因素&am…

【UGP VR眼镜排行榜】2018VR眼镜眼镜哪个好?什么VR眼镜值得买?综合推荐十大热品

科技的发展&#xff0c;高科技产品层出不穷&#xff0c;VR眼镜的出现使人们足不出户也能享受到高品质的观影感受。VR(Virtual Reality&#xff09;即虚拟现实&#xff0c;简称VR.虚拟现实头戴显示器设备&#xff0c;简称VR头显VR眼镜.现在&#xff0c;VR眼镜已不是什么稀奇的东…

《谷歌眼镜》新书作者:眼镜需要成为AR的载体吗?

近10年前&#xff0c;谷歌推出了首款AR眼镜Google Glass&#xff0c;尽管这款产品并没有如预期般取得成功&#xff0c;但它为后续AR硬件技术的发展奠定了基础。我们知道&#xff0c;从微软HoloLens开始&#xff0c;AR头显/眼镜产品更侧重于B端应用&#xff0c;面向C端发售的很少…

Karl Guttag:现有Micro LED/LCoS+光波导AR眼镜对比解析

轻量化是未来AR眼镜的发展趋势&#xff0c;为了缩减尺寸&#xff0c;AR眼镜厂商尝试了多种方案&#xff0c;长期来看Micro LED光机在小型化上更有优势&#xff0c;但现阶段LCoS光机的图像表现更好。在CES 2023期间&#xff0c;DigiLens、Lumus、Vuzix、OPPO、Avegant也展出了不…

偏光太阳镜测试图片软件,[专题]真假偏光太阳镜简单、实用辨别方法!

偏光太阳镜主要是通过镜片的平衡排列的结晶体原理&#xff0c;只让与晶体平衡的光波通过&#xff0c;而向其它角度震动的光波会一律被阻挡的方法(如同百叶窗的原理)制作而成。 正是利用这种原理&#xff0c;偏光太阳镜便可以有效地排除和滤除光束中的偏振光&#xff0c;使光线能…

智能眼镜的两种显示方式

to管理员&#xff1a;哪一个是广告&#xff0c;全是广告&#xff01;难不成网友的链接都不能给了&#xff1f;&#xff01;你们的评判标识是什么&#xff1f; 就现有的技术而言&#xff0c;受限于通讯及周边模块、电源的限制&#xff0c;眼镜只适合于作为显示器使用。 眼镜显示…

谷歌眼镜

谷歌眼镜(Google Project Glass)是由谷歌公司于2012年4月发布的一款“拓展现实”眼镜&#xff0c;它具有和智能手机一样的功能&#xff0c;可以通过声音控制拍照&#xff0c;视频通话和辨明方向以及上网冲浪、处理文字信息和电子邮件等。 查看精彩图册 目录 产品简介 发布信息…

微信小程序开发制作 | 小程序开发者工具功能介绍

小程序开发者工具是微信官方提供的用于开发和调试小程序的工具。它支持 Windows 和 Mac 两种操作系统&#xff0c;并提供了许多实用的功能&#xff0c;使得小程序开发者能够快速地开发和调试小程序。 下面是小程序开发者工具的主要功能介绍&#xff1a; 1.编辑器&#xff1a;…

微信里的小程序怎么制作

自小程序普及以来&#xff0c;除了公司企业&#xff0c;很多的个体户商家都会想了解微信里的小程序怎么制作的&#xff0c;毕竟小程序能解决很多经营上的需求。那么就给大家讲解微信里的小程序怎么制作的流程&#xff0c;希望大家对此能有了解。 流程一、制作小程序前准备 我…

微信小程序开发之——制作表格

一 概述 表格样式一表格样式二 二 绘制过程 外层设置display:table&#xff0c;并设置border-collapse表格边框模型表头设置display:table-row&#xff0c;单元格设置为display:table-cell每一行单元格同表头设置 三 示例代码 3.1 table.wxml(布局文件) <view class&qu…

发明了万维网的他,如今却想亲手推翻它

本文转载自 差评 他有个计划&#xff0c;一个推翻现有互联网&#xff0c;重建数字世界的计划。 看到这句话时&#xff0c;相信很多差友和差评君的第一反应一个样&#xff1a;这谁也太狂了吧&#xff1f;几个菜啊&#xff0c;喝成这样&#xff1f; 毕竟&#xff0c;就算是马云…

计算机中文核心投递经历

中文核心投递录用经历 这篇文章主要记录了我在研究生期间从论文投递到录用的一个心酸过程&#xff0c;因为文章质量不是很高&#xff0c;所以投递过程中也是多次被拒稿。接下来介绍一下我的经历&#xff0c;为后来人提供一些经验。 这篇文章我是从2021年9月份开始着手写&#…

计算机专业留学动机信范文,出国留学,如何写好动机信(Motivation Letter)?

一篇好的动机信最重要的是简洁易懂,用最简洁的语言展示申请者最突出的优点。 浙大毕业后在美国(UIUC)和欧洲(KTH, CTH, EPFL, NTNU)留学,PhD。另外由于在之前的工作中也参与系里招生,帮老板评审申请材料,参与系里招生会议。经手的材料主要有以下几种:需要做论文的硕士,…