From 72c07d2a09da577db8b47c34c35576da0d34cee1 Mon Sep 17 00:00:00 2001 From: callmeyan Date: Sun, 20 Nov 2022 23:46:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=9B=B4=E6=96=B0=E7=99=BB=E5=BD=95=E7=BB=93?= =?UTF-8?q?=E6=9E=84?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- api/pom.xml | 10 +++ .../point/api/config/AppResponseConfig.java | 47 +++++++++++ .../api/config/ResponseExceptionConfig.java | 39 +++++++++ .../api/controller/WechatController.java | 26 +++--- .../xiaoyan/point/api/error/BizException.java | 21 +++-- .../xiaoyan/point/api/pojo/dto/ApiResult.java | 34 ++++++++ .../point/api/pojo/dto/CodeSessionData.java | 33 ++++++++ .../point/api/pojo/dto/WechatUserInfo.java | 33 ++++++++ .../point/api/pojo/vo/UserLoginData.java | 16 ++++ .../point/api/service/UserInfoService.java | 5 +- .../api/service/impl/UserInfoServiceImpl.java | 35 ++++---- .../point/api/util/WechatDecryptDataUtil.java | 82 +++++++++++++++++++ .../me/xiaoyan/point/api/WechatTests.java | 26 ++++++ 13 files changed, 369 insertions(+), 38 deletions(-) create mode 100644 api/src/main/java/me/xiaoyan/point/api/config/AppResponseConfig.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/config/ResponseExceptionConfig.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/pojo/dto/ApiResult.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/pojo/dto/CodeSessionData.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/pojo/dto/WechatUserInfo.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/pojo/vo/UserLoginData.java create mode 100644 api/src/main/java/me/xiaoyan/point/api/util/WechatDecryptDataUtil.java create mode 100644 api/src/test/java/me/xiaoyan/point/api/WechatTests.java diff --git a/api/pom.xml b/api/pom.xml index 1ebca30..abcbebe 100644 --- a/api/pom.xml +++ b/api/pom.xml @@ -35,6 +35,11 @@ druid-spring-boot-starter 1.2.14 + + com.google.code.gson + gson + 2.8.9 + org.springframework.boot @@ -72,6 +77,11 @@ springfox-boot-starter 3.0.0 + + org.bouncycastle + bcprov-jdk15on + 1.57 + diff --git a/api/src/main/java/me/xiaoyan/point/api/config/AppResponseConfig.java b/api/src/main/java/me/xiaoyan/point/api/config/AppResponseConfig.java new file mode 100644 index 0000000..18280b0 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/config/AppResponseConfig.java @@ -0,0 +1,47 @@ +package me.xiaoyan.point.api.config; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.SneakyThrows; +import me.xiaoyan.point.api.pojo.dto.ApiResult; +import org.springframework.core.MethodParameter; +import org.springframework.http.MediaType; +import org.springframework.http.converter.HttpMessageConverter; +import org.springframework.http.server.ServerHttpRequest; +import org.springframework.http.server.ServerHttpResponse; +import org.springframework.web.bind.annotation.RestControllerAdvice; +import org.springframework.web.servlet.mvc.method.annotation.ResponseBodyAdvice; + +import javax.annotation.Resource; +import java.util.List; + +@RestControllerAdvice +public class AppResponseConfig implements ResponseBodyAdvice { + @Resource + private ObjectMapper objectMapper; + + @Override + public boolean supports(MethodParameter methodParameter, Class> aClass) { + return true; + } + + @SneakyThrows + @Override + public Object beforeBodyWrite(Object o, MethodParameter methodParameter, MediaType mediaType, Class> aClass, ServerHttpRequest serverHttpRequest, ServerHttpResponse serverHttpResponse) { + long traceId = 0; + List header = serverHttpResponse.getHeaders().get("x-trace-id"); + if (header != null && header.size() > 0){ + String xTraceId = header.get(0); + if (xTraceId != null) { + traceId = Long.valueOf(xTraceId); + } + } + if (o instanceof String) { + return objectMapper.writeValueAsString(ApiResult.success(o).setTraceId(traceId)); + } + if (o instanceof ApiResult) { + return ((ApiResult) o).setTraceId(traceId); + } + + return ApiResult.success(o).setTraceId(traceId); + } +} \ No newline at end of file diff --git a/api/src/main/java/me/xiaoyan/point/api/config/ResponseExceptionConfig.java b/api/src/main/java/me/xiaoyan/point/api/config/ResponseExceptionConfig.java new file mode 100644 index 0000000..d58ce46 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/config/ResponseExceptionConfig.java @@ -0,0 +1,39 @@ +package me.xiaoyan.point.api.config; + +import me.xiaoyan.point.api.error.BizException; +import me.xiaoyan.point.api.pojo.dto.ApiResult; +import org.springframework.http.HttpStatus; +import org.springframework.validation.BindingResult; +import org.springframework.web.bind.MethodArgumentNotValidException; +import org.springframework.web.bind.annotation.ExceptionHandler; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; +import org.springframework.web.bind.annotation.RestControllerAdvice; + +import javax.servlet.http.HttpServletRequest; + +@RestControllerAdvice +public class ResponseExceptionConfig { + @ExceptionHandler(value = BizException.class) // 要捕获的异常类型 + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public ApiResult bizExceptionHandler(HttpServletRequest req, BizException e) { + return ApiResult.error(e.getCode(), e.getMessage()); + } + // 参数异常 + @ExceptionHandler(value = MethodArgumentNotValidException.class) // 要捕获的异常类型 + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public ApiResult bizExceptionHandler(HttpServletRequest req, MethodArgumentNotValidException e) { + System.out.println(e.getFieldError().getDefaultMessage()); + return ApiResult.error(1201, e.getBindingResult().getAllErrors().get(0).getDefaultMessage()); + } + + // 没有单独处理的异常都会被此方法处理 + @ExceptionHandler(value = Exception.class) // 要捕获的异常类型 + @ResponseBody + @ResponseStatus(HttpStatus.OK) + public ApiResult exceptionHandler(HttpServletRequest req, Exception e) { + return ApiResult.error(-1, "App Internal Error:" + e.getMessage()); + } +} diff --git a/api/src/main/java/me/xiaoyan/point/api/controller/WechatController.java b/api/src/main/java/me/xiaoyan/point/api/controller/WechatController.java index 0f28de7..92e5a0c 100644 --- a/api/src/main/java/me/xiaoyan/point/api/controller/WechatController.java +++ b/api/src/main/java/me/xiaoyan/point/api/controller/WechatController.java @@ -1,29 +1,29 @@ package me.xiaoyan.point.api.controller; -import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; -import org.apache.hc.client5.http.classic.HttpClient; -import org.apache.hc.client5.http.classic.methods.HttpGet; -import org.apache.hc.client5.http.impl.classic.CloseableHttpClient; -import org.apache.hc.client5.http.impl.classic.CloseableHttpResponse; -import org.apache.hc.client5.http.impl.classic.HttpClients; -import org.apache.hc.core5.http.HttpResponse; -import org.apache.hc.core5.http.io.entity.EntityUtils; -import org.apache.hc.core5.http.message.BasicClassicHttpResponse; -import org.springframework.beans.factory.annotation.Value; +import me.xiaoyan.point.api.pojo.UserInfo; +import me.xiaoyan.point.api.pojo.vo.UserLoginData; +import me.xiaoyan.point.api.service.UserInfoService; +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.RestController; -import java.util.Map; +import javax.annotation.Resource; +import javax.validation.constraints.NotNull; @RestController public class WechatController { + @Resource + private UserInfoService userInfoService; // code => openid等信息 // 使用wx.login获取到的code进行登录,完成登录后下发用户登录凭证 @RequestMapping("/wechat/login") @SneakyThrows - public String login(String code) { - return null; + public UserInfo login(@Validated @RequestBody UserLoginData data) { + //@NotNull(message = "登录code不能为空") + + return userInfoService.login(data); } } diff --git a/api/src/main/java/me/xiaoyan/point/api/error/BizException.java b/api/src/main/java/me/xiaoyan/point/api/error/BizException.java index 27a9e35..dc70520 100644 --- a/api/src/main/java/me/xiaoyan/point/api/error/BizException.java +++ b/api/src/main/java/me/xiaoyan/point/api/error/BizException.java @@ -1,18 +1,23 @@ package me.xiaoyan.point.api.error; +import lombok.Data; + +/** + * 自定义的业务异常类 + */ +@Data public class BizException extends RuntimeException { - /** - * 错误码 - */ private int code; - public BizException(int code, String message) { + private BizException(int code, String message) { super(message); this.code = code; } - public BizException(Throwable e) { - super(e.getMessage()); - this.code = 500; + public static BizException create(String message) { + return new BizException(-1, message); } -} + public static BizException create(int code,String message) { + return new BizException(code, message); + } +} \ No newline at end of file diff --git a/api/src/main/java/me/xiaoyan/point/api/pojo/dto/ApiResult.java b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/ApiResult.java new file mode 100644 index 0000000..3454be6 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/ApiResult.java @@ -0,0 +1,34 @@ +package me.xiaoyan.point.api.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.ToString; +import lombok.experimental.Accessors; +import me.xiaoyan.point.api.error.BizException; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +@ToString +@AllArgsConstructor +@NoArgsConstructor +public class ApiResult implements Serializable { + private int code; + private String message; + private Object data; + private long traceId; + + public static ApiResult success(Object data) { + return new ApiResult(0, "success", data,0); + } + + public static ApiResult error(int code, String message) { + return new ApiResult(code, message, null,0); + } + + public static ApiResult error(BizException e) { + return new ApiResult(e.getCode(), e.getMessage(), null,0); + } +} diff --git a/api/src/main/java/me/xiaoyan/point/api/pojo/dto/CodeSessionData.java b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/CodeSessionData.java new file mode 100644 index 0000000..a970fa4 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/CodeSessionData.java @@ -0,0 +1,33 @@ +package me.xiaoyan.point.api.pojo.dto; + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.Accessors; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +@Builder +@NoArgsConstructor +@AllArgsConstructor +public class CodeSessionData implements Serializable { + /** + * 用户唯一标识 + */ + private String openid; + /** + * 会话密钥,用于签名或解密 + */ + private String sessionKey; + /** + * 用户在开放平台的唯一标识符 + */ + private String unionid; + + private Integer errcode; + + private String errmsg; +} diff --git a/api/src/main/java/me/xiaoyan/point/api/pojo/dto/WechatUserInfo.java b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/WechatUserInfo.java new file mode 100644 index 0000000..0d6981b --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/pojo/dto/WechatUserInfo.java @@ -0,0 +1,33 @@ +package me.xiaoyan.point.api.pojo.dto; + +import lombok.*; +import lombok.experimental.Accessors; +import me.xiaoyan.point.api.pojo.UserInfo; + +import java.io.Serializable; + +@Data +@Accessors(chain = true) +@Builder +@ToString +@NoArgsConstructor +@AllArgsConstructor +public class WechatUserInfo implements Serializable { + private String openId; + private String unionId; + private String avatarUrl; + private String nickName; + private Integer gender; + private String city; + private String province; + private String country; + + public UserInfo getUserinfo() { + return UserInfo.builder() + .province(province).city(city) + .nickname(nickName) + .headImage(avatarUrl) + .openId(openId) + .build(); + } +} diff --git a/api/src/main/java/me/xiaoyan/point/api/pojo/vo/UserLoginData.java b/api/src/main/java/me/xiaoyan/point/api/pojo/vo/UserLoginData.java new file mode 100644 index 0000000..0faf4e4 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/pojo/vo/UserLoginData.java @@ -0,0 +1,16 @@ +package me.xiaoyan.point.api.pojo.vo; + +import lombok.Data; + +import javax.validation.constraints.NotEmpty; +import java.io.Serializable; + +@Data +public class UserLoginData implements Serializable { + @NotEmpty(message = "encryptedData参数不能为空") + private String encryptedData; + @NotEmpty(message = "iv参数不能为空") + private String iv; + @NotEmpty(message = "code参数不能为空") + private String code; +} diff --git a/api/src/main/java/me/xiaoyan/point/api/service/UserInfoService.java b/api/src/main/java/me/xiaoyan/point/api/service/UserInfoService.java index 26a59ac..6348036 100644 --- a/api/src/main/java/me/xiaoyan/point/api/service/UserInfoService.java +++ b/api/src/main/java/me/xiaoyan/point/api/service/UserInfoService.java @@ -2,13 +2,14 @@ package me.xiaoyan.point.api.service; import com.baomidou.mybatisplus.extension.service.IService; import me.xiaoyan.point.api.pojo.UserInfo; +import me.xiaoyan.point.api.pojo.vo.UserLoginData; public interface UserInfoService extends IService { /** * 用户的登录 - * @param code 执行wx.login成功后的code + * @param data * @return */ - public UserInfo login(String code); + public UserInfo login(UserLoginData data); } diff --git a/api/src/main/java/me/xiaoyan/point/api/service/impl/UserInfoServiceImpl.java b/api/src/main/java/me/xiaoyan/point/api/service/impl/UserInfoServiceImpl.java index 26181fc..fe59b0e 100644 --- a/api/src/main/java/me/xiaoyan/point/api/service/impl/UserInfoServiceImpl.java +++ b/api/src/main/java/me/xiaoyan/point/api/service/impl/UserInfoServiceImpl.java @@ -4,11 +4,14 @@ import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl; import com.fasterxml.jackson.databind.ObjectMapper; import lombok.SneakyThrows; import lombok.extern.slf4j.Slf4j; +import me.xiaoyan.point.api.util.WechatDecryptDataUtil; import me.xiaoyan.point.api.error.BizException; -import me.xiaoyan.point.api.mapper.PointMapper; import me.xiaoyan.point.api.mapper.UserInfoMapper; import me.xiaoyan.point.api.pojo.Point; import me.xiaoyan.point.api.pojo.UserInfo; +import me.xiaoyan.point.api.pojo.dto.CodeSessionData; +import me.xiaoyan.point.api.pojo.dto.WechatUserInfo; +import me.xiaoyan.point.api.pojo.vo.UserLoginData; import me.xiaoyan.point.api.service.PointRecordService; import me.xiaoyan.point.api.service.PointService; import me.xiaoyan.point.api.service.UserInfoService; @@ -21,9 +24,6 @@ import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; -import java.io.IOException; -import java.util.Map; - @Service @Slf4j public class UserInfoServiceImpl extends ServiceImpl @@ -63,16 +63,21 @@ public class UserInfoServiceImpl extends ServiceImpl @Transactional @Override - public UserInfo login(String code) { - String openId = getOpenIdByCode(code); - // TODO 获取用户的基本信息 - + public UserInfo login(UserLoginData data) { + // 使用code换取登录session相关数据 + CodeSessionData sessionData = getOpenIdByCode(data.getCode()); // 使用openid查询用户信息 - final UserInfo userInfo = this.getBaseMapper().selectOneByOpenId(openId); + final UserInfo userInfo = this.getBaseMapper().selectOneByOpenId(sessionData.getOpenid()); // 判断用户信息是否存在 if (userInfo == null) { + // 先解码获取用户数据 + WechatUserInfo wechatUserInfo = WechatDecryptDataUtil.decryptData( + data.getEncryptedData(), + sessionData.getSessionKey(), + data.getIv() + ); // 不存在走 第一次登录流程 - firstLogin(userInfo); + firstLogin(wechatUserInfo.getUserinfo()); } // 首先 return userInfo; @@ -88,7 +93,7 @@ public class UserInfoServiceImpl extends ServiceImpl // 使用code换取openId @SneakyThrows - private String getOpenIdByCode(String code) { + private CodeSessionData getOpenIdByCode(String code) { String url = CODE2SESSION + "?appid=" + app_id + "&secret=" + app_secret + "&js_code=" + code + "&grant_type=authorization_code"; try (CloseableHttpClient client = HttpClients.createDefault()) { @@ -96,11 +101,11 @@ public class UserInfoServiceImpl extends ServiceImpl final CloseableHttpResponse response = (CloseableHttpResponse) client.execute(get); final String content = EntityUtils.toString(response.getEntity()); ObjectMapper mapper = new ObjectMapper(); - final Map map = mapper.convertValue(content, Map.class); - if ((Integer) map.get("errcode") != 0) { - throw new BizException(1101, "换取code失败(" + map.get("errmsg") + ")"); + final CodeSessionData data = mapper.convertValue(content, CodeSessionData.class); + if (data.getErrcode() != 0) { + throw BizException.create(1101, "换取code失败(" + data.getErrmsg() + ")"); } - return map.get("openid").toString(); + return data; } } } diff --git a/api/src/main/java/me/xiaoyan/point/api/util/WechatDecryptDataUtil.java b/api/src/main/java/me/xiaoyan/point/api/util/WechatDecryptDataUtil.java new file mode 100644 index 0000000..0614844 --- /dev/null +++ b/api/src/main/java/me/xiaoyan/point/api/util/WechatDecryptDataUtil.java @@ -0,0 +1,82 @@ +package me.xiaoyan.point.api.util; + +import com.google.gson.Gson; +import me.xiaoyan.point.api.pojo.dto.WechatUserInfo; +import org.bouncycastle.jce.provider.BouncyCastleProvider; +import org.springframework.util.StringUtils; + +import javax.crypto.Cipher; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; +import java.security.Key; +import java.security.Security; +import java.util.Arrays; +import java.util.Base64; + +/** + * 微信工具类 + */ +public class WechatDecryptDataUtil { + + public static WechatUserInfo decryptData(String encryptDataB64, String sessionKeyB64, String ivB64) { + String result = new String( + decryptOfDiyIV( + Base64.getDecoder().decode(encryptDataB64), + Base64.getDecoder().decode(sessionKeyB64), + Base64.getDecoder().decode(ivB64) + ) + ); + if(!StringUtils.hasLength(result)){ + throw new RuntimeException("解码失败"); + } + return (new Gson()).fromJson(result,WechatUserInfo.class); + } + + private static final String KEY_ALGORITHM = "AES"; + private static final String ALGORITHM_STR = "AES/CBC/PKCS7Padding"; + private static Key key; + private static Cipher cipher; + + private static void init(byte[] keyBytes) { + // 如果密钥不足16位,那么就补足. 这个if 中的内容很重要 + int base = 16; + if (keyBytes.length % base != 0) { + int groups = keyBytes.length / base + (keyBytes.length % base != 0 ? 1 : 0); + byte[] temp = new byte[groups * base]; + Arrays.fill(temp, (byte) 0); + System.arraycopy(keyBytes, 0, temp, 0, keyBytes.length); + keyBytes = temp; + } + // 初始化 + Security.addProvider(new BouncyCastleProvider()); + // 转化成JAVA的密钥格式 + key = new SecretKeySpec(keyBytes, KEY_ALGORITHM); + try { + // 初始化cipher + cipher = Cipher.getInstance(ALGORITHM_STR, "BC"); + } catch (Exception e) { + e.printStackTrace(); + } + } + + /** + * 解密方法 + * + * @param encryptedData 要解密的字符串 + * @param keyBytes 解密密钥 + * @param ivs 自定义对称解密算法初始向量 iv + * @return 解密后的字节数组 + */ + private static byte[] decryptOfDiyIV(byte[] encryptedData, byte[] keyBytes, byte[] ivs) { + byte[] encryptedText = null; + init(keyBytes); + try { + cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(ivs)); + encryptedText = cipher.doFinal(encryptedData); + } catch (Exception e) { + e.printStackTrace(); + } + return encryptedText; + } + +} diff --git a/api/src/test/java/me/xiaoyan/point/api/WechatTests.java b/api/src/test/java/me/xiaoyan/point/api/WechatTests.java new file mode 100644 index 0000000..554899e --- /dev/null +++ b/api/src/test/java/me/xiaoyan/point/api/WechatTests.java @@ -0,0 +1,26 @@ +package me.xiaoyan.point.api; + +import me.xiaoyan.point.api.pojo.dto.WechatUserInfo; +import me.xiaoyan.point.api.util.WechatDecryptDataUtil; +import org.junit.jupiter.api.DisplayName; +import org.junit.jupiter.api.Test; + +public class WechatTests { + + + @DisplayName("解密用户信息") + @Test + void testDecode(){ + String encryptedData = "Ezl1S3BHjBeg/HP0Nim/c9eLLYP5L1kgvqSpR+RQYFG6c3Qx0K5U6btEY5IByCw1OsnZ0hKDccNJn3VA/ZgpkxjDODt+XLcph3KqEL6LDU9BLDRFxCI7u+eHBorz5HWYzGITXiuyPb9NWmGAPXwwp0abFqqaycb5u4oii7I/tnh7NIRIcMxAft1YfbVdzQDjraRHFH5hg6Eh4RGSjy6rg1bG/sMecw4+XWM1psTjKBYNwtsG5oxBWja6DniPhmWU6ZwjVMfgJX5Z7wcw2vtmuDPOMCEE1SEcwmTcQ5YKLlPsPBAJhrF3Lxg9oTD1/IlZ"; + String iv = "ckSRPBLA61WONzUDINIWMg=="; + + + WechatUserInfo wechatUserInfo = WechatDecryptDataUtil.decryptData( + "SCK0Ik7THl+USkwTRqTQ9BYGe6rWlXosQ8fWA3I3AsFHTCuPnjbsjFooIEZVcS6mq911XeP5BJJBpPU6A1O3aNuC9L7ebXqTMQX83bVBtaDQySvCIlyyq26xhm8AbWWexl5994NJDpKkNml9ilbYia99bF8bXzXvLCksQQkz82EpZTqztzmCrTdFBZOIrJ+lDnl7rSBWJvVVtoagzgSq2Ux59LcJtxCukIUoZ8fz54//Hm4GhrLucO4zPKTi087f77Pd9K9Rz3LLJ79NMQHHQLtZ38Ws79IKoHBZ7xHXbl3O8xPeTBrrWeHbNfNs1CbOyoe0RwXVjs/fMR9451PeLVM5jg4fj3IDyTFjpx5aUzQRaIrSY/BjVFJoxU/viwQC6LsCBOyXl5uV5h+qIPC5suFbmhl5Q56eU07wbOjNxktEIJsIgbrT+GWRZVPba3dUo+6RoZySxCMA16TRKfuTwjXceV3oNueFdTNyw05s9N43OkrLeKcz1dFeNnpA9DHEVngB1J7MY4RkazdKaWzrld1DjxW6+rk01GgtAr3+H88=", + "PKX9EeH6y+pzz8qYrga2jQ==", + "Ql/m+Ksll5ziCCZj+07J6g==" + + ); + System.out.println(wechatUserInfo); + } +}