1、天貓智能家居對接
1-1、介紹
術語:
- 開發者:指非天貓精靈官方,或對接天貓精靈平台數據的程序員或服務提供者
- 官方:指天貓精靈,或者天貓生活物聯網平台
- 用戶:統一代指購買了開發者設備,或者使用天貓精靈的用戶
1-2、天貓精靈智能家居的應用
天貓精靈嘛!阿里精靈開放平台,iot開發者平台,阿里生活物聯網平台,還有iot開放平台。這些亂七八糟的名字都是直接或間接描述天貓精靈智能家居的。下面的文字或者圖片中如果出現這些名字,你們就統一當成是天貓精靈設備對接的那個平台就行了。

官方提供了一個供給開發者對接設備的一個接口。用戶可以通過天貓精靈app里下載開發者創建的技能,綁定用戶在開發者平台擁有的設備。讓用戶可以使用自己的天貓精靈訪問我們的設備。
但是在此之前,我覺得有必要讓開發者首先知道,這個應用是怎么使用的。首先假設我們的技能發布上線了,那么我們需要做的是找到我們的技能,並且注冊,且能夠執行:
-
打開天貓精靈app,進入首頁面,也就是精靈家這里,然后點擊技能廣場,如果找不到,可以去”查看更多“按鈕里面去尋找。進入技能廣場,點擊搜索按鈕進入搜索界面查找我們發布的技能

-
進入搜索界面,輸入技能名稱並搜索。就可以搜索我們發布的技能了

- 進入技能的信息界面,點解賬戶綁定,就可以進入我們的項目的范圍內,可以跳轉到前端,讓用戶輸入賬號和密碼,將天貓精靈設備與我們的賬號綁定。到時候就可以讓天貓精靈訪問我們的項目了

1-3、天貓精靈的技術開發准備
天貓開發前配置
和天貓精靈對接的流程我打算使用一個demo演示,但是這個demo不會太復雜,基本是只是使用到springboot,maven。至於官方提到的OAuth2.0。嗯,實際的項目中出於安全考慮,建議用上。但是這里只是一個demo,所以不考慮,只讓讀者了解大致的脈絡就好。
首先需要准備的是帶https的域名或者是公網ip(這個ssl可以自己生成,然后掛上去,效果是一樣的)或者購買內網穿透工具(花生殼,natapp),讓天貓精靈的服務器能工訪問到我們的服務器就可以了。
開發前首先要添加一些配置到阿里官方,打開阿里精靈開放平台,這里一般都會要求進行登錄,也有可能要你進行開發者信息的配置。這里不講跳過。登錄過后就會進入技能開發界面,如圖

確認網址是否一致,是否進入技能開發頁面,如果成功了,就點擊添加技能,這里的話,就很簡單輸入技能名稱,點擊下一步就好。

之后就會進入如圖所示界面,在網關url中輸入之后的業務url。這里建議使用https協議的ip或域名(這里可以不使用https協議),點擊保存。華東滾輪到下面。

這里可以看見有五個輸入框,前面兩個輸入框,是給token令牌登錄檢測使用的,用於區分不同用戶訪問服務器而使用的。只不過這里的token指的是天貓精靈服務器向我們訪問,而不是天貓精靈的使用者向我們訪問。
- Authorization URL:這里我打算讓他跳轉到html頁面去讓用戶自己輸入登錄信息,進行驗證。
- Access Token URL:這里是用戶更新token信息使用的路徑,也是用戶綁定賬戶時需要用到的
- Logout URL:當用戶不想使用我們的技能時,就需要到在技能廣場中取消掉技能,而取消技能就會調用這個注銷功能

確認信息沒有錯誤之后,就可以點擊保存了。(之后可以回到這個頁面隨意修改),項目還沒部署,暫時用不上那個“點擊測試“按鈕。
流程
- 首先是用戶發起請求,要求將天貓精靈賬號和開發者賬號綁定
- 天貓將跳轉到實現准備好的Authorization URL路徑並發送數據
- 開發者保留這些數據,並重定向到前端頁面
- 用戶填寫開發者提供的賬號和密碼,點擊提交
- 開發者驗證信息無誤,生成code字符串,向之前保留的數據中提取出返回路徑,將信息發送給天貓。
- 天貓拿到code,發起新的Access Token URL請求,依照code拿到開發者生成的token令牌,之后這個天貓app賬戶就和開發者網站的賬號綁定了。
1-4、項目創建
創建springboot部分的代碼
創建一個springboot項目,導入maven依賴,maven主要依賴如下。這里主要是作為天貓精靈對接演示所需要使用到的依賴,簡單的web就夠了。
<dependencys>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba</groupId>
<artifactId>fastjson</artifactId>
<version>1.2.74</version>
</dependency>
</dependencys>
<build>
<plugins>
<plugin>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-maven-plugin</artifactId>
</plugin>
</plugins>
</build>
創建完成之后,這里是我的項目結構(完成品),可以參考一下。默認創建完成之后,resources里的應該是application.properties而不是application.yml,我比較習慣用yml,就改了。

