微信小程序登录
This commit is contained in:
@@ -22,11 +22,17 @@ public class CommonConstants {
|
||||
|
||||
public static final int REFRESH_TOKEN_EXPIRE = 60*60*24*30;
|
||||
|
||||
public static final int THREE_DAY_SECONDS = 60*60*24*3;
|
||||
|
||||
/**
|
||||
* 系统用户id
|
||||
*/
|
||||
public static final String SYSTEM_USER_ID = "system";
|
||||
public static final String COMMA = ",";
|
||||
public static final String MOSAICS = "#";
|
||||
|
||||
public static final String WX_APP_SECRET_KEY = "wx_app_secret_key:{0}";
|
||||
public static final String MINI_PROGRAM_SESSION_KEY = "mini_program_session_key:{0}:{1}";
|
||||
|
||||
|
||||
public static final int ZERO = 0;
|
||||
|
||||
@@ -35,6 +35,10 @@ public enum ErrorCodeEnum {
|
||||
ENTERPRISE_NOT_EXIST(1021020,"企业不存在",null),
|
||||
USER_NOT_EXIST(1021021,"用户不存在",null),
|
||||
USER_WAIT_AUDIT(1021018,"账号审核中,请联系企业管理员",null),
|
||||
OPERATION_OVER_TIME(1021019, "您的操作过于频繁,休息一下~", null),
|
||||
GET_APP_SECRET_ERROR(1021019, "获取secret异常", null),
|
||||
WX_SERVICE_ERROR(1021020, "调用微信服务异常", null),
|
||||
SESSION_KEY_ERROR(1021021, "sessionKey过期", null),
|
||||
;
|
||||
|
||||
|
||||
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.cool.store.utils;
|
||||
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import org.springframework.util.Assert;
|
||||
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.spec.IvParameterSpec;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.AlgorithmParameters;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: AesUtil
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
|
||||
public class AesUtil {
|
||||
|
||||
private static final Charset utf8 = StandardCharsets.UTF_8;
|
||||
|
||||
|
||||
public static String genAesKey() {
|
||||
return StringUtil.random(32);
|
||||
}
|
||||
|
||||
public static String encrypt(String content, String aesTextKey) {
|
||||
return Base64.getEncoder().encodeToString(encrypt(content.getBytes(utf8), aesTextKey.getBytes(utf8)));
|
||||
}
|
||||
|
||||
public static String decrypt(String content, String aesTextKey) {
|
||||
byte[] buffer = Base64.getDecoder().decode(content);
|
||||
return new String(decrypt(buffer, aesTextKey.getBytes(utf8)), utf8);
|
||||
}
|
||||
|
||||
public static byte[] encrypt(byte[] content, byte[] aesKey) {
|
||||
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(aesKey, 0, 16);
|
||||
cipher.init(Cipher.ENCRYPT_MODE, keySpec, iv);
|
||||
return cipher.doFinal(Pkcs7Encoder.encode(content));
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] decrypt(byte[] encrypted, byte[] aesKey) {
|
||||
Assert.isTrue(aesKey.length == 32, "IllegalAesKey, aesKey's length must be 32");
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
SecretKeySpec keySpec = new SecretKeySpec(aesKey, "AES");
|
||||
IvParameterSpec iv = new IvParameterSpec(Arrays.copyOfRange(aesKey, 0, 16));
|
||||
cipher.init(Cipher.DECRYPT_MODE, keySpec, iv);
|
||||
return Pkcs7Encoder.decode(cipher.doFinal(encrypted));
|
||||
} catch (Exception e) {
|
||||
throw new ServiceException(e.getMessage());
|
||||
}
|
||||
}
|
||||
|
||||
public static String decryptWechat(String sessionKey, String encryptedData, String ivStr) {
|
||||
try {
|
||||
AlgorithmParameters params = AlgorithmParameters.getInstance("AES");
|
||||
params.init(new IvParameterSpec(Base64.getDecoder().decode(ivStr)));
|
||||
Cipher cipher = Cipher.getInstance("AES/CBC/NoPadding");
|
||||
cipher.init(2, new SecretKeySpec(Base64.getDecoder().decode(sessionKey), "AES"), params);
|
||||
return new String(Pkcs7Encoder.decode(cipher.doFinal(Base64.getDecoder().decode(encryptedData))), StandardCharsets.UTF_8);
|
||||
} catch (Exception var5) {
|
||||
throw new ServiceException("AES解密失败");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package com.cool.store.utils;
|
||||
|
||||
import java.nio.charset.Charset;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: Pkcs7Encoder
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:29
|
||||
*/
|
||||
public class Pkcs7Encoder {
|
||||
|
||||
private static int BLOCK_SIZE = 32;
|
||||
private static final Charset CHARSET = StandardCharsets.UTF_8;
|
||||
|
||||
/**
|
||||
* 获得对明文进行补位填充的字节.
|
||||
*
|
||||
* @param count 需要进行填充补位操作的明文字节个数
|
||||
* @return 补齐用的字节数组
|
||||
*/
|
||||
public static byte[] encode(int count) {
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
// 获得补位所用的字符
|
||||
char padChr = chr(amountToPad);
|
||||
StringBuilder tmp = new StringBuilder();
|
||||
for (int index = 0; index < amountToPad; index++) {
|
||||
tmp.append(padChr);
|
||||
}
|
||||
return tmp.toString().getBytes(CHARSET);
|
||||
}
|
||||
|
||||
public static byte[] encode(byte[] src) {
|
||||
int count = src.length;
|
||||
// 计算需要填充的位数
|
||||
int amountToPad = BLOCK_SIZE - (count % BLOCK_SIZE);
|
||||
if (amountToPad == 0) {
|
||||
amountToPad = BLOCK_SIZE;
|
||||
}
|
||||
// 获得补位所用的字符
|
||||
byte pad = (byte) (amountToPad & 0xFF);
|
||||
byte[] pads = new byte[amountToPad];
|
||||
for (int index = 0; index < amountToPad; index++) {
|
||||
pads[index] = pad;
|
||||
}
|
||||
int length = count + amountToPad;
|
||||
byte[] dest = new byte[length];
|
||||
System.arraycopy(src, 0, dest, 0, count);
|
||||
System.arraycopy(pads, 0, dest, count, amountToPad);
|
||||
return dest;
|
||||
}
|
||||
|
||||
public static byte[] decode(byte[] decrypted) {
|
||||
int pad = decrypted[decrypted.length - 1];
|
||||
if (pad < 1 || pad > BLOCK_SIZE) {
|
||||
pad = 0;
|
||||
}
|
||||
if (pad > 0) {
|
||||
return Arrays.copyOfRange(decrypted, 0, decrypted.length - pad);
|
||||
}
|
||||
return decrypted;
|
||||
}
|
||||
|
||||
private static char chr(int a) {
|
||||
byte target = (byte) (a & 0xFF);
|
||||
return (char) target;
|
||||
}
|
||||
}
|
||||
@@ -1372,7 +1372,16 @@ public class RedisUtilPool {
|
||||
return setnx.equals(1L) ;
|
||||
}
|
||||
}.getResult();
|
||||
}
|
||||
|
||||
public Boolean lock(String key){
|
||||
return new Executor<Boolean>(shardedJedisPool) {
|
||||
@Override
|
||||
Boolean execute() {
|
||||
Long setnx = jedis.setnx(key, System.currentTimeMillis() + "");
|
||||
return setnx.equals(1L) ;
|
||||
}
|
||||
}.getResult();
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,223 @@
|
||||
package com.cool.store.utils;
|
||||
|
||||
import org.springframework.lang.Nullable;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.web.util.HtmlUtils;
|
||||
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadLocalRandom;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: AesUtil
|
||||
* @Description:字符串相关操作
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
|
||||
public class StringUtil extends org.apache.commons.lang3.StringUtils {
|
||||
|
||||
private static final char UPPER_A = 'A';
|
||||
private static final char LOWER_A = 'a';
|
||||
private static final char UPPER_Z = 'Z';
|
||||
private static final char LOWER_Z = 'z';
|
||||
|
||||
private static final byte[] DIGITS = {
|
||||
'0', '1', '2', '3', '4', '5',
|
||||
'6', '7', '8', '9', 'a', 'b',
|
||||
'c', 'd', 'e', 'f', 'g', 'h',
|
||||
'i', 'j', 'k', 'l', 'm', 'n',
|
||||
'o', 'p', 'q', 'r', 's', 't',
|
||||
'u', 'v', 'w', 'x', 'y', 'z',
|
||||
'A', 'B', 'C', 'D', 'E', 'F',
|
||||
'G', 'H', 'I', 'J', 'K', 'L',
|
||||
'M', 'N', 'O', 'P', 'Q', 'R',
|
||||
'S', 'T', 'U', 'V', 'W', 'X',
|
||||
'Y', 'Z'
|
||||
};
|
||||
/**
|
||||
* 特殊字符正则,sql特殊字符和空白符
|
||||
*/
|
||||
private static final Pattern SPECIAL_CHARS_REGEX = Pattern.compile("[`'\"|/,;()-+*%#·•<C2B7> \\s]");
|
||||
/**
|
||||
* 随机字符串因子
|
||||
*/
|
||||
private static final String INT_STR = "0123456789";
|
||||
private static final String STR_STR = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
|
||||
private static final String ALL_STR = INT_STR + STR_STR;
|
||||
|
||||
/**
|
||||
* 首字母变小写
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return {String}
|
||||
*/
|
||||
public static String firstCharToLower(String str) {
|
||||
char firstChar = str.charAt(0);
|
||||
if (firstChar >= UPPER_A && firstChar <= UPPER_Z) {
|
||||
char[] arr = str.toCharArray();
|
||||
arr[0] += (LOWER_A - UPPER_A);
|
||||
return new String(arr);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
/**
|
||||
* 首字母变大写
|
||||
*
|
||||
* @param str 字符串
|
||||
* @return {String}
|
||||
*/
|
||||
public static String firstCharToUpper(String str) {
|
||||
char firstChar = str.charAt(0);
|
||||
if (firstChar >= LOWER_A && firstChar <= LOWER_Z) {
|
||||
char[] arr = str.toCharArray();
|
||||
arr[0] -= (LOWER_A - UPPER_A);
|
||||
return new String(arr);
|
||||
}
|
||||
return str;
|
||||
}
|
||||
|
||||
public static String appendParams(String url, Map<String, Object> params) {
|
||||
if (StringUtil.isBlank(url)) {
|
||||
return "";
|
||||
} else if (CollectionUtils.isEmpty(params)) {
|
||||
return url.trim();
|
||||
} else {
|
||||
StringBuilder sb = new StringBuilder(200);
|
||||
params.forEach((k, v) -> sb.append(k).append("=").append(v).append("&"));
|
||||
sb.deleteCharAt(sb.length() - 1);
|
||||
url = url.trim();
|
||||
int length = url.length();
|
||||
int index = url.indexOf("?");
|
||||
if (index > -1) {
|
||||
if ((length - 1) == index) {
|
||||
url += sb.toString();
|
||||
} else {
|
||||
url += "&" + sb.toString();
|
||||
}
|
||||
} else {
|
||||
url += "?" + sb.toString();
|
||||
}
|
||||
return url;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 生成uuid,采用 jdk 9 的形式,优化性能
|
||||
*
|
||||
* @return UUID
|
||||
*/
|
||||
public static String getUUID() {
|
||||
ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
long lsb = random.nextLong();
|
||||
long msb = random.nextLong();
|
||||
byte[] buf = new byte[32];
|
||||
formatUnsignedLong(lsb, buf, 20, 12);
|
||||
formatUnsignedLong(lsb >>> 48, buf, 16, 4);
|
||||
formatUnsignedLong(msb, buf, 12, 4);
|
||||
formatUnsignedLong(msb >>> 16, buf, 8, 4);
|
||||
formatUnsignedLong(msb >>> 32, buf, 0, 8);
|
||||
return new String(buf, StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
private static void formatUnsignedLong(long val, byte[] buf, int offset, int len) {
|
||||
int charPos = offset + len;
|
||||
int radix = 1 << 4;
|
||||
int mask = radix - 1;
|
||||
do {
|
||||
buf[--charPos] = DIGITS[((int) val) & mask];
|
||||
val >>>= 4;
|
||||
} while (charPos > offset);
|
||||
}
|
||||
|
||||
/**
|
||||
* 转义HTML用于安全过滤
|
||||
*
|
||||
* @param html html
|
||||
* @return {String}
|
||||
*/
|
||||
public static String escapeHtml(String html) {
|
||||
return HtmlUtils.htmlEscape(html);
|
||||
}
|
||||
|
||||
/**
|
||||
* 清理字符串,清理出某些不可见字符和一些sql特殊字符
|
||||
*
|
||||
* @param txt 文本
|
||||
* @return {String}
|
||||
*/
|
||||
@Nullable
|
||||
public static String cleanText(@Nullable String txt) {
|
||||
if (txt == null) {
|
||||
return null;
|
||||
}
|
||||
return SPECIAL_CHARS_REGEX.matcher(txt).replaceAll(StringUtil.EMPTY);
|
||||
}
|
||||
|
||||
/**
|
||||
* 获取标识符,用于参数清理
|
||||
*
|
||||
* @param param 参数
|
||||
* @return 清理后的标识符
|
||||
*/
|
||||
@Nullable
|
||||
public static String cleanIdentifier(@Nullable String param) {
|
||||
if (param == null) {
|
||||
return null;
|
||||
}
|
||||
StringBuilder paramBuilder = new StringBuilder();
|
||||
for (int i = 0; i < param.length(); i++) {
|
||||
char c = param.charAt(i);
|
||||
if (Character.isJavaIdentifierPart(c)) {
|
||||
paramBuilder.append(c);
|
||||
}
|
||||
}
|
||||
return paramBuilder.toString();
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机数生成
|
||||
*
|
||||
* @param count 字符长度
|
||||
* @return 随机数
|
||||
*/
|
||||
public static String random(int count) {
|
||||
return random(count, RandomType.ALL);
|
||||
}
|
||||
|
||||
/**
|
||||
* 随机数生成
|
||||
*
|
||||
* @param count 字符长度
|
||||
* @param randomType 随机数类别
|
||||
* @return 随机数
|
||||
*/
|
||||
public static String random(int count, RandomType randomType) {
|
||||
if (count == 0) {
|
||||
return "";
|
||||
}
|
||||
Assert.isTrue(count > 0, "Requested random string length " + count + " is less than 0.");
|
||||
final ThreadLocalRandom random = ThreadLocalRandom.current();
|
||||
char[] buffer = new char[count];
|
||||
for (int i = 0; i < count; i++) {
|
||||
if (RandomType.INT == randomType) {
|
||||
buffer[i] = INT_STR.charAt(random.nextInt(INT_STR.length()));
|
||||
} else if (RandomType.STRING == randomType) {
|
||||
buffer[i] = STR_STR.charAt(random.nextInt(STR_STR.length()));
|
||||
} else {
|
||||
buffer[i] = ALL_STR.charAt(random.nextInt(ALL_STR.length()));
|
||||
}
|
||||
}
|
||||
return new String(buffer);
|
||||
}
|
||||
|
||||
public enum RandomType {
|
||||
/**
|
||||
* INT STRING ALL
|
||||
*/
|
||||
INT, STRING, ALL;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
package com.cool.store.dto.wx;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: CodeSessionDTO
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
@Data
|
||||
public class CodeSessionDTO extends WXBaseResultDTO{
|
||||
|
||||
@JSONField(name = "session_key")
|
||||
private String sessionKey;
|
||||
|
||||
@JSONField(name = "openid")
|
||||
private String openid;
|
||||
|
||||
@JSONField(name = "unionid")
|
||||
private String unionId;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.cool.store.dto.wx;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: MiniProgramLoginDTO
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
@Data
|
||||
public class MiniProgramLoginDTO {
|
||||
|
||||
@NotBlank(message = "appid不能为空")
|
||||
private String appid;
|
||||
|
||||
@NotBlank(message = "jsCode不能为空")
|
||||
private String jsCode;
|
||||
|
||||
@NotBlank(message = "用户encryptedData不能为空")
|
||||
private String encryptedData;
|
||||
|
||||
@NotBlank(message = "ivStr不能为空")
|
||||
private String ivStr;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.cool.store.dto.wx;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import javax.validation.constraints.NotBlank;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: MiniProgramMsgDTO
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
@Data
|
||||
public class MiniProgramMsgDTO {
|
||||
|
||||
@NotBlank(message = "appid不能为空")
|
||||
private String appid;
|
||||
|
||||
@NotBlank(message = "encryptedData不能为空")
|
||||
private String encryptedData;
|
||||
|
||||
@NotBlank(message = "ivStr不能为空")
|
||||
private String ivStr;
|
||||
|
||||
@NotBlank(message = "openid不能为空")
|
||||
private String openid;
|
||||
}
|
||||
@@ -0,0 +1,27 @@
|
||||
package com.cool.store.dto.wx;
|
||||
|
||||
import com.alibaba.fastjson.annotation.JSONField;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: WXBaseResultDTO
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:52
|
||||
*/
|
||||
@Data
|
||||
public class WXBaseResultDTO {
|
||||
|
||||
private static final String SUCCESS_CODE = "0";
|
||||
|
||||
@JSONField(name = "errcode")
|
||||
private String errCode;
|
||||
|
||||
@JSONField(name = "errmsg")
|
||||
private String errMsg;
|
||||
|
||||
public boolean isSuccess() {
|
||||
return this.errCode == null || this.errCode.isEmpty() || this.errCode.equals("0");
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.cool.store.vo.wx;
|
||||
|
||||
import lombok.AllArgsConstructor;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
import lombok.NoArgsConstructor;
|
||||
|
||||
|
||||
@Builder
|
||||
@Data
|
||||
@AllArgsConstructor
|
||||
@NoArgsConstructor
|
||||
public class CodeSessionVO {
|
||||
|
||||
private String openid;
|
||||
|
||||
private String unionId;
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.cool.store.vo.wx;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
|
||||
@Data
|
||||
public class MiniProgramUserVO {
|
||||
private String openId;
|
||||
private String nickName;
|
||||
private String gender;
|
||||
private String language;
|
||||
private String city;
|
||||
private String province;
|
||||
private String country;
|
||||
private String avatarUrl;
|
||||
private String unionId;
|
||||
private String wxUnionId;
|
||||
}
|
||||
@@ -5,7 +5,10 @@ import org.springframework.context.annotation.Configuration;
|
||||
import org.springframework.web.client.RestTemplate;
|
||||
|
||||
/**
|
||||
* @author dong_gui on 2020/5/20.
|
||||
* @author zhangchenbiao
|
||||
* @FileName: RestTemplateConfig
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:29
|
||||
*/
|
||||
@Configuration
|
||||
public class RestTemplateConfig {
|
||||
|
||||
@@ -0,0 +1,42 @@
|
||||
package com.cool.store.http;
|
||||
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.cool.store.dto.enterprise.EnterpriseUserDTO;
|
||||
import com.cool.store.dto.wx.CodeSessionDTO;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.utils.RestTemplateUtil;
|
||||
import com.coolstore.base.dto.ResultDTO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: WechatRest
|
||||
* @Description:微信api
|
||||
* @date 2023-05-29 14:49
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WechatRest {
|
||||
|
||||
public CodeSessionDTO miniProgramJsCodeSession(String appId, String secret, String jsCode){
|
||||
String url = "https://api.weixin.qq.com/sns/jscode2session";
|
||||
ResponseEntity<CodeSessionDTO> responseEntity = null;
|
||||
try {
|
||||
responseEntity = RestTemplateUtil.loadGet(url, CodeSessionDTO.class);
|
||||
log.info("url:{}, response:{}", url, JSONObject.toJSONString(responseEntity));
|
||||
if(Objects.nonNull(responseEntity.getBody()) && responseEntity.getBody().isSuccess()){
|
||||
return responseEntity.getBody();
|
||||
}
|
||||
} catch (Exception e) {
|
||||
log.info("调用微信服务异常{}", e);
|
||||
throw new ServiceException(ErrorCodeEnum.WX_SERVICE_ERROR);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.cool.store.service;
|
||||
|
||||
import com.cool.store.dto.wx.MiniProgramLoginDTO;
|
||||
import com.cool.store.dto.wx.MiniProgramMsgDTO;
|
||||
import com.cool.store.vo.wx.CodeSessionVO;
|
||||
import com.cool.store.vo.wx.MiniProgramUserVO;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: WechatMiniAppService
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
public interface WechatMiniAppService {
|
||||
|
||||
|
||||
CodeSessionVO miniProgramLogin(MiniProgramLoginDTO param);
|
||||
|
||||
|
||||
MiniProgramUserVO queryMiniProgramUser(MiniProgramMsgDTO param);
|
||||
}
|
||||
@@ -0,0 +1,79 @@
|
||||
package com.cool.store.service.impl;
|
||||
|
||||
import com.alibaba.fastjson.JSON;
|
||||
import com.alibaba.fastjson.JSONObject;
|
||||
import com.aliyun.openservices.shade.org.apache.commons.lang3.StringUtils;
|
||||
import com.cool.store.constants.CommonConstants;
|
||||
import com.cool.store.dto.wx.CodeSessionDTO;
|
||||
import com.cool.store.dto.wx.MiniProgramLoginDTO;
|
||||
import com.cool.store.dto.wx.MiniProgramMsgDTO;
|
||||
import com.cool.store.enums.ErrorCodeEnum;
|
||||
import com.cool.store.exception.ServiceException;
|
||||
import com.cool.store.http.WechatRest;
|
||||
import com.cool.store.service.WechatMiniAppService;
|
||||
import com.cool.store.utils.AesUtil;
|
||||
import com.cool.store.utils.RedisUtilPool;
|
||||
import com.cool.store.vo.wx.CodeSessionVO;
|
||||
import com.cool.store.vo.wx.MiniProgramUserVO;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import java.text.MessageFormat;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: WechatMiniAppServiceImpl
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:29
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class WechatMiniAppServiceImpl implements WechatMiniAppService {
|
||||
|
||||
@Resource
|
||||
private RedisUtilPool redisUtilPool;
|
||||
@Resource
|
||||
private WechatRest wechatRest;
|
||||
|
||||
@Override
|
||||
public CodeSessionVO miniProgramLogin(MiniProgramLoginDTO param) {
|
||||
String jsCode = param.getJsCode();
|
||||
String lockKey = "codeSession:" + param.getAppid() + CommonConstants.MOSAICS + jsCode;
|
||||
boolean lock = redisUtilPool.lock(lockKey);
|
||||
if (!lock) {
|
||||
throw new ServiceException(ErrorCodeEnum.OPERATION_OVER_TIME);
|
||||
}
|
||||
String appid = param.getAppid();
|
||||
String secret = redisUtilPool.getString(MessageFormat.format(CommonConstants.WX_APP_SECRET_KEY, appid));
|
||||
if(StringUtils.isBlank(secret)){
|
||||
throw new ServiceException(ErrorCodeEnum.GET_APP_SECRET_ERROR);
|
||||
}
|
||||
CodeSessionDTO codeSession = wechatRest.miniProgramJsCodeSession(appid, secret, jsCode);
|
||||
String openid = codeSession.getOpenid();
|
||||
String sessionCacheKey = MessageFormat.format(CommonConstants.MINI_PROGRAM_SESSION_KEY, appid, openid);
|
||||
redisUtilPool.setString(sessionCacheKey, codeSession.getSessionKey(), CommonConstants.THREE_DAY_SECONDS);
|
||||
String unionId = codeSession.getUnionId();
|
||||
log.info("小程序登录:{}", unionId);
|
||||
//todo 保存授权信息 判断是否第一次授权
|
||||
return CodeSessionVO.builder().openid(openid).unionId(unionId).build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public MiniProgramUserVO queryMiniProgramUser(MiniProgramMsgDTO param) {
|
||||
String sessionCacheKey = MessageFormat.format(CommonConstants.MINI_PROGRAM_SESSION_KEY, param.getAppid(), param.getOpenid());
|
||||
String sessionKey = redisUtilPool.getString(sessionCacheKey);
|
||||
if (StringUtils.isBlank(sessionKey)) {
|
||||
throw new ServiceException(ErrorCodeEnum.SESSION_KEY_ERROR);
|
||||
}
|
||||
log.info("sessionKey {}", sessionKey);
|
||||
String decryptUser = AesUtil.decryptWechat(sessionKey, param.getEncryptedData(), param.getIvStr());
|
||||
log.info("解密用户信息:{}", decryptUser);
|
||||
MiniProgramUserVO miniProgramUser = JSON.parseObject(decryptUser, MiniProgramUserVO.class);
|
||||
if (Objects.isNull(miniProgramUser)) {
|
||||
throw new ServiceException("获取小程序用户信息失败");
|
||||
}
|
||||
return miniProgramUser;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
package com.cool.store.controller;
|
||||
|
||||
|
||||
import com.cool.store.dto.wx.MiniProgramLoginDTO;
|
||||
import com.cool.store.dto.wx.MiniProgramMsgDTO;
|
||||
import com.cool.store.response.ResponseResult;
|
||||
import com.cool.store.service.WechatMiniAppService;
|
||||
import com.cool.store.vo.wx.CodeSessionVO;
|
||||
import com.cool.store.vo.wx.MiniProgramUserVO;
|
||||
import io.swagger.annotations.Api;
|
||||
import io.swagger.annotations.ApiOperation;
|
||||
import org.springframework.web.bind.annotation.*;
|
||||
|
||||
import javax.annotation.Resource;
|
||||
import javax.validation.Valid;
|
||||
|
||||
/**
|
||||
* @author zhangchenbiao
|
||||
* @FileName: MiniProgramAppController
|
||||
* @Description:
|
||||
* @date 2023-05-29 14:28
|
||||
*/
|
||||
|
||||
@Api(tags = "微信小程序app接口")
|
||||
@RestController
|
||||
@RequestMapping("/appApi/mini-program")
|
||||
public class MiniProgramAppController {
|
||||
|
||||
@Resource
|
||||
private WechatMiniAppService wechatMiniAppService;
|
||||
|
||||
@ApiOperation("小程序登录")
|
||||
@PostMapping("/code/login")
|
||||
public ResponseResult<CodeSessionVO> login(@RequestBody @Valid MiniProgramLoginDTO param) {
|
||||
CodeSessionVO codeSessionVO = wechatMiniAppService.miniProgramLogin(param);
|
||||
return ResponseResult.success(codeSessionVO);
|
||||
}
|
||||
|
||||
@ApiOperation("获取小程序用户信息")
|
||||
@PostMapping("/user")
|
||||
public ResponseResult<MiniProgramUserVO> queryMiniProgramUser(@RequestBody @Valid MiniProgramMsgDTO param) {
|
||||
MiniProgramUserVO miniProgramUserVO = wechatMiniAppService.queryMiniProgramUser(param);
|
||||
return ResponseResult.success(miniProgramUserVO);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user