微信小程序登录
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 REFRESH_TOKEN_EXPIRE = 60*60*24*30;
|
||||||
|
|
||||||
|
public static final int THREE_DAY_SECONDS = 60*60*24*3;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 系统用户id
|
* 系统用户id
|
||||||
*/
|
*/
|
||||||
public static final String SYSTEM_USER_ID = "system";
|
public static final String SYSTEM_USER_ID = "system";
|
||||||
public static final String COMMA = ",";
|
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;
|
public static final int ZERO = 0;
|
||||||
|
|||||||
@@ -35,6 +35,10 @@ public enum ErrorCodeEnum {
|
|||||||
ENTERPRISE_NOT_EXIST(1021020,"企业不存在",null),
|
ENTERPRISE_NOT_EXIST(1021020,"企业不存在",null),
|
||||||
USER_NOT_EXIST(1021021,"用户不存在",null),
|
USER_NOT_EXIST(1021021,"用户不存在",null),
|
||||||
USER_WAIT_AUDIT(1021018,"账号审核中,请联系企业管理员",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) ;
|
return setnx.equals(1L) ;
|
||||||
}
|
}
|
||||||
}.getResult();
|
}.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;
|
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
|
@Configuration
|
||||||
public class RestTemplateConfig {
|
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