項目創建完成后,在resources中的static里創建login.html文件,這個文件就負責登錄功能就可以了,可以簡單的美化一下。
在這里的form表單中,action路徑不能出現域名或者ip,否則提交時session會出現不一致的情況。則會導致session架構的項目無法接收數據。
<!---->
<form action="/tianmao/home/login" method="post">
<table style="border: 1px solid;border-radius: 10px;margin-top: 100px">
<tr style="height: 50px">
<td style="font-size: 20px">用戶名</td>
<td><input style="height: 25px;width: 220px" type="text" name="username"/></td>
</tr>
<tr>
<td style="font-size: 20px">密碼</td>
<td><input style="height: 25px;width: 220px" type="password" name="password"/></td>
</tr>
<tr>
<td colspan="2"><input style="padding: 10px 18px;float: right;margin-right: 45px" type="submit" value="登錄"></td>
</tr>
</table>
</form>
回到java代碼部分,創建一個controller包,里面創建一個帶Controller的注解的類。里面代碼大致如下:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.util.Arrays;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Controller
@RequestMapping("/tianmao/home")
public class TianMaoLoginController {
}
創建一個Authorization里配置相同的RequestMapping注解,我這里是類上面也創建了一個路徑注解。所以在方法這里,就剩掉前綴了。不影響代碼運行。這里的validation方法,只是簡單的參數檢測,並把參數取出來。
/**
* @return 跳轉到天貓精靈的登錄頁面
*/
@RequestMapping("/toLogin")
public String toLogin(HttpServletRequest request) {
System.out.println("TianMaoSkilLogin.toLogin");
//獲取數據,驗證數據信息
Map<String, Object> data = validation(request, null);
//將數據保存到session中,方便下一次調用使用
request.getSession().setAttribute("oauth2", data);
//進入到天貓精靈的登錄頁面的時候,使用的就是之前的Session,提交也是,所以可以煲出你這些數據到提交之后使用
return "/login.html";
//如果想跳過驗證,可以將將return語句替換為下面的語句,可以直接跳過登錄驗證界面
// return "redirect:" + data.get("redirect_uri") + "&code=" + 123456 + "&state=" + data.get("state");
}
@RequestMapping("/login")
public String login(HttpServletRequest request) throws Exception {
//for循環
Map<String, String[]> map = request.getParameterMap();
for (String s : map.keySet()) {
//這里是數組,建議采用Arrays的toString方法輸出字符串數組
System.out.println("key = " + s + " | value = " + Arrays.toString(map.get(s)));
}
return null;
}
創建新產品,測試java代碼跳轉
這一步測試需要在阿里平台創建一個產品,回到產品開發界面,點擊添加新產品。

如圖,選擇電子/電工類別選擇插座,然后點擊下一步

點擊下一步后,官方會讓你填寫一些參數,這些參數內容如下圖,之后就慢慢的介紹了

品牌:首先是這個品牌,別被下面的這一行小提示嚇到了,以為需要有品牌才能注冊,實際上嘛,你隨便選一個都可以。我這里選擇我們公司的一個商標進行注冊。

產品名稱:這里填寫中文英文都可以,不作要求
產品型號:這里應該是要做唯一標識使用。但是重復也是可以的
接入方式:你沒的選
所屬技能:選擇你在之前步驟所創建的技能就可以了

通訊協議:選擇wifi
數據格式:選擇標准,透傳需要改寫js文件。想了解的可以去查看文檔

之后點擊提交,就成功創建一個產品,接下來就是配置插座的功能,這里選擇在線狀態和開關功能,簡單測試提交就好。

如圖所示,點擊設備調試,跳轉到調試界面,到調試界面時,還會看見官方向我們的服務器發送了一條請求,這里是在查詢你的設備數據(我這里是會這樣)。


進入調試界面后會彈出以下界面,可以直接在當前窗口內調試,也可點擊新窗口打開打開新窗口打開,效果都是一樣的。



這里可以看到我們都把數據拿到手了,在第二次請求時。可以做做具體的業務,驗證用戶提交的數據信息。

當然,你還會在天貓調試網頁上看到你的項目的報錯信息,這是正常的,因為我沒有合理的返回數據信息,返回的是一個空值。

下面就要在代碼中驗證前端提交的賬號和密碼,這里就涉及到具體的數據庫層面。但是也可以簡單的測試一下,隨便的驗證一下前端信息,生成一個code,之后官方會用這個code剛跟我們交換代碼。需要臨時存儲一下這個code數據。這里面的data是HashMap類,里面是之前保存在session中的代碼數據。取出來就可以了
return "redirect:" + data.get("redirect_uri") + "&code=" + code + "&state=" + data.get("state");
當你的返回信息正確無誤,官方就會發起下一次請求,這個請求和之前請求的sessionId是不一致的(這是因為token令牌)。,訪問的路徑也是之前步驟中的Access Token URL配置的路徑。還是先看看有哪些訪問信息。
這里能看到,授權類型,客戶端id,客戶端秘鑰,以及之前提到的code還有最后的跳轉連接。

在這之后就要根據code信息,生成token就行了,當然拿到code后生成,也是一樣的。返回格式采用的是json格式。
@RequestMapping("/token")
@ResponseBody
public Map<String, Object> token(HttpServletRequest request) throws Exception {
Map<String, Object> data = validation(request, null);
System.out.println(data);
Map<String, Object> map = new HashMap<>();
String code = (String) data.get("code");
System.out.println(bind);
UserService.User user = (UserService.User) bind.get(code);
//自己定義的token,真實環境還是需要進行加密
String token = user.getUsername() + "_" + user.getPassword() + '_' + System.currentTimeMillis();
map.put("access_token", token);
map.put("refresh_token", token);
map.put("expires_in", 17600000L);
//返回結果
return map;
}
