11 changed files with 419 additions and 2 deletions
@ -0,0 +1,56 @@ |
|||||
|
package com.ccsens.signin.api; |
||||
|
|
||||
|
import cn.hutool.core.collection.CollectionUtil; |
||||
|
import cn.hutool.core.util.ObjectUtil; |
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import cn.hutool.extra.servlet.ServletUtil; |
||||
|
import com.ccsens.signin.bean.dto.FiotDto; |
||||
|
import com.ccsens.signin.bean.dto.UserDto; |
||||
|
import com.ccsens.signin.bean.vo.FiotVo; |
||||
|
import com.ccsens.signin.bean.vo.UserVo; |
||||
|
import com.ccsens.signin.exception.UserLoginException; |
||||
|
import com.ccsens.util.JsonResponse; |
||||
|
import com.ccsens.util.WebConstant; |
||||
|
import com.ccsens.util.wx.WxXcxUtil; |
||||
|
import com.ccsens.wechatutil.wxfiotexplorer.FiotExplorerSigninUtil; |
||||
|
import io.swagger.annotations.Api; |
||||
|
import io.swagger.annotations.ApiImplicitParams; |
||||
|
import io.swagger.annotations.ApiOperation; |
||||
|
import io.swagger.annotations.ApiParam; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.validation.annotation.Validated; |
||||
|
import org.springframework.web.bind.annotation.RequestBody; |
||||
|
import org.springframework.web.bind.annotation.RequestMapping; |
||||
|
import org.springframework.web.bind.annotation.RequestMethod; |
||||
|
import org.springframework.web.bind.annotation.RestController; |
||||
|
|
||||
|
import javax.servlet.http.HttpServletRequest; |
||||
|
import java.util.Map; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/30 10:42 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Api(tags = "物联网", description = "") |
||||
|
@RestController |
||||
|
@RequestMapping("/fiot") |
||||
|
public class FiotExplorerController { |
||||
|
|
||||
|
@ApiOperation(value = "物联网用户登录") |
||||
|
@ApiImplicitParams({ |
||||
|
}) |
||||
|
@RequestMapping(value = "/signin", method = RequestMethod.POST, produces = {"application/json;charset=UTF-8"}) |
||||
|
public JsonResponse<FiotVo.Signin> userSignin(@ApiParam @Validated @RequestBody(required = true)FiotDto.Signin signin) throws Exception { |
||||
|
log.info("物联网用户登录:{}", signin); |
||||
|
String type = "FIOT"; |
||||
|
WxXcxUtil.WechatUser wechatUser = WxXcxUtil.getUserInfo(signin.getCode(), type); |
||||
|
String token = FiotExplorerSigninUtil.getToken(wechatUser.openid, signin.getNickName(), signin.getAvatar(), WxXcxUtil.appId(type)); |
||||
|
FiotVo.Signin vo = new FiotVo.Signin(); |
||||
|
vo.setToken(token); |
||||
|
log.info("物联网用户登录结果:{}", vo); |
||||
|
return JsonResponse.newInstance().ok(vo); |
||||
|
|
||||
|
} |
||||
|
} |
@ -0,0 +1,31 @@ |
|||||
|
package com.ccsens.signin.bean.dto; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import javax.validation.constraints.NotEmpty; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/30 10:45 |
||||
|
*/ |
||||
|
public class FiotDto { |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("登录-请求") |
||||
|
public static class Signin { |
||||
|
@NotEmpty |
||||
|
@ApiModelProperty("登录code") |
||||
|
private String code; |
||||
|
@NotEmpty |
||||
|
@ApiModelProperty("用户名") |
||||
|
private String nickName; |
||||
|
@NotEmpty |
||||
|
@ApiModelProperty("头像") |
||||
|
private String avatar; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,18 @@ |
|||||
|
package com.ccsens.signin.bean.vo; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModel; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/30 10:52 |
||||
|
*/ |
||||
|
public class FiotVo { |
||||
|
|
||||
|
@Data |
||||
|
@ApiModel("物联网登录-返回") |
||||
|
public static class Signin{ |
||||
|
private String token; |
||||
|
} |
||||
|
} |
@ -0,0 +1,74 @@ |
|||||
|
package com.ccsens.wechatutil.bean.po.wxfiotexplorer; |
||||
|
|
||||
|
import cn.hutool.core.date.DatePattern; |
||||
|
import cn.hutool.core.date.DateUtil; |
||||
|
import cn.hutool.core.util.HexUtil; |
||||
|
import com.alibaba.fastjson.JSON; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.ccsens.wechatutil.util.TC3Util; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
import java.util.Calendar; |
||||
|
import java.util.Random; |
||||
|
import java.util.TimeZone; |
||||
|
import java.util.UUID; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/26 9:33 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class FiotSignin { |
||||
|
/** |
||||
|
* 公共参数,本接口取值:AppGetTokenByWeiXin |
||||
|
*/ |
||||
|
private String Action = "AppGetTokenByWeiXin"; |
||||
|
/** |
||||
|
* 公共参数,唯一请求 ID,可自行生成,推荐使用 uuid。定位问题时,需提供该次请求的 RequestId |
||||
|
*/ |
||||
|
private String RequestId = UUID.randomUUID().toString(); |
||||
|
/** |
||||
|
* 公共参数,应用 AppKey ,用于标识对应的小程序或 App |
||||
|
*/ |
||||
|
private String AppKey = null; |
||||
|
/** |
||||
|
* 公共参数,请求签名,需用户自行生成,用于校验请求的合法性 |
||||
|
*/ |
||||
|
private String Signature = null; |
||||
|
/** |
||||
|
* 公共参数,请求的 UINX 时间戳(秒级) |
||||
|
*/ |
||||
|
private long Timestamp = System.currentTimeMillis() / 1000; |
||||
|
/** |
||||
|
* 公共参数,随机正整数,与 Timestamp 联合起来,防止重放攻击 |
||||
|
*/ |
||||
|
private int Nonce = new Random().nextInt(10000); |
||||
|
/** |
||||
|
* 微信用户的 OpenID 或 UnionID |
||||
|
*/ |
||||
|
private String WxOpenID; |
||||
|
/** |
||||
|
* 昵称 |
||||
|
*/ |
||||
|
private String NickName; |
||||
|
/** |
||||
|
* 头像 |
||||
|
*/ |
||||
|
private String Avatar; |
||||
|
|
||||
|
public FiotSignin() { |
||||
|
} |
||||
|
|
||||
|
public FiotSignin(String openId, String nickName, String avatar, String appKey) throws Exception { |
||||
|
this(); |
||||
|
this.WxOpenID = openId; |
||||
|
this.NickName = nickName; |
||||
|
this.Avatar = avatar; |
||||
|
this.AppKey = appKey; |
||||
|
this.Signature = TC3Util.generateSignature("iot", "iot.cloud.tencent.com", this.Timestamp, null); |
||||
|
|
||||
|
} |
||||
|
|
||||
|
|
||||
|
} |
@ -0,0 +1,17 @@ |
|||||
|
package com.ccsens.wechatutil.bean.po.wxfiotexplorer; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/30 10:25 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class FiotSigninResponse { |
||||
|
@ApiModelProperty("截止时间,UINX 秒级时间戳") |
||||
|
private Long ExpireAt; |
||||
|
@ApiModelProperty("开发平台返回的 AccessToken,通过该 Token 进行登录后的接口请求") |
||||
|
private String Token; |
||||
|
} |
@ -0,0 +1,30 @@ |
|||||
|
package com.ccsens.wechatutil.bean.po.wxfiotexplorer; |
||||
|
|
||||
|
import io.swagger.annotations.ApiModelProperty; |
||||
|
import lombok.Data; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/30 10:17 |
||||
|
*/ |
||||
|
@Data |
||||
|
public class ResponseInfo<T> { |
||||
|
private Info<T> Response; |
||||
|
private String RequestId; |
||||
|
|
||||
|
|
||||
|
@Data |
||||
|
public static class Info<T>{ |
||||
|
@ApiModelProperty("数据,异常时,data为空") |
||||
|
private T Data; |
||||
|
@ApiModelProperty("错误码,正确返回时,error为空") |
||||
|
private ErrorInfo Error; |
||||
|
} |
||||
|
|
||||
|
@Data |
||||
|
public static class ErrorInfo{ |
||||
|
private String Code; |
||||
|
private String Message; |
||||
|
} |
||||
|
} |
@ -0,0 +1,107 @@ |
|||||
|
package com.ccsens.wechatutil.util; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
|
||||
|
import javax.crypto.Mac; |
||||
|
import javax.crypto.spec.SecretKeySpec; |
||||
|
import javax.xml.bind.DatatypeConverter; |
||||
|
import java.nio.charset.Charset; |
||||
|
import java.nio.charset.StandardCharsets; |
||||
|
import java.security.MessageDigest; |
||||
|
import java.text.SimpleDateFormat; |
||||
|
import java.util.Date; |
||||
|
import java.util.TimeZone; |
||||
|
import java.util.TreeMap; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/29 17:50 |
||||
|
*/ |
||||
|
public class TC3Util { |
||||
|
private final static Charset UTF8 = StandardCharsets.UTF_8; |
||||
|
private final static String SECRET_ID = "AKIDxhBRRAdplRpwnMfnfGaeRxDBsJTN0NTI"; |
||||
|
private final static String SECRET_KEY = "Zrte9MPFo68tMZU8WcXDeqnVx95rYzA6"; |
||||
|
private final static String CT_JSON = "application/json; charset=utf-8"; |
||||
|
|
||||
|
public static byte[] hmac256(byte[] key, String msg) throws Exception { |
||||
|
Mac mac = Mac.getInstance("HmacSHA256"); |
||||
|
SecretKeySpec secretKeySpec = new SecretKeySpec(key, mac.getAlgorithm()); |
||||
|
mac.init(secretKeySpec); |
||||
|
return mac.doFinal(msg.getBytes(UTF8)); |
||||
|
} |
||||
|
|
||||
|
public static String sha256Hex(String s) throws Exception { |
||||
|
MessageDigest md = MessageDigest.getInstance("SHA-256"); |
||||
|
byte[] d = md.digest(s.getBytes(UTF8)); |
||||
|
return DatatypeConverter.printHexBinary(d).toLowerCase(); |
||||
|
} |
||||
|
|
||||
|
public static String generateSignature(String service, String host, long timestamp, String payload) throws Exception { |
||||
|
//String service = "cvm";
|
||||
|
//String host = "cvm.tencentcloudapi.com";
|
||||
|
// String region = "ap-guangzhou";
|
||||
|
// String action = "DescribeInstances";
|
||||
|
// String version = "2017-03-12";
|
||||
|
String algorithm = "TC3-HMAC-SHA256"; |
||||
|
//String timestamp = "1551113065";
|
||||
|
SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd"); |
||||
|
// 注意时区,否则容易出错
|
||||
|
sdf.setTimeZone(TimeZone.getTimeZone("UTC")); |
||||
|
String date = sdf.format(new Date(Long.valueOf(timestamp + "000"))); |
||||
|
|
||||
|
// ************* 步骤 1:拼接规范请求串 *************
|
||||
|
String httpRequestMethod = "POST"; |
||||
|
String canonicalUri = "/"; |
||||
|
String canonicalQueryString = ""; |
||||
|
String canonicalHeaders = "content-type:application/json; charset=utf-8\n" + "host:" + host + "\n"; |
||||
|
String signedHeaders = "content-type;host"; |
||||
|
|
||||
|
// String payload = "{\"Limit\": 1, \"Filters\": [{\"Values\": [\"\\u672a\\u547d\\u540d\"], \"Name\": \"instance-name\"}]}";
|
||||
|
String hashedRequestPayload = StrUtil.isEmpty(payload) ? "" : sha256Hex(payload); |
||||
|
String canonicalRequest = httpRequestMethod + "\n" + canonicalUri + "\n" + canonicalQueryString + "\n" |
||||
|
+ canonicalHeaders + "\n" + signedHeaders + "\n" + hashedRequestPayload; |
||||
|
System.out.println(canonicalRequest); |
||||
|
|
||||
|
// ************* 步骤 2:拼接待签名字符串 *************
|
||||
|
String credentialScope = date + "/" + service + "/" + "tc3_request"; |
||||
|
String hashedCanonicalRequest = sha256Hex(canonicalRequest); |
||||
|
String stringToSign = algorithm + "\n" + timestamp + "\n" + credentialScope + "\n" + hashedCanonicalRequest; |
||||
|
System.out.println(stringToSign); |
||||
|
|
||||
|
// ************* 步骤 3:计算签名 *************
|
||||
|
byte[] secretDate = hmac256(("TC3" + SECRET_KEY).getBytes(UTF8), date); |
||||
|
byte[] secretService = hmac256(secretDate, service); |
||||
|
byte[] secretSigning = hmac256(secretService, "tc3_request"); |
||||
|
String signature = DatatypeConverter.printHexBinary(hmac256(secretSigning, stringToSign)).toLowerCase(); |
||||
|
System.out.println(signature); |
||||
|
|
||||
|
// ************* 步骤 4:拼接 Authorization *************
|
||||
|
String authorization = algorithm + " " + "Credential=" + SECRET_ID + "/" + credentialScope + ", " |
||||
|
+ "SignedHeaders=" + signedHeaders + ", " + "Signature=" + signature; |
||||
|
|
||||
|
TreeMap<String, String> headers = new TreeMap<String, String>(); |
||||
|
headers.put("Authorization", authorization); |
||||
|
headers.put("Content-Type", CT_JSON); |
||||
|
headers.put("Host", host); |
||||
|
// headers.put("X-TC-Action", action);
|
||||
|
// headers.put("X-TC-Timestamp", timestamp);
|
||||
|
// headers.put("X-TC-Version", version);
|
||||
|
// headers.put("X-TC-Region", region);
|
||||
|
|
||||
|
StringBuilder sb = new StringBuilder(); |
||||
|
sb.append("curl -X POST https://").append(host) |
||||
|
.append(" -H \"Authorization: ").append(authorization).append("\"") |
||||
|
.append(" -H \"Content-Type: application/json; charset=utf-8\"") |
||||
|
.append(" -H \"Host: ").append(host).append("\""); |
||||
|
// .append(" -H \"X-TC-Action: ").append(action).append("\"")
|
||||
|
// .append(" -H \"X-TC-Timestamp: ").append(timestamp).append("\"")
|
||||
|
// .append(" -H \"X-TC-Version: ").append(version).append("\"")
|
||||
|
// .append(" -H \"X-TC-Region: ").append(region).append("\"")
|
||||
|
if (StrUtil.isNotEmpty(payload)) { |
||||
|
sb.append(" -d '").append(payload).append("'"); |
||||
|
} |
||||
|
|
||||
|
return sb.toString(); |
||||
|
} |
||||
|
} |
@ -0,0 +1,79 @@ |
|||||
|
package com.ccsens.wechatutil.wxfiotexplorer; |
||||
|
|
||||
|
import cn.hutool.core.util.StrUtil; |
||||
|
import com.alibaba.fastjson.JSONObject; |
||||
|
import com.ccsens.util.CodeError; |
||||
|
import com.ccsens.util.RedisUtil; |
||||
|
import com.ccsens.util.RestTemplateUtil; |
||||
|
import com.ccsens.util.exception.BaseException; |
||||
|
import com.ccsens.wechatutil.bean.po.wxfiotexplorer.FiotSignin; |
||||
|
import com.ccsens.wechatutil.bean.po.wxfiotexplorer.FiotSigninResponse; |
||||
|
import com.ccsens.wechatutil.bean.po.wxfiotexplorer.ResponseInfo; |
||||
|
import lombok.extern.slf4j.Slf4j; |
||||
|
import org.springframework.beans.factory.annotation.Value; |
||||
|
import org.springframework.stereotype.Component; |
||||
|
|
||||
|
import javax.annotation.PostConstruct; |
||||
|
import javax.annotation.Resource; |
||||
|
|
||||
|
/** |
||||
|
* @description: |
||||
|
* @author: whj |
||||
|
* @time: 2021/11/25 11:47 |
||||
|
*/ |
||||
|
@Slf4j |
||||
|
@Component |
||||
|
public class FiotExplorerSigninUtil { |
||||
|
|
||||
|
private final static String SIGN_URL = "https://iot.cloud.tencent.com/api/exploreropen/appapi"; |
||||
|
private final static String TOKEN_KEY = "FIOT_{}"; |
||||
|
@Value("${fiot.appId:}") |
||||
|
private String appKey; |
||||
|
@Value("${fiot.secret:}") |
||||
|
private String appSecret; |
||||
|
@Resource |
||||
|
private RedisUtil redisUtil; |
||||
|
private static FiotExplorerSigninUtil util; |
||||
|
|
||||
|
@PostConstruct |
||||
|
public void init(){ |
||||
|
util = this; |
||||
|
util.redisUtil = this.redisUtil; |
||||
|
util.appKey = this.appKey; |
||||
|
util.appSecret = this.appSecret; |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
|
||||
|
public static String getToken(String openId, String nickName, String avatar, String appKey) throws Exception { |
||||
|
log.info("物联网查询token:{},{},{}", openId, nickName, avatar); |
||||
|
String key = StrUtil.format(TOKEN_KEY, openId); |
||||
|
Object o = util.redisUtil.get(key); |
||||
|
log.info("{}缓存的token:{}", key, o); |
||||
|
if (o != null) { |
||||
|
return (String) o; |
||||
|
} |
||||
|
// 查询token
|
||||
|
|
||||
|
FiotSignin fiotSignin = new FiotSignin(openId, nickName, avatar, appKey); |
||||
|
log.info("登录:{}", fiotSignin); |
||||
|
String s = RestTemplateUtil.postBody(SIGN_URL, fiotSignin); |
||||
|
log.info("登录结果:{}", s); |
||||
|
if (StrUtil.isEmpty(s)) { |
||||
|
throw new BaseException(CodeError.THIRD_ERROR); |
||||
|
} |
||||
|
ResponseInfo<FiotSigninResponse> res = JSONObject.parseObject(s, ResponseInfo.class); |
||||
|
if (res.getResponse() == null) { |
||||
|
throw new BaseException(CodeError.THIRD_ERROR); |
||||
|
} else if (res.getResponse().getError() != null ) { |
||||
|
throw new BaseException(CodeError.THIRD_ERROR.getCode(), res.getResponse().getError().getMessage()); |
||||
|
} |
||||
|
FiotSigninResponse data = res.getResponse().getData(); |
||||
|
util.redisUtil.set(key, data.getToken(), data.getExpireAt() - System.currentTimeMillis()/1000); |
||||
|
return data.getToken(); |
||||
|
} |
||||
|
|
||||
|
|
||||
|
|
||||
|
} |
Loading…
Reference in new issue