springboot封装 涂鸦物联网平台API鉴权请求的demo

  • 先看依赖
    
    org.springframework.bootspring-boot-starter-data-redis
    
    
    org.apache.httpcomponentshttpclient
    
    
    org.apache.httpcomponentshttpasyncclient
    
    
    org.bgee.log4jdbc-log4j2log4jdbc-log4j2-jdbc4.11.16
    

    封装的助手类中主要使用httpclient作为请求管理类;并且使用了redis作为缓存提供者,没有用redis的使用其他缓存也可以;
    看一下我的redis缓存实现类
     

    package com.abon.smart_family_lot.application.common.util;import org.springframework.beans.factory.annotation.Autowired;
    import org.springframework.data.redis.core.StringRedisTemplate;
    import org.springframework.stereotype.Component;import java.util.concurrent.TimeUnit;/*** desc: redis缓存助手类* author: zmq3821@163.com* date: 2021/12/2 15:09*/
    @Component
    public class RedisCacheUtil {private StringRedisTemplate stringRedisTemplate;@Autowiredpublic void setStringRedisTemplate(StringRedisTemplate stringRedisTemplate) {this.stringRedisTemplate = stringRedisTemplate;}// 设置缓存public void set(String key, String value) {stringRedisTemplate.opsForValue().set(key, value);}// 设置缓存public void set(String key, String value, long expire) {stringRedisTemplate.opsForValue().set(key, value, expire, TimeUnit.SECONDS);}// 获取缓存值public String get(String key) {return stringRedisTemplate.opsForValue().get(key);}// 设置有效期public boolean expire(String key, long expire) {return Boolean.TRUE.equals(stringRedisTemplate.expire(key, expire, TimeUnit.SECONDS));}// 清理缓存public void remove(String key) {if (hasKey(key)) {stringRedisTemplate.delete(key);}}// 缓存递增public Long increment(String key, long delta) {return stringRedisTemplate.opsForValue().increment(key, delta);}// 是否存在指定缓存public boolean hasKey(String key) {return Boolean.TRUE.equals(stringRedisTemplate.hasKey(key));}}
    

  • 先看一下我的目录结构,只是为了方便搞清楚代码存放的逻辑。红框里的就是主要使用到文件

 

  1. 先在resources创建tuya.yml
    #云端
    cloud:client-id: xxx #授权密钥client-secret: xxxxxxxxxx #授权密钥endpoint: https://openapi.tuyacn.com #接入地址-中国数据中心
  2. 在common/config中创建TuyaCloudConfig.java

    package com.abon.smart_family_lot.application.common.config;import lombok.Data;
    import org.springframework.boot.context.properties.ConfigurationProperties;
    import org.springframework.context.annotation.Configuration;
    import org.springframework.context.annotation.PropertySource;@Data
    @Configuration
    @PropertySource(value = {"classpath:tuya.yml"}, encoding = "utf-8", factory = YamlConfigFactory.class)
    @ConfigurationProperties(prefix = "cloud")
    public class TuyaCloudConfig {private String clientId;private String clientSecret;private String endpoint;}
    

    这里用到了 YamlConfigFactory,用于辅助加载yml文件

    package com.abon.smart_family_lot.application.common.config;import org.springframework.beans.factory.config.YamlPropertiesFactoryBean;
    import org.springframework.core.env.PropertiesPropertySource;
    import org.springframework.core.env.PropertySource;
    import org.springframework.core.io.support.DefaultPropertySourceFactory;
    import org.springframework.core.io.support.EncodedResource;
    import org.springframework.lang.Nullable;import java.io.IOException;
    import java.util.Properties;public class YamlConfigFactory extends DefaultPropertySourceFactory {@Overridepublic PropertySource createPropertySource(@Nullable String name, EncodedResource resource) throws IOException {String sourceName = name != null ? name : resource.getResource().getFilename();if (!resource.getResource().exists()) {assert sourceName != null;return new PropertiesPropertySource(sourceName, new Properties());} else {assert sourceName != null;if (sourceName.endsWith(".yml") || sourceName.endsWith(".yaml")) {Properties propertiesFromYaml = loadYml(resource);return new PropertiesPropertySource(sourceName, propertiesFromYaml);} else {return super.createPropertySource(name, resource);}}}private Properties loadYml(EncodedResource resource) throws IOException {YamlPropertiesFactoryBean factory = new YamlPropertiesFactoryBean();factory.setResources(resource.getResource());factory.afterPropertiesSet();return factory.getObject();}}
    
  3. 创建请求基类BaseRequest

    package com.abon.smart_family_lot.application.common.extend.request;/*** desc: 请求基类* author: zmq3821@163.com* date: 2021/12/6 9:58*/
    public class BaseRequest {private String error = "";private Object result = "";public void setError(String error) {this.error = error;}public String getError() {return error;}public void setResult(Object result) {this.result = result;}public Object getResult() {return result;}}
    
  4. 创建真正的请求封装类TuyaRequest.java

    package com.abon.smart_family_lot.application.common.extend.request.tuya;import com.abon.smart_family_lot.application.common.exception.TuyaCloudSDKException;
    import com.abon.smart_family_lot.application.common.extend.request.BaseRequest;
    import com.abon.smart_family_lot.application.common.util.HttpUtil;
    import com.alibaba.fastjson.JSONObject;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;import javax.annotation.Resource;
    import java.util.HashMap;
    import java.util.Map;/*** desc: 涂鸦平台请求封装* author: zmq3821@163.com* date: 2021/12/6 10:00*/
    @Slf4j
    @Component
    public class TuyaRequest extends BaseRequest {@Resourceprivate TuyaRequestHelper tuyaRequestHelper;// GET请求public boolean doGetRequest(String uri, Map customHeaders) {Object _result;try {String accessToken = tuyaRequestHelper.getAccesToken();String url = tuyaRequestHelper.getUrlStr(uri, null);Map newHeaders = tuyaRequestHelper.getHeader("GET", url, customHeaders, null, accessToken);log.info("doGetRequest => Url: {}, headers: {}", uri, "\n" + JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpGet(url, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new TuyaCloudSDKException(responseObject);}_result = responseObject.get("result");setResult(_result);return true;} catch (Exception e) {e.printStackTrace();setError(e.getMessage());return false;}}// POST请求public boolean doPostRequest(String uri, String body, Map customHeaders) {Object _result;try {if (customHeaders == null) {customHeaders = new HashMap<>();}customHeaders.put("Content-Type", "application/json");String accessToken = tuyaRequestHelper.getAccesToken();String url = tuyaRequestHelper.getUrlStr(uri, null);Map newHeaders = tuyaRequestHelper.getHeader("POST", url, customHeaders, body, accessToken);log.info("doPostRequest => Url: {}, \n body: {}, \n headers: {}", uri, body, JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpPost(url, body, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new TuyaCloudSDKException(responseObject);}_result = responseObject.get("result");setResult(_result);return true;} catch (Exception e) {e.printStackTrace();setError(e.getMessage());return false;}}// PUT请求public boolean doPutRequest(String uri, String body, Map customHeaders) {Object _result;try {if (customHeaders == null) {customHeaders = new HashMap<>();}customHeaders.put("Content-Type", "application/json");String accessToken = tuyaRequestHelper.getAccesToken();String url = tuyaRequestHelper.getUrlStr(uri, null);Map newHeaders = tuyaRequestHelper.getHeader("PUT", url, customHeaders, body, accessToken);log.info("doPutRequest => Url: {}, \n body: {}, \n headers: {}", uri, body, JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpPut(url, body, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new TuyaCloudSDKException(responseObject);}_result = responseObject.get("result");setResult(_result);return true;} catch (Exception e) {e.printStackTrace();setError(e.getMessage());return false;}}// DELETE请求public boolean doDeleteRequest(String uri, Map customHeaders) {Object _result;try {String accessToken = tuyaRequestHelper.getAccesToken();String url = tuyaRequestHelper.getUrlStr(uri, null);Map newHeaders = tuyaRequestHelper.getHeader("DELETE", url, customHeaders, null, accessToken);log.info("doDeleteRequest => Url: {}, headers: {}", uri, "\n" + JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpDelete(url, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new TuyaCloudSDKException(responseObject);}_result = responseObject.get("result");setResult(_result);return true;} catch (Exception e) {e.printStackTrace();setError(e.getMessage());return false;}}}
    
  5. 创建请求的助手类TuyaRequestHelper,请求的授权签名和header处理都在这里了
    package com.abon.smart_family_lot.application.common.extend.request.tuya;import com.abon.smart_family_lot.application.common.config.TuyaCloudConfig;
    import com.abon.smart_family_lot.application.common.extend.request.BaseRequest;
    import com.abon.smart_family_lot.application.common.util.Sha256Util;
    import lombok.extern.slf4j.Slf4j;
    import org.apache.commons.lang3.StringUtils;
    import org.springframework.stereotype.Component;import javax.annotation.Resource;
    import java.net.URL;
    import java.net.URLDecoder;
    import java.nio.charset.StandardCharsets;
    import java.util.Arrays;
    import java.util.HashMap;
    import java.util.Map;
    import java.util.TreeMap;
    import java.util.stream.Collectors;/*** desc: 涂鸦平台API SIGN辅助类* author: zmq3821@163.com* date: 2021/12/6 10:00*/
    @Slf4j
    @Component
    public class TuyaRequestHelper extends BaseRequest {// 当body为空时的SHA256值private static final String EMPTY_HASH = "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855";// 需要参与签名的Header的keyprivate static final String SING_HEADER_NAME = "Signature-Headers";@Resourceprivate TuyaCloudConfig tuyaCloudConfig;@Resourceprivate TuyaRequestAccessToken tuyaRequestAccessToken;// header请求头参数初始化public Map getHeader(String method, String url, Map headerMap, String body, String accessToken) {Map flattenHeaders = flattenHeaders(headerMap);String t = System.currentTimeMillis() + "";String nonceStr = flattenHeaders.getOrDefault("nonce", "");String signatureHeaders = flattenHeaders.getOrDefault(SING_HEADER_NAME, "");Map headers = new HashMap<>(flattenHeaders);System.out.println("_headers:" + headers);headers.put("client_id", tuyaCloudConfig.getClientId());headers.put("t", t);headers.put("sign_method", "HMAC-SHA256");headers.put("lang", "zh"); // 语言类型。中国区默认 zh,其他区默认 enheaders.put("nonce", nonceStr); // API调用者生成的UUIDheaders.put(SING_HEADER_NAME, signatureHeaders); // 开发者自定义需要加入签名的header字段。String stringToSign = stringToSign(method, url, body, flattenHeaders);if (StringUtils.isNotBlank(accessToken)) {headers.put("access_token", accessToken);headers.put("sign", sign(t, nonceStr, stringToSign, accessToken));} else {headers.put("sign", sign(t, nonceStr, stringToSign, null));}return headers;}// 签名算法// 令牌管理: sign = HMAC-SHA256(client_id + t + nonce + stringToSign, secret).toUpperCase()// 业务管理: sign = HMAC-SHA256(client_id + access_token + t + nonce + stringToSign, secret).toUpperCase()private String sign(String t, String nonce, String stringToSign, String accessToken) {String clientId = tuyaCloudConfig.getClientId();String secret = tuyaCloudConfig.getClientSecret();StringBuilder str = new StringBuilder();accessToken = StringUtils.isBlank(accessToken) ? "" : accessToken;nonce = StringUtils.isBlank(nonce) ? "" : nonce;str.append(clientId).append(accessToken).append(t).append(nonce).append(stringToSign);return Sha256Util.sha256HMAC(str.toString(), secret);}// 生成签名字符串public String stringToSign(String httpMethod, String url, String bodyStr, Map headers) {try {log.info("httpMethod:{}, url:{}, bodyStr:{}, headers:{}", httpMethod, url, bodyStr, headers);String sha256 = EMPTY_HASH;if (bodyStr != null && bodyStr.length() > 0) {System.out.println("bodyStr:" + bodyStr);sha256 = Sha256Util.encryption(bodyStr);}String headersStr = getSignHeaderStr(headers);String newUrl = getPathAndSortParam(new URL(url));log.info("生成签名字符串 => \n httpMethod: {},sha256: {},headersStr: {}, newUrl: {}", httpMethod, sha256, headersStr, newUrl);return httpMethod.toUpperCase() + "\n" + sha256 + "\n" + headersStr + "\n" + newUrl;} catch (Exception e) {e.printStackTrace();throw new RuntimeException(e);}}// 过滤headerprivate static Map flattenHeaders(Map headers) {Map newHeaders = new HashMap<>();if (headers != null) {headers.forEach((name, values) -> {if (values == null || values.isEmpty()) {newHeaders.put(name, "");} else {newHeaders.put(name, values);}});}return newHeaders;}// 返回参与签名计算的SignHeaderStrprivate String getSignHeaderStr(Map headers) {String headersStr = "";String signHeaderStr = headers.getOrDefault(SING_HEADER_NAME, "");if (StringUtils.isNotEmpty(signHeaderStr)) {String[] signHeaderKeys = signHeaderStr.split("\\s*:\\s*");headersStr = Arrays.stream(signHeaderKeys).map(String::trim).filter(it -> it.length() > 0).map(it -> it + ":" + headers.get(it)).collect(Collectors.joining("\n"));}return headersStr;}// 返回排序处理过的urlpublic static String getPathAndSortParam(URL url) {String path = "";try {path = url.getPath();String query = "";if (StringUtils.isNotBlank(url.getQuery())) {query = URLDecoder.decode(url.getQuery(), StandardCharsets.UTF_8);}if (StringUtils.isBlank(query)) {return path;}Map kvMap = new TreeMap<>(); //红黑树排序String[] kvs = query.split("\\&");for (String kv : kvs) {String[] kvArr = kv.split("=");if (kvArr.length > 1) {kvMap.put(kvArr[0], kvArr[1]);} else {kvMap.put(kvArr[0], "");}}return path + "?" + kvMap.entrySet().stream().map(it -> it.getKey() + "=" + it.getValue()).collect(Collectors.joining("&"));} catch (Exception ignored) {}return path;}// 拼接URL与参数public String getUrlStr(String path, Map querys) {String endpoint = tuyaCloudConfig.getEndpoint();StringBuilder urlStr = new StringBuilder();urlStr.append(endpoint);if (StringUtils.isNotBlank(path)) {urlStr.append(path);}if (querys != null) {StringBuilder paramsStr = new StringBuilder();querys.forEach((key, val) -> {if (paramsStr.length() > 0) {paramsStr.append("&");}if (StringUtils.isNotBlank(key)) {paramsStr.append(key).append("=").append(val);}});if (paramsStr.length() > 0) {urlStr.append("?").append(paramsStr);}}return urlStr.toString();}// 返回accessTokenpublic String getAccesToken() {return tuyaRequestAccessToken.getAccessToken();}}
    

    4

  6. 还要创建accessToekn管理类TuyaRequestAccessToken

    简单来说是把获取到的accessToken和refreshToken分别放入缓存中,若accessToken缓存到期则使用refreshToken刷新Token并再次放入缓存;TuyaRequestAccessToken由此实现了token的管理,而在请求内只需调用而无需关心accessToken的获取逻辑
     

    package com.abon.smart_family_lot.application.common.extend.request.tuya;import com.abon.smart_family_lot.application.common.exception.TuyaCloudSDKException;
    import com.abon.smart_family_lot.application.common.extend.request.BaseRequest;
    import com.abon.smart_family_lot.application.common.util.DateUtil;
    import com.abon.smart_family_lot.application.common.util.HttpUtil;
    import com.abon.smart_family_lot.application.common.util.RedisCacheUtil;
    import com.alibaba.fastjson.JSON;
    import com.alibaba.fastjson.JSONObject;
    import lombok.AllArgsConstructor;
    import lombok.Data;
    import lombok.NoArgsConstructor;
    import lombok.extern.slf4j.Slf4j;
    import org.springframework.stereotype.Component;import javax.annotation.Resource;
    import java.io.Serializable;
    import java.util.Map;/*** desc: 涂鸦平台API accessToekn管理类* author: zmq3821@163.com* date: 2021/12/6 10:00*/
    @Slf4j
    @Component
    public class TuyaRequestAccessToken {// accessToken的缓存键private final String cacheAccessTokenKey = "TUYA_API_ACCESS_TOKEN";// refreshToken的缓存键private final String cacheRefreshTokenKey = "TUYA_API_REFRESH_ACCESS_TOKEN";@Resourceprivate TuyaRequestHelper tuyaRequestSign;@Resourceprivate RedisCacheUtil redisCacheUtil;@Data@NoArgsConstructor // 注解在类上;为类提供一个无参的构造方法@AllArgsConstructor // 注解在类上;为类提供一个全参的构造方法public static class TokenVO implements Serializable {private String accessToken;private String refreshToken;private long expireTime;}// 获取AccessTokenprivate void createAccessToken() {log.info("获取AccessToken");try {String uri = "/v1.0/token?grant_type=1";String url = tuyaRequestSign.getUrlStr(uri, null);Map newHeaders = tuyaRequestSign.getHeader("GET", url, null, null, null);log.info("doGetRequest => Url: {}, headers: {}", uri, "\n" + JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpGet(url, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new RuntimeException(responseObject.getString("message"));}JSONObject data = responseObject.getJSONObject("result");TokenVO tokenVO = JSONObject.toJavaObject(data, TokenVO.class);long _expireTime = tokenVO.getExpireTime() - 10; //留出10秒避免临界值redisCacheUtil.set(cacheAccessTokenKey, tokenVO.getAccessToken(), _expireTime);redisCacheUtil.set(cacheRefreshTokenKey, tokenVO.getRefreshToken());} catch (Exception e) {e.printStackTrace();}}// 刷新RefreshTokenprivate void createRefreshToken(String refresh_token) {log.info("使用refreshToken换取accessToken");try {String uri = "/v1.0/token/" + refresh_token;String url = tuyaRequestSign.getUrlStr(uri, null);Map newHeaders = tuyaRequestSign.getHeader("GET", url, null, null, null);log.info("doGetRequest => Url: {}, headers: {}", uri, "\n" + JSONObject.toJSONString(newHeaders));String response = HttpUtil.httpGet(url, newHeaders);log.info("response: {}", response);JSONObject responseObject = JSONObject.parseObject(response);Boolean success = responseObject.getBoolean("success");if (!success) {throw new RuntimeException(responseObject.getString("message"));}JSONObject data = responseObject.getJSONObject("result");TokenVO tokenVO = JSONObject.toJavaObject(data, TokenVO.class);long _expireTime = tokenVO.getExpireTime() - 10; //留出10秒避免临界值redisCacheUtil.set(cacheAccessTokenKey, tokenVO.getAccessToken(), _expireTime);redisCacheUtil.set(cacheRefreshTokenKey, tokenVO.getRefreshToken());} catch (Exception e) {e.printStackTrace();}}// 返回accessTokenpublic String getAccessToken() {String _accessToken = redisCacheUtil.get(cacheAccessTokenKey);System.out.println("_accessToken:" + _accessToken);if (_accessToken == null) {String _refreshToken = redisCacheUtil.get(cacheRefreshTokenKey);System.out.println("_refreshToken:" + _refreshToken);if (_refreshToken == null) {createAccessToken();} else {createRefreshToken(_refreshToken);}return getAccessToken();} else {return _accessToken;}}}
    
  7. 最后示例一个GET请求的调用
     

    
    public boolean info(DeviceValidate.infoTDO params) {String deviceId = params.getDeviceId();boolean res = tuyaRequest.doGetRequest("/v1.0/devices/" + deviceId, null);if (!res) {setError(tuyaRequest.getError());return false;}JSONObject data = JSONObject.parseObject(tuyaRequest.getResult().toString());setResult(data);return true;}

8.应某位小伙伴的要求再补加一个Sha256Util工具类

package com.abon.smart_family_lot.application.common.util;import javax.crypto.Mac;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
import javax.xml.bind.annotation.adapters.HexBinaryAdapter;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;/*** desc: Sha256工具类* author: zmq3821@163.com* date: 2021/12/3 16:07*/
public class Sha256Util {public static String encryption(String str) throws Exception {return encryption(str.getBytes(StandardCharsets.UTF_8));}public static String encryption(byte[] buf) throws Exception {MessageDigest messageDigest;messageDigest = MessageDigest.getInstance("SHA-256");messageDigest.update(buf);return byte2Hex(messageDigest.digest());}private static String byte2Hex(byte[] bytes) {StringBuilder stringBuffer = new StringBuilder();String temp;for (byte aByte : bytes) {temp = Integer.toHexString(aByte & 0xFF);if (temp.length() == 1) {stringBuffer.append("0");}stringBuffer.append(temp);}return stringBuffer.toString();}public static String sha256HMAC(String content, String secret) {Mac sha256HMAC = null;try {sha256HMAC = Mac.getInstance("HmacSHA256");} catch (NoSuchAlgorithmException e) {e.printStackTrace();}SecretKey secretKey = new SecretKeySpec(secret.getBytes(StandardCharsets.UTF_8), "HmacSHA256");try {assert sha256HMAC != null;sha256HMAC.init(secretKey);} catch (InvalidKeyException e) {e.printStackTrace();}byte[] digest = sha256HMAC.doFinal(content.getBytes(StandardCharsets.UTF_8));return new HexBinaryAdapter().marshal(digest).toUpperCase();}}


本文来自互联网用户投稿,文章观点仅代表作者本人,不代表本站立场,不承担相关法律责任。如若转载,请注明出处。 如若内容造成侵权/违法违规/事实不符,请点击【内容举报】进行投诉反馈!

相关文章

立即
投稿

微信公众账号

微信扫一扫加关注

返回
顶部