- 先看依赖
org.springframework.boot spring-boot-starter-data-redisorg.apache.httpcomponents httpclientorg.apache.httpcomponents httpasyncclientorg.bgee.log4jdbc-log4j2 log4jdbc-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; @Autowired public 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)); } }
-
先看一下我的目录结构,只是为了方便搞清楚代码存放的逻辑。红框里的就是主要使用到文件
- 先在resources创建tuya.yml
#云端 cloud: client-id: xxx #授权密钥 client-secret: xxxxxxxxxx #授权密钥 endpoint: https://openapi.tuyacn.com #接入地址-中国数据中心
-
在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 { @Override public 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(); } }
-
创建请求基类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; } }
-
创建真正的请求封装类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 { @Resource private 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; } } } - 创建请求的助手类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的key private static final String SING_HEADER_NAME = "Signature-Headers"; @Resource private TuyaCloudConfig tuyaCloudConfig; @Resource private 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,其他区默认 en headers.put("nonce", nonceStr); // API调用者生成的UUID headers.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); } } // 过滤header private 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; } // 返回参与签名计算的SignHeaderStr private 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; } // 返回排序处理过的url public 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(); } // 返回accessToken public String getAccesToken() { return tuyaRequestAccessToken.getAccessToken(); } } 4
-
还要创建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"; @Resource private TuyaRequestHelper tuyaRequestSign; @Resource private RedisCacheUtil redisCacheUtil; @Data @NoArgsConstructor // 注解在类上;为类提供一个无参的构造方法 @AllArgsConstructor // 注解在类上;为类提供一个全参的构造方法 public static class TokenVO implements Serializable { private String accessToken; private String refreshToken; private long expireTime; } // 获取AccessToken private 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(); } } // 刷新RefreshToken private 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(); } } // 返回accessToken public 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; } } } -
最后示例一个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();
}
}
本文章来源于互联网,如有侵权,请联系删除!原文地址:springboot封装 涂鸦物联网平台API鉴权请求的demo
随着物联网或物联网解决方案的出现,许多行业都从提高生产力和运营可靠性的物联网技术中受益匪浅。物联网解决方案提供了一种设置,其中包括传感器、仪器、机器和许多其他连接设备,无需人工干预即可运行。本文将慢慢分解物联网解决方案架构,以更多地了解物联网实施的分步过程。 …