diff --git a/signin/src/main/java/com/ccsens/signin/api/FiotExplorerController.java b/signin/src/main/java/com/ccsens/signin/api/FiotExplorerController.java new file mode 100644 index 00000000..2b1c0c67 --- /dev/null +++ b/signin/src/main/java/com/ccsens/signin/api/FiotExplorerController.java @@ -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 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); + + } +} diff --git a/signin/src/main/java/com/ccsens/signin/bean/dto/FiotDto.java b/signin/src/main/java/com/ccsens/signin/bean/dto/FiotDto.java new file mode 100644 index 00000000..04031e92 --- /dev/null +++ b/signin/src/main/java/com/ccsens/signin/bean/dto/FiotDto.java @@ -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; + } + + +} diff --git a/signin/src/main/java/com/ccsens/signin/bean/vo/FiotVo.java b/signin/src/main/java/com/ccsens/signin/bean/vo/FiotVo.java new file mode 100644 index 00000000..9cc85766 --- /dev/null +++ b/signin/src/main/java/com/ccsens/signin/bean/vo/FiotVo.java @@ -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; + } +} diff --git a/signin/src/main/java/com/ccsens/signin/service/UserService.java b/signin/src/main/java/com/ccsens/signin/service/UserService.java index d4300c7e..b3d77577 100644 --- a/signin/src/main/java/com/ccsens/signin/service/UserService.java +++ b/signin/src/main/java/com/ccsens/signin/service/UserService.java @@ -358,7 +358,7 @@ public class UserService implements IUserService { if (sign == null) { return null; } - redisUtil.set(StrUtil.format(WebConstant.Wx.SESSION_KEY, sign.getUserId(), gameType), wechatUser.session_key); + redisUtil.set(StrUtil.format(WebConstant.Wx.SESSION_KEY, sign.getUserId(), gameType), wechatUser.session_key, WebConstant.Time.DAY); return sign; } diff --git a/util/src/main/java/com/ccsens/util/WebConstant.java b/util/src/main/java/com/ccsens/util/WebConstant.java index c3efbef0..3f01b317 100644 --- a/util/src/main/java/com/ccsens/util/WebConstant.java +++ b/util/src/main/java/com/ccsens/util/WebConstant.java @@ -25,6 +25,10 @@ public class WebConstant { /* 导入WBS ,规定标签长度 */ public static final Integer LABEL_LENGTH = 6; + public static class Time { + public static final long DAY = 24 * 3600 * 1000; + } + /**属性名*/ public static class Field{ public static final String CODE = "code"; @@ -47,6 +51,7 @@ public class WebConstant { } public static final String SESSION_KEY = "{}_{}_session_key"; + public static final String OPEN_KEY = "{}_{}_openid_key"; } diff --git a/util/src/main/java/com/ccsens/util/wx/WxXcxUtil.java b/util/src/main/java/com/ccsens/util/wx/WxXcxUtil.java index 26ac0908..1cecbe6f 100644 --- a/util/src/main/java/com/ccsens/util/wx/WxXcxUtil.java +++ b/util/src/main/java/com/ccsens/util/wx/WxXcxUtil.java @@ -190,7 +190,7 @@ public class WxXcxUtil { private static final String key = ""; - private static String appId(String gameType){ + public static String appId(String gameType){ switch (gameType){ case "SP": return appid_sp; diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSignin.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSignin.java new file mode 100644 index 00000000..fa209c7e --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSignin.java @@ -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); + + } + + +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSigninResponse.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSigninResponse.java new file mode 100644 index 00000000..896e4286 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/FiotSigninResponse.java @@ -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; +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/ResponseInfo.java b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/ResponseInfo.java new file mode 100644 index 00000000..4aa5486d --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/bean/po/wxfiotexplorer/ResponseInfo.java @@ -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 { + private Info Response; + private String RequestId; + + + @Data + public static class Info{ + @ApiModelProperty("数据,异常时,data为空") + private T Data; + @ApiModelProperty("错误码,正确返回时,error为空") + private ErrorInfo Error; + } + + @Data + public static class ErrorInfo{ + private String Code; + private String Message; + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/util/TC3Util.java b/wechatutil/src/main/java/com/ccsens/wechatutil/util/TC3Util.java new file mode 100644 index 00000000..f5b3be29 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/util/TC3Util.java @@ -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 headers = new TreeMap(); + 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(); + } +} diff --git a/wechatutil/src/main/java/com/ccsens/wechatutil/wxfiotexplorer/FiotExplorerSigninUtil.java b/wechatutil/src/main/java/com/ccsens/wechatutil/wxfiotexplorer/FiotExplorerSigninUtil.java new file mode 100644 index 00000000..65bc60e1 --- /dev/null +++ b/wechatutil/src/main/java/com/ccsens/wechatutil/wxfiotexplorer/FiotExplorerSigninUtil.java @@ -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 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(); + } + + + +}