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